@@ -8,7 +8,7 @@ import java.util.List; | |||
import java.util.Optional; | |||
public interface CompanyRepository extends AbstractRepository<Company, Long> { | |||
List<CompanySearchInfo> findCompanySearchInfoBy(); | |||
List<CompanySearchInfo> findCompanySearchInfoByAndDeletedFalse(); | |||
Optional<Company> findCompanyByIdAndDeletedFalse(Long companyId); | |||
} |
@@ -1,10 +1,14 @@ | |||
package com.ffii.tsms.modules.data.entity; | |||
import com.fasterxml.jackson.annotation.JsonManagedReference; | |||
import com.ffii.core.entity.BaseEntity; | |||
import jakarta.persistence.*; | |||
import jakarta.validation.constraints.NotNull; | |||
import java.time.LocalTime; | |||
import java.util.List; | |||
import static jakarta.persistence.CascadeType.ALL; | |||
@Entity | |||
@Table(name = "subsidiary") | |||
@@ -31,6 +35,10 @@ public class Subsidiary extends BaseEntity<Long> { | |||
@JoinColumn(name = "typeId") | |||
private SubsidiaryType subsidiaryType; | |||
@OneToMany(mappedBy = "subsidiary", cascade = ALL, orphanRemoval = true) | |||
@JsonManagedReference | |||
private List<SubsidiaryContact> subsidiaryContacts; | |||
public String getAddress() { | |||
return address; | |||
} | |||
@@ -78,4 +86,12 @@ public class Subsidiary extends BaseEntity<Long> { | |||
public void setSubsidiaryType(SubsidiaryType subsidiaryType) { | |||
this.subsidiaryType = subsidiaryType; | |||
} | |||
public List<SubsidiaryContact> getSubsidiaryContacts() { | |||
return subsidiaryContacts; | |||
} | |||
public void setSubsidiaryContacts(List<SubsidiaryContact> subsidiaryContacts) { | |||
this.subsidiaryContacts = subsidiaryContacts; | |||
} | |||
} |
@@ -1,18 +1,17 @@ | |||
package com.ffii.tsms.modules.data.entity; | |||
import com.fasterxml.jackson.annotation.JsonBackReference; | |||
import com.fasterxml.jackson.annotation.JsonManagedReference; | |||
import com.ffii.core.entity.IdEntity; | |||
import jakarta.persistence.Column; | |||
import jakarta.persistence.Entity; | |||
import jakarta.persistence.JoinColumn; | |||
import jakarta.persistence.OneToOne; | |||
import jakarta.persistence.Table; | |||
import jakarta.persistence.*; | |||
import jakarta.validation.constraints.NotNull; | |||
@Entity | |||
@Table(name = "subsidiary_contact") | |||
public class SubsidiaryContact extends IdEntity<Long> { | |||
@NotNull | |||
@OneToOne | |||
@ManyToOne | |||
@JsonBackReference | |||
@JoinColumn(name = "subsidiaryId") | |||
private Subsidiary subsidiary; | |||
@@ -9,5 +9,7 @@ import java.util.Optional; | |||
public interface SubsidiaryRepository extends AbstractRepository<Subsidiary, Long> { | |||
List<Subsidiary> findAllByDeletedFalse(); | |||
List<Subsidiary> findAllByDeletedFalseAndIdIn(List<Long> id); | |||
Optional<Subsidiary> findByCode(@Param("code") String code); | |||
} |
@@ -22,4 +22,7 @@ interface StaffSearchInfo { | |||
@get:Value("#{target.currentPosition?.name}") | |||
val currentPosition: String? | |||
@get:Value("#{target.user?.id}") | |||
val userId: Long? | |||
} |
@@ -18,7 +18,7 @@ open class CompanyService( | |||
private val jdbcDao: JdbcDao, | |||
) : AbstractBaseEntityService<Company, Long, CompanyRepository>(jdbcDao, companyRepository) { | |||
open fun allCompanys(): List<CompanySearchInfo>{ | |||
return companyRepository.findCompanySearchInfoBy() | |||
return companyRepository.findCompanySearchInfoByAndDeletedFalse() | |||
} | |||
open fun getCompanyDetails(companyId: Long): Company? { | |||
@@ -1,5 +1,6 @@ | |||
package com.ffii.tsms.modules.data.service | |||
import com.ffii.core.exception.UnprocessableEntityException | |||
import com.ffii.core.support.AbstractBaseEntityService | |||
import com.ffii.core.support.JdbcDao | |||
import com.ffii.tsms.modules.common.SecurityUtils | |||
@@ -8,12 +9,10 @@ import com.ffii.tsms.modules.data.entity.projections.StaffSearchInfo | |||
import com.ffii.tsms.modules.data.web.models.NewStaffRequest | |||
import com.ffii.tsms.modules.user.entity.User | |||
import com.ffii.tsms.modules.user.entity.UserRepository | |||
import org.springframework.data.jpa.domain.AbstractPersistable_.id | |||
import org.springframework.security.crypto.password.PasswordEncoder | |||
import org.springframework.stereotype.Service | |||
import org.springframework.transaction.annotation.Transactional | |||
import java.util.* | |||
import java.util.stream.Collectors | |||
import kotlin.jvm.optionals.getOrNull | |||
@@ -96,6 +95,13 @@ open class StaffsService( | |||
@Transactional(rollbackFor = [Exception::class]) | |||
open fun saveStaff(req: NewStaffRequest): Staff { | |||
// if (req.staffId) | |||
val checkStaffIdList: List<StaffSearchInfo> = staffRepository.findStaffSearchInfoByAndDeletedFalse() | |||
checkStaffIdList.forEach{ s -> | |||
if (s.staffId == req.staffId) { | |||
throw UnprocessableEntityException("Duplicated StaffId Found") | |||
} | |||
} | |||
val currentPosition = positionRepository.findById(req.currentPositionId).orElseThrow() | |||
val joinPosition = positionRepository.findById(req.joinPositionId).orElseThrow() | |||
val company = companyRepository.findById(req.companyId).orElseThrow() | |||
@@ -39,7 +39,7 @@ open class TeamService( | |||
@Transactional(rollbackFor = [Exception::class]) | |||
open fun saveTeam(req: NewTeamRequest): Team { | |||
val ids = req.addStaffIds!! | |||
println(ids) | |||
// println(ids) | |||
val teamLead = staffRepository.findById(ids[0]).orElseThrow() | |||
val teamName = "Team " + teamLead.name | |||
@@ -7,6 +7,7 @@ import com.ffii.tsms.modules.data.service.SkillService | |||
import com.ffii.tsms.modules.data.web.models.NewSkillRequest | |||
import jakarta.servlet.http.HttpServletRequest | |||
import jakarta.validation.Valid | |||
import org.springframework.http.HttpStatus | |||
import org.springframework.web.bind.ServletRequestBindingException | |||
import org.springframework.web.bind.annotation.* | |||
@@ -25,6 +26,11 @@ class SkillController(private val skillService: SkillService) { | |||
args["id"] = id | |||
return skillService.list(args); | |||
} | |||
@DeleteMapping("/delete/{id}") | |||
@ResponseStatus(HttpStatus.NO_CONTENT) | |||
fun delete(@PathVariable id: Long?) { | |||
skillService.markDelete(id) | |||
} | |||
@GetMapping | |||
fun list(): List<Map<String, Any>> { | |||
@@ -5,10 +5,6 @@ import jakarta.validation.constraints.NotNull | |||
import java.time.LocalDate | |||
data class NewStaffRequest( | |||
val id: Long?, | |||
// @field:NotNull(message = "Staff userId cannot be empty") | |||
// val userId: Long, | |||
@field:NotBlank(message = "Staff name cannot be empty") | |||
val name: String, | |||
@field:NotBlank(message = "Staff staffId cannot be empty") | |||
@@ -17,21 +13,30 @@ data class NewStaffRequest( | |||
val companyId: Long, | |||
@field:NotNull(message = "Staff salaryId cannot be empty") | |||
val salaryId: Long, | |||
// @field:NotNull(message = "Staff skillSetId cannot be empty") | |||
val skillSetId: List<Long>?, | |||
@field:NotNull(message = "joinDate cannot be empty") | |||
val joinDate: LocalDate, | |||
@field:NotNull(message = "Staff currentPositionId cannot be empty") | |||
val currentPositionId: Long, | |||
// val salaryEffId: Long, | |||
@field:NotNull(message = "Staff joinPositionId cannot be empty") | |||
val joinPositionId: Long, | |||
val gradeId: Long?, | |||
val teamId: Long?, | |||
@field:NotNull(message = "Staff departmentId cannot be empty") | |||
val departmentId: Long, | |||
@field:NotBlank(message = "Staff phone1 cannot be empty") | |||
val phone1: String, | |||
val phone2: String?, | |||
@field:NotBlank(message = "Staff email cannot be empty") | |||
val email: String, | |||
@field:NotBlank(message = "Staff emergContactName cannot be empty") | |||
val emergContactName: String, | |||
@field:NotBlank(message = "Staff emergContactPhone cannot be empty") | |||
val emergContactPhone: String, | |||
@field:NotBlank(message = "Staff employType cannot be empty") | |||
val employType: String, | |||
val id: Long?, | |||
val skillSetId: List<Long>?, | |||
val gradeId: Long?, | |||
val phone2: String?, | |||
val teamId: Long?, | |||
val departDate: LocalDate?, | |||
val departReason: String?, | |||
val remark: String?, |
@@ -108,4 +108,14 @@ open class Project : BaseEntity<Long>() { | |||
inverseJoinColumns = [JoinColumn(name = "workNaturesId")] | |||
) | |||
open var workNatures: MutableSet<WorkNature> = mutableSetOf() | |||
@ManyToOne | |||
@JoinColumn(name = "taskTemplateId") | |||
open var taskTemplate: TaskTemplate? = null | |||
@Column(name = "status") | |||
open var status: String? = null | |||
@Column(name = "isClpProject") | |||
open var isClpProject: Boolean? = null | |||
} |
@@ -4,6 +4,7 @@ import com.ffii.core.support.AbstractRepository | |||
import com.ffii.tsms.modules.project.entity.projections.InvoiceInfoSearchInfo | |||
import com.ffii.tsms.modules.project.entity.projections.InvoiceSearchInfo | |||
import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo | |||
import java.io.Serializable | |||
interface ProjectRepository : AbstractRepository<Project, Long> { | |||
fun findProjectSearchInfoByOrderByCreatedDesc(): List<ProjectSearchInfo> | |||
@@ -13,4 +14,6 @@ interface ProjectRepository : AbstractRepository<Project, Long> { | |||
fun findInvoiceSearchInfoById(id: Long): List<InvoiceSearchInfo> | |||
fun findInvoiceInfoSearchInfoById(id: Long): List<InvoiceInfoSearchInfo> | |||
fun findFirstByIsClpProjectAndIdNotOrderByIdDesc(isClpProject: Boolean, id: Serializable?): Project? | |||
} |
@@ -9,6 +9,7 @@ interface ProjectSearchInfo { | |||
val id: Long? | |||
val name: String? | |||
val code: String? | |||
val status: String? | |||
@get:Value("#{target.projectCategory.name}") | |||
val category: String? | |||
@@ -96,7 +96,7 @@ open class InvoiceService( | |||
open fun getMilestonePaymentWithProjectCode(code: List<String>): List<Map<String, Any>> { | |||
val sql = StringBuilder("select " | |||
+ " p.code, mp.* " | |||
+ " p.code, mp.*, m.taskGroupId " | |||
+ " from milestone_payment mp " | |||
+ " left join milestone m on mp.milestoneId = m.id " | |||
+ " left join project p on p.id = m.projectId " | |||
@@ -108,6 +108,20 @@ open class InvoiceService( | |||
return jdbcDao.queryForList(sql.toString(), args) | |||
} | |||
open fun getMilestonePaymentId(code: String, paymentMilestone: String): Long{ | |||
val sql = StringBuilder(" select" | |||
+ " mp.id as milestonePaymentId" | |||
+ " from milestone_payment mp" | |||
+ " left join milestone m on mp.milestoneId = m.id" | |||
+ " left join project p on p.id = m.projectId" | |||
+ " where p.deleted = false" | |||
+ " and p.code = :code" | |||
+ " and mp.description = :description " | |||
) | |||
val args = mapOf("code" to code, "description" to paymentMilestone) | |||
return jdbcDao.queryForInt(sql.toString(), args).toLong() | |||
} | |||
open fun getInvoiceByInvoiceNo(invoiceNo: String): Invoice { | |||
return invoiceRepository.findByInvoiceNo(invoiceNo) | |||
} | |||
@@ -227,17 +241,18 @@ open class InvoiceService( | |||
/** | |||
* @return true when cellValue Object exist in DB | |||
*/ | |||
fun checkStringExists(list: List<Map<String, Any>>, cellValue: Map<String, Any>): Boolean { | |||
private fun checkStringExists(list: List<Map<String, Any>>, cellValue: Map<String, Any>): Boolean { | |||
// println("LIST-------------: $list") | |||
// println("CELL VALUE-------------: $cellValue") | |||
// println(list.contains(cellValue)) | |||
// println(list.any { it["code"] == cellValue["code"] && it["description"] == cellValue["description"] }) | |||
// return list.contains(cellValue) | |||
return list.any { it["code"] == cellValue["code"] && it["description"] == cellValue["description"] } | |||
// println(list.any { it["code"] == cellValue["code"] && it["description"] == cellValue["description"] && it["groupTaskId"] == cellValue["stage"]}) | |||
return list.any { it["code"] == cellValue["code"] && it["description"] == cellValue["description"] && it["groupTaskId"] == cellValue["stage"]} | |||
} | |||
open fun checkMilestonePayment( | |||
open fun checkMilestonePaymentByStageAndDescription( | |||
sheet: Sheet, | |||
startingRow: Int, | |||
stageColumnIndex: Int, | |||
columnIndex: Int, | |||
invoiceColumnIndex: Int, | |||
projectCodeColumnIndex: Int, | |||
@@ -250,15 +265,18 @@ open class InvoiceService( | |||
val milestonePaymentCell = row?.getCell(columnIndex) | |||
val invoiceNoCell = row?.getCell(invoiceColumnIndex) | |||
val projectCodeCell = row?.getCell(projectCodeColumnIndex) | |||
val stageCell = row?.getCell(stageColumnIndex) | |||
if (milestonePaymentCell != null && milestonePaymentCell.cellType == CellType.STRING && | |||
invoiceNoCell != null && invoiceNoCell.cellType == CellType.STRING && | |||
projectCodeCell != null && projectCodeCell.cellType == CellType.STRING) | |||
projectCodeCell != null && projectCodeCell.cellType == CellType.STRING && | |||
stageCell != null && stageCell.cellType == CellType.NUMERIC) | |||
{ | |||
val milestonePaymentCellValue = milestonePaymentCell.stringCellValue | |||
val invoiceNoCellValue = invoiceNoCell.stringCellValue | |||
val projectCodeCellValue = projectCodeCell.stringCellValue | |||
val stageCellValue = stageCell.numericCellValue.toInt() | |||
val cellValue = mapOf("code" to projectCodeCellValue, "description" to milestonePaymentCellValue) | |||
val cellValue = mapOf("code" to projectCodeCellValue, "description" to milestonePaymentCellValue, "groupTaskId" to stageCellValue) | |||
if(!checkStringExists(paymentMilestoneWithCode, cellValue)) { | |||
if(!nonExistMilestone.contains(mapOf("paymentMilestone" to milestonePaymentCellValue, "invoiceNo" to invoiceNoCellValue))){ | |||
@@ -359,7 +377,7 @@ open class InvoiceService( | |||
val milestonepaymentWithCode = getMilestonePaymentWithProjectCode(projectsCodes) | |||
// println("newProjectCodes == 0") | |||
// println(checkMilestonePayment(sheet, 2, 5, 0, 1, milestonepaymentWithCode)) | |||
val paymenMilestones = checkMilestonePayment(sheet, 2, 5, 0, 1, milestonepaymentWithCode) | |||
val paymenMilestones = checkMilestonePaymentByStageAndDescription(sheet, 2, 4,5, 0, 1, milestonepaymentWithCode) | |||
if (paymenMilestones.isNotEmpty()){ | |||
return InvoiceResponse(false, "Imported Invoice's format is incorrect", newProjectCodes, emptyRowList, invoicesResult, duplicateItemsInInvoice, paymenMilestones) | |||
} | |||
@@ -376,6 +394,9 @@ open class InvoiceService( | |||
} | |||
for (i in 2..sheet.lastRowNum){ | |||
val paymentMilestoneId = getMilestonePaymentId(ExcelUtils.getCell(sheet, i, 1).stringCellValue, ExcelUtils.getCell(sheet, i, 5).stringCellValue) | |||
println("paymentMilestoneId--------------: $paymentMilestoneId") | |||
val milestonePayment = milestonePaymentRepository.findById(paymentMilestoneId).orElseThrow() | |||
val invoice = Invoice().apply { | |||
invoiceNo = ExcelUtils.getCell(sheet, i, 0).stringCellValue | |||
projectCode = ExcelUtils.getCell(sheet, i, 1).stringCellValue | |||
@@ -390,6 +411,7 @@ open class InvoiceService( | |||
invoiceDate = ExcelUtils.getCell(sheet, i, 10).dateCellValue.toInstant().atZone(ZoneId.systemDefault()).toLocalDate() | |||
dueDate = ExcelUtils.getCell(sheet, i, 11).dateCellValue.toInstant().atZone(ZoneId.systemDefault()).toLocalDate() | |||
issueAmount = ExcelUtils.getCell(sheet, i, 12).numericCellValue.toBigDecimal() | |||
this.milestonePayment = milestonePayment | |||
} | |||
saveAndFlush(invoice) | |||
} | |||
@@ -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 | |||
) | |||
} | |||
@@ -13,6 +13,8 @@ data class EditProjectDetails( | |||
val projectLeadId: Long?, | |||
val projectActualStart: LocalDate?, | |||
val projectActualEnd: LocalDate?, | |||
val projectStatus: String?, | |||
val isClpProject: Boolean?, | |||
val serviceTypeId: Long?, | |||
val fundingTypeId: Long?, | |||
@@ -20,6 +22,7 @@ data class EditProjectDetails( | |||
val locationId: Long?, | |||
val buildingTypeIds: List<Long>, | |||
val workNatureIds: List<Long>, | |||
val taskTemplateId: Long?, | |||
// Client details | |||
val clientId: Long?, | |||
@@ -1,12 +1,13 @@ | |||
package com.ffii.tsms.modules.project.web.models | |||
import com.ffii.tsms.modules.data.entity.SubsidiaryContact | |||
import jakarta.validation.constraints.NotBlank | |||
import java.time.LocalDate | |||
data class NewProjectRequest( | |||
// Project details | |||
@field:NotBlank(message = "project code cannot be empty") | |||
val projectCode: String, | |||
// @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, | |||
@@ -15,6 +16,7 @@ data class NewProjectRequest( | |||
val projectId: Long?, | |||
val projectActualStart: LocalDate?, | |||
val projectActualEnd: LocalDate?, | |||
val isClpProject: Boolean?, | |||
val serviceTypeId: Long, | |||
val fundingTypeId: Long, | |||
@@ -22,6 +24,8 @@ data class NewProjectRequest( | |||
val locationId: Long, | |||
val buildingTypeIds: List<Long>, | |||
val workNatureIds: List<Long>, | |||
val taskTemplateId: Long?, | |||
val isSubsidiaryContact: Boolean?, | |||
// Client details | |||
val clientId: Long, | |||
@@ -0,0 +1,13 @@ | |||
-- liquibase formatted sql | |||
-- changeset cyril:project | |||
ALTER TABLE `project` | |||
ADD COLUMN `taskTemplateId` INT NULL DEFAULT NULL AFTER `customerSubsidiaryId`, | |||
ADD INDEX `FK_PROJECT_ON_TASKTEMPLATEID` (`taskTemplateId` ASC) VISIBLE; | |||
; | |||
ALTER TABLE `project` | |||
ADD CONSTRAINT `FK_PROJECT_ON_TASKTEMPLATEID` | |||
FOREIGN KEY (`taskTemplateId`) | |||
REFERENCES `task_template` (`id`) | |||
ON DELETE NO ACTION | |||
ON UPDATE NO ACTION; |
@@ -0,0 +1,5 @@ | |||
-- liquibase formatted sql | |||
-- changeset cyril:project | |||
ALTER TABLE `project` | |||
ADD COLUMN `status` VARCHAR(40) NULL DEFAULT 'Pending to Start' AFTER `taskTemplateId`; |
@@ -0,0 +1,5 @@ | |||
-- liquibase formatted sql | |||
-- changeset cyril:project | |||
ALTER TABLE `project` | |||
ADD COLUMN `isClpProject` TINYINT NULL DEFAULT 0 AFTER `status`; |