From ad929b03234a9a22f0c2a49199f2527a0c0567b4 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Tue, 21 May 2024 18:41:46 +0800 Subject: [PATCH] add "create sub project" --- .../project/entity/ProjectRepository.kt | 4 +- .../project/service/ProjectsService.kt | 174 +++++++++++------- .../modules/project/web/ProjectsController.kt | 9 +- .../project/web/models/MainProjectDetails.kt | 30 +++ .../project/web/models/NewProjectRequest.kt | 1 + 5 files changed, 150 insertions(+), 68 deletions(-) create mode 100644 src/main/java/com/ffii/tsms/modules/project/web/models/MainProjectDetails.kt diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt index 0f4b06b..b1c38f9 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt @@ -21,12 +21,12 @@ interface ProjectRepository : AbstractRepository { fun findAllByPlanStartLessThanEqualAndPlanEndGreaterThanEqual(remainedDateFrom: LocalDate?, remainedDateTo: LocalDate?):List - //fun findAllByDateRange(start: LocalDate, end: LocalDate): List - @Query("SELECT max(cast(substring_index(substring_index(p.code, '-', 2), '-', -1) as long)) FROM Project p WHERE p.isClpProject = ?1 and p.id != ?2" + "") fun getLatestCodeNumberByMainProject(isClpProject: Boolean, id: Serializable?): Long? @Query("SELECT max(cast(substring_index(p.code, '-', -1) as long)) FROM Project p WHERE p.code like ?1 and p.id != ?2") fun getLatestCodeNumberBySubProject(code: String, id: Serializable?): Long? + + fun findAllByStatusIsNotAndMainProjectIsNull(status: String): List } \ 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 ad4e206..73ba11f 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 @@ -6,6 +6,7 @@ import com.ffii.tsms.modules.data.service.CustomerContactService import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo import com.ffii.tsms.modules.data.service.CustomerService import com.ffii.tsms.modules.data.service.GradeService +import com.ffii.tsms.modules.data.service.SubsidiaryContactService import com.ffii.tsms.modules.project.entity.* import com.ffii.tsms.modules.project.entity.Milestone import com.ffii.tsms.modules.project.entity.projections.InvoiceInfoSearchInfo @@ -44,10 +45,12 @@ open class ProjectsService( private val taskGroupRepository: TaskGroupRepository, private val timesheetRepository: TimesheetRepository, private val taskTemplateRepository: TaskTemplateRepository, - private val subsidiaryRepository: SubsidiaryRepository, private val subsidiaryContactRepository: SubsidiaryContactRepository + private val subsidiaryContactService: SubsidiaryContactService, + private val subsidiaryContactRepository: SubsidiaryContactRepository ) { open fun allProjects(): List { - return projectRepository.findProjectSearchInfoByOrderByCreatedDesc().sortedByDescending { it.status?.lowercase() != "deleted" } + return projectRepository.findProjectSearchInfoByOrderByCreatedDesc() + .sortedByDescending { it.status?.lowercase() != "deleted" } } open fun allInvoices(): List { @@ -63,63 +66,57 @@ open class ProjectsService( } open fun markDeleted(id: Long) { - projectRepository.save(projectRepository.findById(id).orElseThrow() - .apply { - deleted = true - status = "Deleted" - }) + projectRepository.save(projectRepository.findById(id).orElseThrow().apply { + deleted = true + status = "Deleted" + }) } 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.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 + ) } + } } } ?: emptyList() } open fun allProjectWithTasks(): List { return projectRepository.findAll().map { project -> - ProjectWithTasks( - id = project.id!!, + ProjectWithTasks(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) -> + .associateBy { milestone -> milestone.taskGroup!!.id!! }.mapValues { (_, milestone) -> MilestoneInfo( startDate = milestone.startDate?.format(DateTimeFormatter.ISO_LOCAL_DATE), endDate = milestone.endDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ) - } - ) + }) } } @@ -135,15 +132,28 @@ open class ProjectsService( if (latestProjectCode != null) { val lastFix = latestProjectCode - return "$prefix-" + String.format("%04d", lastFix + 1L) + return "$prefix-" + String.format("%04d", lastFix + 1L) } else { return "$prefix-0001" } } + + open fun createSubProjectCode(mainProject: Project, project: Project): String { + val prefix = mainProject.code + + val latestProjectCode = projectRepository.getLatestCodeNumberBySubProject(prefix!!, project.id ?: -1) + + if (latestProjectCode != null) { + val lastFix = latestProjectCode + return "$prefix-" + String.format("%03d", lastFix + 1L) + } else { + return "$prefix-001" + } + } + @Transactional open fun saveProject(request: NewProjectRequest): NewProjectResponse { - val projectCategory = - projectCategoryRepository.findById(request.projectCategoryId).orElseThrow() + val projectCategory = projectCategoryRepository.findById(request.projectCategoryId).orElseThrow() val fundingType = fundingTypeRepository.findById(request.fundingTypeId).orElseThrow() val serviceType = serviceTypeRepository.findById(request.serviceTypeId).orElseThrow() val contractType = contractTypeRepository.findById(request.contractTypeId).orElseThrow() @@ -158,6 +168,7 @@ open class ProjectsService( val subsidiaryContact = subsidiaryContactRepository.findById(request.clientContactId).orElse(null) val clientContact = customerContactService.findByContactId(request.clientContactId) val customerSubsidiary = request.clientSubsidiaryId?.let { subsidiaryService.findSubsidiary(it) } + val mainProject = if (request.mainProjectId != null && request.mainProjectId > 0) projectRepository.findById(request.mainProjectId).orElse(null) else null val allTasksMap = tasksService.allTasks().associateBy { it.id } val taskGroupMap = tasksService.allTaskGroups().associateBy { it.id } @@ -169,7 +180,7 @@ open class ProjectsService( project.apply { name = request.projectName description = request.projectDescription - code = if (this.code.isNullOrEmpty()) createProjectCode(request.isClpProject, project) else this.code + code = if (this.code.isNullOrEmpty() && request.mainProjectId == null) createProjectCode(request.isClpProject, project) else if (this.code.isNullOrEmpty() && request.mainProjectId != null && mainProject != null) createSubProjectCode(mainProject, project) else this.code expectedTotalFee = request.expectedProjectFee totalManhour = request.totalManhour actualStart = request.projectActualStart @@ -179,6 +190,7 @@ open class ProjectsService( else if (this.actualStart != null) "On-going" else "Pending To Start" isClpProject = request.isClpProject + this.mainProject = mainProject this.projectCategory = projectCategory this.fundingType = fundingType @@ -191,9 +203,12 @@ open class ProjectsService( this.teamLead = teamLead this.customer = customer - custLeadName = if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.name else subsidiaryContact.name - custLeadEmail = if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.email else subsidiaryContact.email - custLeadPhone = if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.phone else subsidiaryContact.phone + custLeadName = + if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.name else subsidiaryContact.name + custLeadEmail = + if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.email else subsidiaryContact.email + custLeadPhone = + if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.phone else subsidiaryContact.phone this.customerSubsidiary = customerSubsidiary } @@ -203,8 +218,7 @@ open class ProjectsService( val milestones = request.taskGroups.entries.map { (taskStageId, taskGroupAllocation) -> val taskGroup = taskGroupRepository.findById(taskStageId).orElse(TaskGroup()) ?: TaskGroup() val milestone = if (project.id != null && project.id!! > 0L) milestoneRepository.findByProjectAndTaskGroup( - project, - taskGroup + project, taskGroup ) ?: Milestone() else Milestone() milestone.apply { val newMilestone = this @@ -233,8 +247,7 @@ open class ProjectsService( taskGroupAllocation.taskIds.map { taskId -> val projectTask = if (project.id != null && project.id!! > 0L) projectTaskRepository.findByProjectAndTask( - project, - allTasksMap[taskId]!! + project, allTasksMap[taskId]!! ) ?: ProjectTask() else ProjectTask() projectTask.apply { @@ -252,8 +265,7 @@ open class ProjectsService( val gradeAllocations = request.manhourPercentageByGrade.entries.map { val gradeAllocation = if (project.id != null && project.id!! > 0L) gradeAllocationRepository.findByProjectAndGrade( - project, - gradeMap[it.key]!! + project, gradeMap[it.key]!! ) ?: GradeAllocation() else GradeAllocation() gradeAllocation.apply { @@ -313,16 +325,16 @@ open class ProjectsService( val project = projectRepository.findById(projectId) return project.getOrNull()?.let { - val customerContact = - it.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) } - ?: emptyList() + val subsidiaryContact = it.customerSubsidiary?.id?.let { subsidiaryId -> + subsidiaryContactService.findAllBySubsidiaryId(subsidiaryId) + } ?: emptyList() + val customerContact = it.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) } + ?: emptyList() - val milestoneMap = it.milestones - .filter { milestone -> milestone.taskGroup?.id != null } + val milestoneMap = it.milestones.filter { milestone -> milestone.taskGroup?.id != null } .associateBy { milestone -> milestone.taskGroup!!.id!! } - EditProjectDetails( - projectId = it.id, + EditProjectDetails(projectId = it.id, projectDeleted = it.deleted, projectCode = it.code, projectName = it.name, @@ -341,7 +353,7 @@ open class ProjectsService( buildingTypeIds = it.buildingTypes.mapNotNull { buildingType -> buildingType.id }, workNatureIds = it.workNatures.mapNotNull { workNature -> workNature.id }, clientId = it.customer?.id, - clientContactId = customerContact.find { contact -> contact.name == it.custLeadName }?.id, + clientContactId = subsidiaryContact.find { contact -> contact.name == it.custLeadName }?.id ?: customerContact.find { contact -> contact.name == it.custLeadName }?.id, clientSubsidiaryId = it.customerSubsidiary?.id, totalManhour = it.totalManhour, manhourPercentageByGrade = gradeAllocationRepository.findByProject(it) @@ -349,8 +361,7 @@ open class ProjectsService( .associate { allocation -> Pair(allocation.grade!!.id!!, allocation.manhour ?: 0.0) }, taskGroups = projectTaskRepository.findAllByProject(it) .mapNotNull { projectTask -> if (projectTask.task?.taskGroup?.id != null) projectTask.task else null } - .groupBy { task -> task.taskGroup!!.id!! } - .mapValues { (taskGroupId, tasks) -> + .groupBy { task -> task.taskGroup!!.id!! }.mapValues { (taskGroupId, tasks) -> TaskGroupAllocation( taskIds = tasks.mapNotNull { task -> task.id }, percentAllocation = milestoneMap[taskGroupId]?.stagePercentAllocation ?: 0.0 @@ -359,8 +370,9 @@ open class ProjectsService( allocatedStaffIds = staffAllocationRepository.findByProject(it) .mapNotNull { allocation -> allocation.staff?.id }, milestones = milestoneMap.mapValues { (_, milestone) -> - com.ffii.tsms.modules.project.web.models.Milestone( - startDate = milestone.startDate?.format(DateTimeFormatter.ISO_LOCAL_DATE), + com.ffii.tsms.modules.project.web.models.Milestone(startDate = milestone.startDate?.format( + DateTimeFormatter.ISO_LOCAL_DATE + ), endDate = milestone.endDate?.format(DateTimeFormatter.ISO_LOCAL_DATE), payments = milestone.milestonePayments.map { payment -> PaymentInputs( @@ -369,8 +381,7 @@ open class ProjectsService( description = payment.description!!, date = payment.date!!.format(DateTimeFormatter.ISO_LOCAL_DATE) ) - } - ) + }) }, expectedProjectFee = it.expectedTotalFee ) @@ -400,4 +411,37 @@ open class ProjectsService( open fun allWorkNatures(): List { return workNatureRepository.findAll() } + + open fun allMainProjects(): List { + val mainProjects: List = projectRepository.findAllByStatusIsNotAndMainProjectIsNull("Deleted") + + return mainProjects.map { project: Project -> + val subsidiaryContact = project.customerSubsidiary?.id?.let { subsidiaryId -> + subsidiaryContactService.findAllBySubsidiaryId(subsidiaryId) + } ?: emptyList() + val customerContact = project.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) } + ?: emptyList() + + MainProjectDetails( + projectId = project.id, + projectCode = project.code, + projectName = project.name, + projectCategoryId = project.projectCategory?.id, + projectDescription = project.description, + projectLeadId = project.teamLead?.id, + projectStatus = project.status, + isClpProject = project.isClpProject, + serviceTypeId = project.serviceType?.id, + fundingTypeId = project.fundingType?.id, + contractTypeId = project.contractType?.id, + locationId = project.location?.id, + buildingTypeIds = project.buildingTypes.mapNotNull { buildingType -> buildingType.id }, + workNatureIds = project.workNatures.mapNotNull { workNature -> workNature.id }, + clientId = project.customer?.id, + clientContactId = subsidiaryContact.find { contact -> contact.name == project.custLeadName }?.id ?: customerContact.find { contact -> contact.name == project.custLeadName }?.id, + clientSubsidiaryId = project.customerSubsidiary?.id, + expectedProjectFee = project.expectedTotalFee + ) + } + } } diff --git a/src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt b/src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt index 0bf696a..447a50b 100644 --- a/src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt +++ b/src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt @@ -2,8 +2,10 @@ package com.ffii.tsms.modules.project.web import com.ffii.core.exception.NotFoundException import com.ffii.tsms.modules.data.entity.* +import com.ffii.tsms.modules.project.entity.Project import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo import com.ffii.tsms.modules.project.entity.ProjectCategory +import com.ffii.tsms.modules.project.entity.ProjectRepository import com.ffii.tsms.modules.project.service.ProjectsService import com.ffii.tsms.modules.project.web.models.* import jakarta.validation.Valid @@ -12,12 +14,17 @@ import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/projects") -class ProjectsController(private val projectsService: ProjectsService) { +class ProjectsController(private val projectsService: ProjectsService, private val projectRepository: ProjectRepository) { @GetMapping fun allProjects(): List { return projectsService.allProjects() } + @GetMapping("/main") + fun allMainProjects(): List { + return projectsService.allMainProjects() + } + @DeleteMapping("/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) fun deleteProject(@PathVariable id: Long) { diff --git a/src/main/java/com/ffii/tsms/modules/project/web/models/MainProjectDetails.kt b/src/main/java/com/ffii/tsms/modules/project/web/models/MainProjectDetails.kt new file mode 100644 index 0000000..3c2a748 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/web/models/MainProjectDetails.kt @@ -0,0 +1,30 @@ +package com.ffii.tsms.modules.project.web.models + +data class MainProjectDetails ( + + // Project details + val projectId: Long?, + val projectCode: String?, + val projectName: String?, + + val projectCategoryId: Long?, + val projectDescription: String?, + + val projectLeadId: Long?, + val projectStatus: String?, + val isClpProject: Boolean?, + + val serviceTypeId: Long?, + val fundingTypeId: Long?, + val contractTypeId: Long?, + val locationId: Long?, + val buildingTypeIds: List, + val workNatureIds: List, + + // Client details + val clientId: Long?, + val clientContactId: Long?, + val clientSubsidiaryId: Long?, + + val expectedProjectFee: Double?, +) \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectRequest.kt b/src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectRequest.kt index 18bcfab..1f59b33 100644 --- a/src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectRequest.kt +++ b/src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectRequest.kt @@ -17,6 +17,7 @@ data class NewProjectRequest( val projectActualStart: LocalDate?, val projectActualEnd: LocalDate?, val isClpProject: Boolean?, + val mainProjectId: Long?, val serviceTypeId: Long, val fundingTypeId: Long,