|
|
@@ -40,8 +40,11 @@ open class ProjectsService( |
|
|
|
private val milestoneRepository: MilestoneRepository, |
|
|
|
private val gradeAllocationRepository: GradeAllocationRepository, |
|
|
|
private val projectTaskRepository: ProjectTaskRepository, |
|
|
|
private val milestonePaymentRepository: MilestonePaymentRepository, private val taskGroupRepository: TaskGroupRepository, |
|
|
|
private val timesheetRepository: TimesheetRepository |
|
|
|
private val milestonePaymentRepository: MilestonePaymentRepository, |
|
|
|
private val taskGroupRepository: TaskGroupRepository, |
|
|
|
private val timesheetRepository: TimesheetRepository, |
|
|
|
private val taskTemplateRepository: TaskTemplateRepository, |
|
|
|
private val subsidiaryRepository: SubsidiaryRepository, private val subsidiaryContactRepository: SubsidiaryContactRepository |
|
|
|
) { |
|
|
|
open fun allProjects(): List<ProjectSearchInfo> { |
|
|
|
return projectRepository.findProjectSearchInfoByOrderByCreatedDesc() |
|
|
@@ -60,34 +63,42 @@ open class ProjectsService( |
|
|
|
} |
|
|
|
|
|
|
|
open fun markDeleted(id: Long) { |
|
|
|
projectRepository.save(projectRepository.findById(id).orElseThrow().apply { deleted = true }) |
|
|
|
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 |
|
|
|
) |
|
|
|
} } |
|
|
|
.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() |
|
|
|
} |
|
|
@@ -102,10 +113,12 @@ open class ProjectsService( |
|
|
|
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) |
|
|
|
) } |
|
|
|
.mapValues { (_, milestone) -> |
|
|
|
MilestoneInfo( |
|
|
|
startDate = milestone.startDate?.format(DateTimeFormatter.ISO_LOCAL_DATE), |
|
|
|
endDate = milestone.endDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) |
|
|
|
) |
|
|
|
} |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
@@ -114,6 +127,19 @@ open class ProjectsService( |
|
|
|
return projectCategoryRepository.findAll() |
|
|
|
} |
|
|
|
|
|
|
|
open fun createProjectCode(isClpProject: Boolean?, project: Project): String { |
|
|
|
val checkIsClpProject = isClpProject != null && isClpProject |
|
|
|
val prefix = if (checkIsClpProject) "C" else "M" |
|
|
|
|
|
|
|
val latestProjectCode = projectRepository.findFirstByIsClpProjectAndIdNotOrderByIdDesc(checkIsClpProject, project.id) |
|
|
|
|
|
|
|
if (latestProjectCode != null) { |
|
|
|
val lastFix = latestProjectCode.code!!.split("-")[1] //C-0001 -> 0001 |
|
|
|
return "$prefix-" + String.format("%04d", lastFix.toLong() + 1L) |
|
|
|
} else { |
|
|
|
return "$prefix-0001" |
|
|
|
} |
|
|
|
} |
|
|
|
@Transactional |
|
|
|
open fun saveProject(request: NewProjectRequest): NewProjectResponse { |
|
|
|
val projectCategory = |
|
|
@@ -124,9 +150,12 @@ open class ProjectsService( |
|
|
|
val location = locationRepository.findById(request.locationId).orElseThrow() |
|
|
|
val buildingTypes = buildingTypeRepository.findAllById(request.buildingTypeIds).toMutableSet() |
|
|
|
val workNatures = workNatureRepository.findAllById(request.workNatureIds).toMutableSet() |
|
|
|
|
|
|
|
val taskTemplate = |
|
|
|
if (request.taskTemplateId != null && request.taskTemplateId > 0) taskTemplateRepository.findById(request.taskTemplateId) |
|
|
|
.orElseThrow() else null |
|
|
|
val teamLead = staffRepository.findById(request.projectLeadId).orElseThrow() |
|
|
|
val customer = customerService.findCustomer(request.clientId) |
|
|
|
val subsidiaryContact = subsidiaryContactRepository.findById(request.clientContactId).orElse(null) |
|
|
|
val clientContact = customerContactService.findByContactId(request.clientContactId) |
|
|
|
val customerSubsidiary = request.clientSubsidiaryId?.let { subsidiaryService.findSubsidiary(it) } |
|
|
|
|
|
|
@@ -134,15 +163,24 @@ open class ProjectsService( |
|
|
|
val taskGroupMap = tasksService.allTaskGroups().associateBy { it.id } |
|
|
|
val gradeMap = gradeService.allGrades().associateBy { it.id } |
|
|
|
|
|
|
|
val project = if (request.projectId != null && request.projectId > 0) projectRepository.findById(request.projectId).orElseThrow() else Project() |
|
|
|
val project = |
|
|
|
if (request.projectId != null && request.projectId > 0) projectRepository.findById(request.projectId) |
|
|
|
.orElseThrow() else Project() |
|
|
|
project.apply { |
|
|
|
name = request.projectName |
|
|
|
description = request.projectDescription |
|
|
|
code = request.projectCode |
|
|
|
println(this.code) |
|
|
|
println(this.code.isNullOrEmpty()) |
|
|
|
code = if (this.code.isNullOrEmpty()) createProjectCode(request.isClpProject, project) else this.code |
|
|
|
expectedTotalFee = request.expectedProjectFee |
|
|
|
totalManhour = request.totalManhour |
|
|
|
actualStart = request.projectActualStart |
|
|
|
actualEnd = request.projectActualEnd |
|
|
|
status = if (this.status == "Deleted" || this.deleted == true) "Deleted" |
|
|
|
else if (this.actualStart != null && this.actualEnd != null) "Completed" |
|
|
|
else if (this.actualStart != null) "On-going" |
|
|
|
else "Pending To Start" |
|
|
|
isClpProject = request.isClpProject |
|
|
|
|
|
|
|
this.projectCategory = projectCategory |
|
|
|
this.fundingType = fundingType |
|
|
@@ -151,12 +189,13 @@ open class ProjectsService( |
|
|
|
this.location = location |
|
|
|
this.buildingTypes = buildingTypes |
|
|
|
this.workNatures = workNatures |
|
|
|
this.taskTemplate = taskTemplate |
|
|
|
|
|
|
|
this.teamLead = teamLead |
|
|
|
this.customer = customer |
|
|
|
custLeadName = clientContact.name |
|
|
|
custLeadEmail = clientContact.email |
|
|
|
custLeadPhone = clientContact.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 |
|
|
|
} |
|
|
|
|
|
|
@@ -165,7 +204,10 @@ open class ProjectsService( |
|
|
|
val tasksToSave = mutableListOf<ProjectTask>() |
|
|
|
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) ?: Milestone() else Milestone() |
|
|
|
val milestone = if (project.id != null && project.id!! > 0L) milestoneRepository.findByProjectAndTaskGroup( |
|
|
|
project, |
|
|
|
taskGroup |
|
|
|
) ?: Milestone() else Milestone() |
|
|
|
milestone.apply { |
|
|
|
val newMilestone = this |
|
|
|
val requestMilestone = request.milestones[taskStageId] |
|
|
@@ -177,7 +219,8 @@ open class ProjectsService( |
|
|
|
this.milestonePayments.removeAll(this.milestonePayments) |
|
|
|
} |
|
|
|
requestMilestone?.payments?.map { |
|
|
|
val milestonePayment = milestonePaymentRepository.findById(it.id).orElse(MilestonePayment()) ?:MilestonePayment() |
|
|
|
val milestonePayment = |
|
|
|
milestonePaymentRepository.findById(it.id).orElse(MilestonePayment()) ?: MilestonePayment() |
|
|
|
this.milestonePayments.add(milestonePayment.apply { |
|
|
|
this.milestone = newMilestone |
|
|
|
this.description = it.description |
|
|
@@ -190,7 +233,11 @@ open class ProjectsService( |
|
|
|
this.stagePercentAllocation = taskGroupAllocation.percentAllocation |
|
|
|
|
|
|
|
taskGroupAllocation.taskIds.map { taskId -> |
|
|
|
val projectTask = if (project.id != null && project.id!! > 0L) projectTaskRepository.findByProjectAndTask(project, allTasksMap[taskId]!!) ?:ProjectTask() else ProjectTask() |
|
|
|
val projectTask = |
|
|
|
if (project.id != null && project.id!! > 0L) projectTaskRepository.findByProjectAndTask( |
|
|
|
project, |
|
|
|
allTasksMap[taskId]!! |
|
|
|
) ?: ProjectTask() else ProjectTask() |
|
|
|
|
|
|
|
projectTask.apply { |
|
|
|
this.project = project |
|
|
@@ -205,7 +252,11 @@ open class ProjectsService( |
|
|
|
|
|
|
|
// Grade allocation (from manhourPercentageByGrade) |
|
|
|
val gradeAllocations = request.manhourPercentageByGrade.entries.map { |
|
|
|
val gradeAllocation = if (project.id != null && project.id!! > 0L) gradeAllocationRepository.findByProjectAndGrade(project, gradeMap[it.key]!!) ?: GradeAllocation() else GradeAllocation() |
|
|
|
val gradeAllocation = |
|
|
|
if (project.id != null && project.id!! > 0L) gradeAllocationRepository.findByProjectAndGrade( |
|
|
|
project, |
|
|
|
gradeMap[it.key]!! |
|
|
|
) ?: GradeAllocation() else GradeAllocation() |
|
|
|
|
|
|
|
gradeAllocation.apply { |
|
|
|
this.project = project |
|
|
@@ -225,7 +276,8 @@ open class ProjectsService( |
|
|
|
|
|
|
|
val milestonesToDelete = milestoneRepository.findAllByProject(project).subtract(milestones.toSet()) |
|
|
|
val tasksToDelete = projectTaskRepository.findAllByProject(project).subtract(tasksToSave.toSet()) |
|
|
|
val gradeAllocationsToDelete = gradeAllocationRepository.findByProject(project).subtract(gradeAllocations.toSet()) |
|
|
|
val gradeAllocationsToDelete = |
|
|
|
gradeAllocationRepository.findByProject(project).subtract(gradeAllocations.toSet()) |
|
|
|
milestoneRepository.deleteAll(milestonesToDelete) |
|
|
|
projectTaskRepository.deleteAll(tasksToDelete) |
|
|
|
gradeAllocationRepository.deleteAll(gradeAllocationsToDelete) |
|
|
@@ -236,11 +288,14 @@ open class ProjectsService( |
|
|
|
|
|
|
|
// Staff allocation |
|
|
|
val allocatedStaff = staffRepository.findAllById(request.allocatedStaffIds) |
|
|
|
val staffAllocations = allocatedStaff.map { staff -> StaffAllocation().apply { |
|
|
|
this.project = savedProject |
|
|
|
this.staff = staff |
|
|
|
} } |
|
|
|
val staffAllocationsToDelete = staffAllocationRepository.findByProject(savedProject).subtract(staffAllocations.toSet()) |
|
|
|
val staffAllocations = allocatedStaff.map { staff -> |
|
|
|
StaffAllocation().apply { |
|
|
|
this.project = savedProject |
|
|
|
this.staff = staff |
|
|
|
} |
|
|
|
} |
|
|
|
val staffAllocationsToDelete = |
|
|
|
staffAllocationRepository.findByProject(savedProject).subtract(staffAllocations.toSet()) |
|
|
|
staffAllocationRepository.deleteAll(staffAllocationsToDelete) |
|
|
|
staffAllocationRepository.saveAll(staffAllocations) |
|
|
|
|
|
|
@@ -260,7 +315,9 @@ open class ProjectsService( |
|
|
|
val project = projectRepository.findById(projectId) |
|
|
|
|
|
|
|
return project.getOrNull()?.let { |
|
|
|
val customerContact = it.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) } ?: emptyList() |
|
|
|
val customerContact = |
|
|
|
it.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) } |
|
|
|
?: emptyList() |
|
|
|
|
|
|
|
val milestoneMap = it.milestones |
|
|
|
.filter { milestone -> milestone.taskGroup?.id != null } |
|
|
@@ -276,11 +333,14 @@ open class ProjectsService( |
|
|
|
projectLeadId = it.teamLead?.id, |
|
|
|
projectActualStart = it.actualStart, |
|
|
|
projectActualEnd = it.actualEnd, |
|
|
|
projectStatus = it.status, |
|
|
|
isClpProject = it.isClpProject, |
|
|
|
serviceTypeId = it.serviceType?.id, |
|
|
|
fundingTypeId = it.fundingType?.id, |
|
|
|
contractTypeId = it.contractType?.id, |
|
|
|
locationId = it.location?.id, |
|
|
|
buildingTypeIds = it.buildingTypes.mapNotNull { buildingType -> buildingType.id }, |
|
|
|
taskTemplateId = it.taskTemplate?.id, |
|
|
|
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, |
|
|
@@ -292,21 +352,28 @@ open class ProjectsService( |
|
|
|
taskGroups = projectTaskRepository.findAllByProject(it) |
|
|
|
.mapNotNull { projectTask -> if (projectTask.task?.taskGroup?.id != null) projectTask.task else null } |
|
|
|
.groupBy { task -> task.taskGroup!!.id!! } |
|
|
|
.mapValues { (taskGroupId, tasks) -> TaskGroupAllocation( |
|
|
|
taskIds = tasks.mapNotNull { task -> task.id }, |
|
|
|
percentAllocation = milestoneMap[taskGroupId]?.stagePercentAllocation ?: 0.0 |
|
|
|
) }, |
|
|
|
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), |
|
|
|
endDate = milestone.endDate?.format(DateTimeFormatter.ISO_LOCAL_DATE), |
|
|
|
payments = milestone.milestonePayments.map { payment -> PaymentInputs( |
|
|
|
id = payment.id!!, |
|
|
|
amount = payment.amount!!, |
|
|
|
description = payment.description!!, |
|
|
|
date = payment.date!!.format(DateTimeFormatter.ISO_LOCAL_DATE) |
|
|
|
)} |
|
|
|
)}, |
|
|
|
.mapValues { (taskGroupId, tasks) -> |
|
|
|
TaskGroupAllocation( |
|
|
|
taskIds = tasks.mapNotNull { task -> task.id }, |
|
|
|
percentAllocation = milestoneMap[taskGroupId]?.stagePercentAllocation ?: 0.0 |
|
|
|
) |
|
|
|
}, |
|
|
|
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), |
|
|
|
endDate = milestone.endDate?.format(DateTimeFormatter.ISO_LOCAL_DATE), |
|
|
|
payments = milestone.milestonePayments.map { payment -> |
|
|
|
PaymentInputs( |
|
|
|
id = payment.id!!, |
|
|
|
amount = payment.amount!!, |
|
|
|
description = payment.description!!, |
|
|
|
date = payment.date!!.format(DateTimeFormatter.ISO_LOCAL_DATE) |
|
|
|
) |
|
|
|
} |
|
|
|
) |
|
|
|
}, |
|
|
|
expectedProjectFee = it.expectedTotalFee |
|
|
|
) |
|
|
|
} |
|
|
|