diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/GradeAllocation.kt b/src/main/java/com/ffii/tsms/modules/project/entity/GradeAllocation.kt new file mode 100644 index 0000000..6c0d8f7 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/entity/GradeAllocation.kt @@ -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() { + @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() +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/GradeAllocationRepository.kt b/src/main/java/com/ffii/tsms/modules/project/entity/GradeAllocationRepository.kt new file mode 100644 index 0000000..5e812cb --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/entity/GradeAllocationRepository.kt @@ -0,0 +1,6 @@ +package com.ffii.tsms.modules.project.entity; + +import com.ffii.core.support.AbstractRepository + +interface GradeAllocationRepository : AbstractRepository { +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/Milestone.kt b/src/main/java/com/ffii/tsms/modules/project/entity/Milestone.kt index 3e68bca..40b4239 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/Milestone.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/Milestone.kt @@ -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() { - @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 = 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() } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/Project.kt b/src/main/java/com/ffii/tsms/modules/project/entity/Project.kt index 0e9da19..551fe58 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/Project.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/Project.kt @@ -60,6 +60,15 @@ open class Project : BaseEntity() { @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 = mutableSetOf() + + @OneToMany(mappedBy = "project", cascade = [CascadeType.ALL], orphanRemoval = true) + open var milestones: MutableSet = mutableSetOf() + @Column(name = "expectedTotalFee") open var expectedTotalFee: Double? = null diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectTask.kt b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectTask.kt index ac924ee..820df0c 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectTask.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectTask.kt @@ -18,12 +18,4 @@ open class ProjectTask : IdEntity() { @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 } \ 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 e2b4ec0..5dcdad3 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 @@ -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 { 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() + 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 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 2b61b59..cd34e4c 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 @@ -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, val workNatureIds: List, + // Client details val clientId: Long, val clientContactId: Long, val clientSubsidiaryId: Long?, + // Allocation + val totalManhour: Double, + val manhourPercentageByGrade: Map, + val taskGroups: Map, val allocatedStaffIds: List, + // Milestones val milestones: Map, + // Miscellaneous val expectedProjectFee: Double ) -data class TaskAllocation( - val manhourAllocation: Map +data class TaskGroupAllocation( + val taskIds: List, + val percentAllocation: Double ) data class Milestone( diff --git a/src/main/resources/db/changelog/changes/20240423_01_wayne/01_update_project_manhour.sql b/src/main/resources/db/changelog/changes/20240423_01_wayne/01_update_project_manhour.sql new file mode 100644 index 0000000..0bb2581 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240423_01_wayne/01_update_project_manhour.sql @@ -0,0 +1,4 @@ +-- liquibase formatted sql +-- changeset wayne:update_project_manhour + +ALTER TABLE project ADD totalManhour DOUBLE NULL; \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20240423_01_wayne/02_update_milestone.sql b/src/main/resources/db/changelog/changes/20240423_01_wayne/02_update_milestone.sql new file mode 100644 index 0000000..bbc96e2 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240423_01_wayne/02_update_milestone.sql @@ -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; \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20240423_01_wayne/03_grade_allocation.sql b/src/main/resources/db/changelog/changes/20240423_01_wayne/03_grade_allocation.sql new file mode 100644 index 0000000..9154796 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240423_01_wayne/03_grade_allocation.sql @@ -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); diff --git a/src/main/resources/db/changelog/changes/20240423_01_wayne/04_remove_milestone_non_null.sql b/src/main/resources/db/changelog/changes/20240423_01_wayne/04_remove_milestone_non_null.sql new file mode 100644 index 0000000..f55fbda --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240423_01_wayne/04_remove_milestone_non_null.sql @@ -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; diff --git a/src/main/resources/db/changelog/changes/20240423_01_wayne/05_remove_percentage_project_task.sql b/src/main/resources/db/changelog/changes/20240423_01_wayne/05_remove_percentage_project_task.sql new file mode 100644 index 0000000..18c72d9 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240423_01_wayne/05_remove_percentage_project_task.sql @@ -0,0 +1,4 @@ +-- liquibase formatted sql +-- changeset wayne:remove_percentage_project_task + +ALTER TABLE project_task DROP COLUMN paymentPercentage, DROP COLUMN reminderPercentage;