@@ -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 | |||||
} | } |
@@ -10,11 +10,14 @@ import com.ffii.tsms.modules.project.entity.projections.InvoiceSearchInfo | |||||
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, | ||||
@@ -24,7 +27,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() | ||||
@@ -61,15 +67,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 | ||||
@@ -85,8 +92,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; |