| @@ -0,0 +1,37 @@ | |||||
| package com.ffii.tsms.modules.project.entity | |||||
| import com.ffii.core.entity.IdEntity | |||||
| import com.ffii.tsms.modules.data.entity.Grade | |||||
| import jakarta.persistence.* | |||||
| import org.hibernate.proxy.HibernateProxy | |||||
| @Entity | |||||
| @Table(name = "grade_allocation") | |||||
| open class GradeAllocation : IdEntity<Long>() { | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "gradeId") | |||||
| open var grade: Grade? = null | |||||
| @Column(name = "manhour") | |||||
| open var manhour: Double? = null | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "projectId") | |||||
| open var project: Project? = null | |||||
| final override fun equals(other: Any?): Boolean { | |||||
| if (this === other) return true | |||||
| if (other == null) return false | |||||
| val oEffectiveClass = | |||||
| if (other is HibernateProxy) other.hibernateLazyInitializer.persistentClass else other.javaClass | |||||
| val thisEffectiveClass = | |||||
| if (this is HibernateProxy) this.hibernateLazyInitializer.persistentClass else this.javaClass | |||||
| if (thisEffectiveClass != oEffectiveClass) return false | |||||
| other as GradeAllocation | |||||
| return id != null && id == other.id | |||||
| } | |||||
| final override fun hashCode(): Int = | |||||
| if (this is HibernateProxy) this.hibernateLazyInitializer.persistentClass.hashCode() else javaClass.hashCode() | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| package com.ffii.tsms.modules.project.entity; | |||||
| import com.ffii.core.support.AbstractRepository | |||||
| interface GradeAllocationRepository : AbstractRepository<GradeAllocation, Long> { | |||||
| } | |||||
| @@ -1,23 +1,49 @@ | |||||
| package com.ffii.tsms.modules.project.entity | package com.ffii.tsms.modules.project.entity | ||||
| import com.ffii.core.entity.IdEntity | import com.ffii.core.entity.IdEntity | ||||
| import jakarta.persistence.Column | |||||
| import jakarta.persistence.Entity | |||||
| import jakarta.persistence.OneToMany | |||||
| import jakarta.persistence.Table | |||||
| import jakarta.persistence.* | |||||
| import jakarta.validation.constraints.NotNull | import jakarta.validation.constraints.NotNull | ||||
| import org.hibernate.proxy.HibernateProxy | |||||
| import java.time.LocalDate | |||||
| @Entity | @Entity | ||||
| @Table(name = "milestone") | @Table(name = "milestone") | ||||
| open class Milestone : IdEntity<Long>() { | open class Milestone : IdEntity<Long>() { | ||||
| @NotNull | |||||
| @Column(name = "name") | @Column(name = "name") | ||||
| open var name: String? = null | open var name: String? = null | ||||
| @NotNull | |||||
| @Column(name = "description") | @Column(name = "description") | ||||
| open var description: String? = null | open var description: String? = null | ||||
| @OneToMany(mappedBy = "milestone", orphanRemoval = true) | |||||
| @OneToMany(mappedBy = "milestone", cascade = [CascadeType.ALL], orphanRemoval = true) | |||||
| open var milestonePayments: MutableList<MilestonePayment> = mutableListOf() | open var milestonePayments: MutableList<MilestonePayment> = mutableListOf() | ||||
| @Column(name = "startDate") | |||||
| open var startDate: LocalDate? = null | |||||
| @Column(name = "endDate") | |||||
| open var endDate: LocalDate? = null | |||||
| @Column(name = "stagePercentAllocation") | |||||
| open var stagePercentAllocation: Double? = null | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "projectId") | |||||
| open var project: Project? = null | |||||
| final override fun equals(other: Any?): Boolean { | |||||
| if (this === other) return true | |||||
| if (other == null) return false | |||||
| val oEffectiveClass = | |||||
| if (other is HibernateProxy) other.hibernateLazyInitializer.persistentClass else other.javaClass | |||||
| val thisEffectiveClass = | |||||
| if (this is HibernateProxy) this.hibernateLazyInitializer.persistentClass else this.javaClass | |||||
| if (thisEffectiveClass != oEffectiveClass) return false | |||||
| other as Milestone | |||||
| return id != null && id == other.id | |||||
| } | |||||
| final override fun hashCode(): Int = | |||||
| if (this is HibernateProxy) this.hibernateLazyInitializer.persistentClass.hashCode() else javaClass.hashCode() | |||||
| } | } | ||||
| @@ -60,6 +60,15 @@ open class Project : BaseEntity<Long>() { | |||||
| @Column(name = "billStatus") | @Column(name = "billStatus") | ||||
| open var billStatus: Boolean? = null | open var billStatus: Boolean? = null | ||||
| @Column(name = "totalManhour") | |||||
| open var totalManhour: Double? = null | |||||
| @OneToMany(mappedBy = "project", cascade = [CascadeType.ALL], orphanRemoval = true) | |||||
| open var gradeAllocations: MutableSet<GradeAllocation> = mutableSetOf() | |||||
| @OneToMany(mappedBy = "project", cascade = [CascadeType.ALL], orphanRemoval = true) | |||||
| open var milestones: MutableSet<Milestone> = mutableSetOf() | |||||
| @Column(name = "expectedTotalFee") | @Column(name = "expectedTotalFee") | ||||
| open var expectedTotalFee: Double? = null | open var expectedTotalFee: Double? = null | ||||
| @@ -18,12 +18,4 @@ open class ProjectTask : IdEntity<Long>() { | |||||
| @NotNull | @NotNull | ||||
| @ManyToOne | @ManyToOne | ||||
| open var task: Task? = null | open var task: Task? = null | ||||
| @NotNull | |||||
| @Column(name = "paymentPercentage") | |||||
| open var paymentPercentage: Double? = null | |||||
| @NotNull | |||||
| @Column(name = "reminderPercentage") | |||||
| open var reminderPercentage: Double? = null | |||||
| } | } | ||||
| @@ -8,11 +8,14 @@ import com.ffii.tsms.modules.project.entity.* | |||||
| import com.ffii.tsms.modules.project.web.models.NewProjectRequest | import com.ffii.tsms.modules.project.web.models.NewProjectRequest | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import org.springframework.transaction.annotation.Transactional | import org.springframework.transaction.annotation.Transactional | ||||
| import java.time.LocalDate | |||||
| import java.time.format.DateTimeFormatter | |||||
| @Service | @Service | ||||
| open class ProjectsService( | open class ProjectsService( | ||||
| private val projectRepository: ProjectRepository, | private val projectRepository: ProjectRepository, | ||||
| private val customerService: CustomerService, | private val customerService: CustomerService, | ||||
| private val tasksService: TasksService, | |||||
| private val customerContactService: CustomerContactService, | private val customerContactService: CustomerContactService, | ||||
| private val projectCategoryRepository: ProjectCategoryRepository, | private val projectCategoryRepository: ProjectCategoryRepository, | ||||
| private val staffRepository: StaffRepository, | private val staffRepository: StaffRepository, | ||||
| @@ -22,7 +25,10 @@ open class ProjectsService( | |||||
| private val contractTypeRepository: ContractTypeRepository, | private val contractTypeRepository: ContractTypeRepository, | ||||
| private val locationRepository: LocationRepository, | private val locationRepository: LocationRepository, | ||||
| private val buildingTypeRepository: BuildingTypeRepository, | private val buildingTypeRepository: BuildingTypeRepository, | ||||
| private val workNatureRepository: WorkNatureRepository | |||||
| private val workNatureRepository: WorkNatureRepository, | |||||
| private val milestoneRepository: MilestoneRepository, | |||||
| private val gradeAllocationRepository: GradeAllocationRepository, | |||||
| private val projectTaskRepository: ProjectTaskRepository | |||||
| ) { | ) { | ||||
| open fun allProjects(): List<ProjectSearchInfo> { | open fun allProjects(): List<ProjectSearchInfo> { | ||||
| return projectRepository.findProjectSearchInfoBy() | return projectRepository.findProjectSearchInfoBy() | ||||
| @@ -47,15 +53,16 @@ open class ProjectsService( | |||||
| val customer = customerService.findCustomer(request.clientId) | val customer = customerService.findCustomer(request.clientId) | ||||
| val clientContact = customerContactService.findByContactId(request.clientContactId) | val clientContact = customerContactService.findByContactId(request.clientContactId) | ||||
| val allTasksMap = tasksService.allTasks().associateBy { it.id } | |||||
| // TODO: Add tasks, milestones | |||||
| val project = | val project = | ||||
| Project().apply { | Project().apply { | ||||
| name = request.projectName | name = request.projectName | ||||
| description = request.projectDescription | description = request.projectDescription | ||||
| code = request.projectCode | code = request.projectCode | ||||
| expectedTotalFee = request.expectedProjectFee | expectedTotalFee = request.expectedProjectFee | ||||
| totalManhour = request.totalManhour | |||||
| this.projectCategory = projectCategory | this.projectCategory = projectCategory | ||||
| this.fundingType = fundingType | this.fundingType = fundingType | ||||
| this.serviceType = serviceType | this.serviceType = serviceType | ||||
| @@ -71,8 +78,51 @@ open class ProjectsService( | |||||
| custLeadPhone = clientContact.phone | custLeadPhone = clientContact.phone | ||||
| } | } | ||||
| // Milestones and tasks | |||||
| val tasksToSave = mutableListOf<ProjectTask>() | |||||
| val milestones = request.milestones.entries.map { (taskStageId, requestMilestone) -> | |||||
| Milestone().apply { | |||||
| val newMilestone = this | |||||
| this.project = project | |||||
| this.startDate = LocalDate.parse(requestMilestone.startDate) | |||||
| this.endDate = LocalDate.parse(requestMilestone.endDate) | |||||
| this.milestonePayments = requestMilestone.payments.map { | |||||
| MilestonePayment().apply { | |||||
| this.description = it.description | |||||
| this.amount = it.amount | |||||
| this.date = LocalDate.parse(it.date) | |||||
| this.milestone = newMilestone | |||||
| } | |||||
| }.toMutableList() | |||||
| // Tasks | |||||
| val taskGroupAllocation = request.taskGroups[taskStageId] | |||||
| this.stagePercentAllocation = taskGroupAllocation?.percentAllocation | |||||
| taskGroupAllocation?.taskIds?.map { taskId -> ProjectTask().apply { | |||||
| this.project = project | |||||
| this.milestone = newMilestone | |||||
| this.task = allTasksMap[taskId] | |||||
| } }?.let { tasksToSave.addAll(it) } | |||||
| } | |||||
| } | |||||
| // TODO: Get Grades from db | |||||
| // Grade allocation (from manhourPercentageByGrade) | |||||
| // request.manhourPercentageByGrade.entries.map { | |||||
| // GradeAllocation().apply { | |||||
| // this.project = project | |||||
| // this.manhour = it.value | |||||
| // } | |||||
| // } | |||||
| val savedProject = projectRepository.save(project) | val savedProject = projectRepository.save(project) | ||||
| milestoneRepository.saveAll(milestones) | |||||
| projectTaskRepository.saveAll(tasksToSave) | |||||
| // Staff allocation | |||||
| val allocatedStaff = staffRepository.findAllById(request.allocatedStaffIds) | val allocatedStaff = staffRepository.findAllById(request.allocatedStaffIds) | ||||
| val staffAllocations = allocatedStaff.map { staff -> StaffAllocation().apply { | val staffAllocations = allocatedStaff.map { staff -> StaffAllocation().apply { | ||||
| this.project = savedProject | this.project = savedProject | ||||
| @@ -3,11 +3,11 @@ package com.ffii.tsms.modules.project.web.models | |||||
| import jakarta.validation.constraints.NotBlank | import jakarta.validation.constraints.NotBlank | ||||
| data class NewProjectRequest( | data class NewProjectRequest( | ||||
| // Project details | |||||
| @field:NotBlank(message = "project code cannot be empty") | @field:NotBlank(message = "project code cannot be empty") | ||||
| val projectCode: String, | val projectCode: String, | ||||
| @field:NotBlank(message = "project name cannot be empty") | @field:NotBlank(message = "project name cannot be empty") | ||||
| val projectName: String, | val projectName: String, | ||||
| val projectCategoryId: Long, | val projectCategoryId: Long, | ||||
| val projectDescription: String, | val projectDescription: String, | ||||
| val projectLeadId: Long, | val projectLeadId: Long, | ||||
| @@ -19,19 +19,27 @@ data class NewProjectRequest( | |||||
| val buildingTypeIds: List<Long>, | val buildingTypeIds: List<Long>, | ||||
| val workNatureIds: List<Long>, | val workNatureIds: List<Long>, | ||||
| // Client details | |||||
| val clientId: Long, | val clientId: Long, | ||||
| val clientContactId: Long, | val clientContactId: Long, | ||||
| val clientSubsidiaryId: Long?, | val clientSubsidiaryId: Long?, | ||||
| // Allocation | |||||
| val totalManhour: Double, | |||||
| val manhourPercentageByGrade: Map<Long, Double>, | |||||
| val taskGroups: Map<Long, TaskGroupAllocation>, | |||||
| val allocatedStaffIds: List<Long>, | val allocatedStaffIds: List<Long>, | ||||
| // Milestones | |||||
| val milestones: Map<Long, Milestone>, | val milestones: Map<Long, Milestone>, | ||||
| // Miscellaneous | |||||
| val expectedProjectFee: Double | val expectedProjectFee: Double | ||||
| ) | ) | ||||
| data class TaskAllocation( | |||||
| val manhourAllocation: Map<Long, Double> | |||||
| data class TaskGroupAllocation( | |||||
| val taskIds: List<Long>, | |||||
| val percentAllocation: Double | |||||
| ) | ) | ||||
| data class Milestone( | data class Milestone( | ||||
| @@ -0,0 +1,4 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset wayne:update_project_manhour | |||||
| ALTER TABLE project ADD totalManhour DOUBLE NULL; | |||||
| @@ -0,0 +1,4 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset wayne:update_milestone | |||||
| ALTER TABLE milestone ADD startDate date NULL, ADD endDate date NULL, ADD stagePercentAllocation DOUBLE NULL, ADD projectId INT NULL; | |||||
| @@ -0,0 +1,14 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset wayne:grade_allocation | |||||
| CREATE TABLE grade_allocation ( | |||||
| id INT NOT NULL AUTO_INCREMENT, | |||||
| gradeId INT NULL, | |||||
| manhour DOUBLE NULL, | |||||
| projectId INT NULL, | |||||
| CONSTRAINT pk_grade_allocation PRIMARY KEY (id) | |||||
| ); | |||||
| ALTER TABLE grade_allocation ADD CONSTRAINT FK_GRADE_ALLOCATION_ON_GRADEID FOREIGN KEY (gradeId) REFERENCES grade (id); | |||||
| ALTER TABLE grade_allocation ADD CONSTRAINT FK_GRADE_ALLOCATION_ON_PROJECTID FOREIGN KEY (projectId) REFERENCES project (id); | |||||
| @@ -0,0 +1,4 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset wayne:remove_milestone_non_null | |||||
| ALTER TABLE milestone MODIFY name VARCHAR(255) NULL, MODIFY `description` VARCHAR(255) NULL; | |||||
| @@ -0,0 +1,4 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset wayne:remove_percentage_project_task | |||||
| ALTER TABLE project_task DROP COLUMN paymentPercentage, DROP COLUMN reminderPercentage; | |||||