Bläddra i källkod

add "create sub project"

tags/Baseline_30082024_BACKEND_UAT
cyril.tsui 1 år sedan
förälder
incheckning
ad929b0323
5 ändrade filer med 150 tillägg och 68 borttagningar
  1. +2
    -2
      src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt
  2. +109
    -65
      src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt
  3. +8
    -1
      src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt
  4. +30
    -0
      src/main/java/com/ffii/tsms/modules/project/web/models/MainProjectDetails.kt
  5. +1
    -0
      src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectRequest.kt

+ 2
- 2
src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt Visa fil

@@ -21,12 +21,12 @@ interface ProjectRepository : AbstractRepository<Project, Long> {

fun findAllByPlanStartLessThanEqualAndPlanEndGreaterThanEqual(remainedDateFrom: LocalDate?, remainedDateTo: LocalDate?):List<Project>

//fun findAllByDateRange(start: LocalDate, end: LocalDate): List<Project>

@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<Project>
}

+ 109
- 65
src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt Visa fil

@@ -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<ProjectSearchInfo> {
return projectRepository.findProjectSearchInfoByOrderByCreatedDesc().sortedByDescending { it.status?.lowercase() != "deleted" }
return projectRepository.findProjectSearchInfoByOrderByCreatedDesc()
.sortedByDescending { it.status?.lowercase() != "deleted" }
}

open fun allInvoices(): List<InvoiceSearchInfo> {
@@ -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<AssignedProject> {
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<ProjectWithTasks> {
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<WorkNature> {
return workNatureRepository.findAll()
}

open fun allMainProjects(): List<MainProjectDetails> {
val mainProjects: List<Project> = 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
)
}
}
}

+ 8
- 1
src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt Visa fil

@@ -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<ProjectSearchInfo> {
return projectsService.allProjects()
}

@GetMapping("/main")
fun allMainProjects(): List<MainProjectDetails> {
return projectsService.allMainProjects()
}

@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun deleteProject(@PathVariable id: Long) {


+ 30
- 0
src/main/java/com/ffii/tsms/modules/project/web/models/MainProjectDetails.kt Visa fil

@@ -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<Long>,
val workNatureIds: List<Long>,

// Client details
val clientId: Long?,
val clientContactId: Long?,
val clientSubsidiaryId: Long?,

val expectedProjectFee: Double?,
)

+ 1
- 0
src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectRequest.kt Visa fil

@@ -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,


Laddar…
Avbryt
Spara