| @@ -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 | |||
| 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 org.hibernate.proxy.HibernateProxy | |||
| import java.time.LocalDate | |||
| @Entity | |||
| @Table(name = "milestone") | |||
| open class Milestone : IdEntity<Long>() { | |||
| @NotNull | |||
| @Column(name = "name") | |||
| open var name: String? = null | |||
| @NotNull | |||
| @Column(name = "description") | |||
| open var description: String? = null | |||
| @OneToMany(mappedBy = "milestone", orphanRemoval = true) | |||
| @OneToMany(mappedBy = "milestone", cascade = [CascadeType.ALL], orphanRemoval = true) | |||
| 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") | |||
| 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") | |||
| open var expectedTotalFee: Double? = null | |||
| @@ -18,12 +18,4 @@ open class ProjectTask : IdEntity<Long>() { | |||
| @NotNull | |||
| @ManyToOne | |||
| 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 org.springframework.stereotype.Service | |||
| import org.springframework.transaction.annotation.Transactional | |||
| import java.time.LocalDate | |||
| import java.time.format.DateTimeFormatter | |||
| @Service | |||
| open class ProjectsService( | |||
| private val projectRepository: ProjectRepository, | |||
| private val customerService: CustomerService, | |||
| private val tasksService: TasksService, | |||
| private val customerContactService: CustomerContactService, | |||
| private val projectCategoryRepository: ProjectCategoryRepository, | |||
| private val staffRepository: StaffRepository, | |||
| @@ -22,7 +25,10 @@ open class ProjectsService( | |||
| private val contractTypeRepository: ContractTypeRepository, | |||
| private val locationRepository: LocationRepository, | |||
| 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> { | |||
| return projectRepository.findProjectSearchInfoBy() | |||
| @@ -47,15 +53,16 @@ open class ProjectsService( | |||
| val customer = customerService.findCustomer(request.clientId) | |||
| val clientContact = customerContactService.findByContactId(request.clientContactId) | |||
| val allTasksMap = tasksService.allTasks().associateBy { it.id } | |||
| // TODO: Add tasks, milestones | |||
| val project = | |||
| Project().apply { | |||
| name = request.projectName | |||
| description = request.projectDescription | |||
| code = request.projectCode | |||
| expectedTotalFee = request.expectedProjectFee | |||
| totalManhour = request.totalManhour | |||
| this.projectCategory = projectCategory | |||
| this.fundingType = fundingType | |||
| this.serviceType = serviceType | |||
| @@ -71,8 +78,51 @@ open class ProjectsService( | |||
| 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) | |||
| milestoneRepository.saveAll(milestones) | |||
| projectTaskRepository.saveAll(tasksToSave) | |||
| // Staff allocation | |||
| val allocatedStaff = staffRepository.findAllById(request.allocatedStaffIds) | |||
| val staffAllocations = allocatedStaff.map { staff -> StaffAllocation().apply { | |||
| this.project = savedProject | |||
| @@ -3,11 +3,11 @@ package com.ffii.tsms.modules.project.web.models | |||
| import jakarta.validation.constraints.NotBlank | |||
| data class NewProjectRequest( | |||
| // Project details | |||
| @field:NotBlank(message = "project code cannot be empty") | |||
| val projectCode: String, | |||
| @field:NotBlank(message = "project name cannot be empty") | |||
| val projectName: String, | |||
| val projectCategoryId: Long, | |||
| val projectDescription: String, | |||
| val projectLeadId: Long, | |||
| @@ -19,19 +19,27 @@ data class NewProjectRequest( | |||
| val buildingTypeIds: List<Long>, | |||
| val workNatureIds: List<Long>, | |||
| // Client details | |||
| val clientId: Long, | |||
| val clientContactId: Long, | |||
| val clientSubsidiaryId: Long?, | |||
| // Allocation | |||
| val totalManhour: Double, | |||
| val manhourPercentageByGrade: Map<Long, Double>, | |||
| val taskGroups: Map<Long, TaskGroupAllocation>, | |||
| val allocatedStaffIds: List<Long>, | |||
| // Milestones | |||
| val milestones: Map<Long, Milestone>, | |||
| // Miscellaneous | |||
| val expectedProjectFee: Double | |||
| ) | |||
| data class TaskAllocation( | |||
| val manhourAllocation: Map<Long, Double> | |||
| data class TaskGroupAllocation( | |||
| val taskIds: List<Long>, | |||
| val percentAllocation: Double | |||
| ) | |||
| 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; | |||