From 91827e0597b93251c238036ae468eacb12cb7f1a Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Tue, 14 May 2024 16:28:31 +0800 Subject: [PATCH 1/4] save milestonePayment in invoice when the invioce is correct --- .../com/ffii/tsms/modules/data/entity/CompanyRepository.java | 2 +- .../java/com/ffii/tsms/modules/data/service/CompanyService.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ffii/tsms/modules/data/entity/CompanyRepository.java b/src/main/java/com/ffii/tsms/modules/data/entity/CompanyRepository.java index b0c2820..bb066fe 100644 --- a/src/main/java/com/ffii/tsms/modules/data/entity/CompanyRepository.java +++ b/src/main/java/com/ffii/tsms/modules/data/entity/CompanyRepository.java @@ -8,7 +8,7 @@ import java.util.List; import java.util.Optional; public interface CompanyRepository extends AbstractRepository { - List findCompanySearchInfoBy(); + List findCompanySearchInfoByAndDeletedFalse(); Optional findCompanyByIdAndDeletedFalse(Long companyId); } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/data/service/CompanyService.kt b/src/main/java/com/ffii/tsms/modules/data/service/CompanyService.kt index 0ac904c..6e2fbf9 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/CompanyService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/CompanyService.kt @@ -18,7 +18,7 @@ open class CompanyService( private val jdbcDao: JdbcDao, ) : AbstractBaseEntityService(jdbcDao, companyRepository) { open fun allCompanys(): List{ - return companyRepository.findCompanySearchInfoBy() + return companyRepository.findCompanySearchInfoByAndDeletedFalse() } open fun getCompanyDetails(companyId: Long): Company? { From 6d45f6d806bc8eee15efb95870b90856ae815213 Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Tue, 14 May 2024 16:29:08 +0800 Subject: [PATCH 2/4] save milestonepayment to invoice --- .../modules/project/service/InvoiceService.kt | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt b/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt index f69a6e5..b95d882 100644 --- a/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt +++ b/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt @@ -96,7 +96,7 @@ open class InvoiceService( open fun getMilestonePaymentWithProjectCode(code: List): List> { 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>, cellValue: Map): Boolean { + private fun checkStringExists(list: List>, cellValue: Map): 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) } From 0227099cc45f4e210c298ca4af25d78aff13eb5f Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Tue, 14 May 2024 18:09:38 +0800 Subject: [PATCH 3/4] update --- .../entity/projections/StaffSearchInfo.kt | 3 +++ .../modules/data/service/StaffsService.kt | 10 ++++++-- .../tsms/modules/data/service/TeamService.kt | 2 +- .../tsms/modules/data/web/SkillController.kt | 6 +++++ .../data/web/models/NewStaffRequest.kt | 25 +++++++++++-------- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/ffii/tsms/modules/data/entity/projections/StaffSearchInfo.kt b/src/main/java/com/ffii/tsms/modules/data/entity/projections/StaffSearchInfo.kt index 8deb4d7..8b9b841 100644 --- a/src/main/java/com/ffii/tsms/modules/data/entity/projections/StaffSearchInfo.kt +++ b/src/main/java/com/ffii/tsms/modules/data/entity/projections/StaffSearchInfo.kt @@ -22,4 +22,7 @@ interface StaffSearchInfo { @get:Value("#{target.currentPosition?.name}") val currentPosition: String? + @get:Value("#{target.user?.id}") + val userId: Long? + } diff --git a/src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt b/src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt index 00b0dd0..9fd341f 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt @@ -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 = 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() diff --git a/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt b/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt index 68678e6..580c3aa 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt @@ -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 diff --git a/src/main/java/com/ffii/tsms/modules/data/web/SkillController.kt b/src/main/java/com/ffii/tsms/modules/data/web/SkillController.kt index d766752..a6d9bd0 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/SkillController.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/SkillController.kt @@ -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> { diff --git a/src/main/java/com/ffii/tsms/modules/data/web/models/NewStaffRequest.kt b/src/main/java/com/ffii/tsms/modules/data/web/models/NewStaffRequest.kt index 1e41acd..c5316f5 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/models/NewStaffRequest.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/models/NewStaffRequest.kt @@ -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?, + @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?, + val gradeId: Long?, + val phone2: String?, + val teamId: Long?, val departDate: LocalDate?, val departReason: String?, val remark: String?, From 6ea265f255af10dea65552d6305b220d5d883b47 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Tue, 14 May 2024 18:30:36 +0800 Subject: [PATCH 4/4] update project, subsidiary --- .../tsms/modules/data/entity/Subsidiary.java | 16 ++ .../data/entity/SubsidiaryContact.java | 11 +- .../data/entity/SubsidiaryRepository.java | 2 + .../tsms/modules/project/entity/Project.kt | 10 + .../project/entity/ProjectRepository.kt | 3 + .../entity/projections/ProjectSearchInfo.kt | 1 + .../project/service/ProjectsService.kt | 189 ++++++++++++------ .../project/web/models/EditProjectDetails.kt | 3 + .../project/web/models/NewProjectRequest.kt | 8 +- .../20240513_01_cyril/01_update_project.sql | 13 ++ .../20240513_01_cyril/02_update_project.sql | 5 + .../20240514_01_cyril/01_update_project.sql | 5 + 12 files changed, 197 insertions(+), 69 deletions(-) create mode 100644 src/main/resources/db/changelog/changes/20240513_01_cyril/01_update_project.sql create mode 100644 src/main/resources/db/changelog/changes/20240513_01_cyril/02_update_project.sql create mode 100644 src/main/resources/db/changelog/changes/20240514_01_cyril/01_update_project.sql diff --git a/src/main/java/com/ffii/tsms/modules/data/entity/Subsidiary.java b/src/main/java/com/ffii/tsms/modules/data/entity/Subsidiary.java index ed802df..980a8ba 100644 --- a/src/main/java/com/ffii/tsms/modules/data/entity/Subsidiary.java +++ b/src/main/java/com/ffii/tsms/modules/data/entity/Subsidiary.java @@ -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 { @JoinColumn(name = "typeId") private SubsidiaryType subsidiaryType; + @OneToMany(mappedBy = "subsidiary", cascade = ALL, orphanRemoval = true) + @JsonManagedReference + private List subsidiaryContacts; + public String getAddress() { return address; } @@ -78,4 +86,12 @@ public class Subsidiary extends BaseEntity { public void setSubsidiaryType(SubsidiaryType subsidiaryType) { this.subsidiaryType = subsidiaryType; } + + public List getSubsidiaryContacts() { + return subsidiaryContacts; + } + + public void setSubsidiaryContacts(List subsidiaryContacts) { + this.subsidiaryContacts = subsidiaryContacts; + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/data/entity/SubsidiaryContact.java b/src/main/java/com/ffii/tsms/modules/data/entity/SubsidiaryContact.java index 8355b8e..871a787 100644 --- a/src/main/java/com/ffii/tsms/modules/data/entity/SubsidiaryContact.java +++ b/src/main/java/com/ffii/tsms/modules/data/entity/SubsidiaryContact.java @@ -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 { @NotNull - @OneToOne + @ManyToOne + @JsonBackReference @JoinColumn(name = "subsidiaryId") private Subsidiary subsidiary; diff --git a/src/main/java/com/ffii/tsms/modules/data/entity/SubsidiaryRepository.java b/src/main/java/com/ffii/tsms/modules/data/entity/SubsidiaryRepository.java index 0742bcf..d94b9b9 100644 --- a/src/main/java/com/ffii/tsms/modules/data/entity/SubsidiaryRepository.java +++ b/src/main/java/com/ffii/tsms/modules/data/entity/SubsidiaryRepository.java @@ -9,5 +9,7 @@ import java.util.Optional; public interface SubsidiaryRepository extends AbstractRepository { List findAllByDeletedFalse(); + List findAllByDeletedFalseAndIdIn(List id); + Optional findByCode(@Param("code") String code); } \ 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 e51f7d2..54c7c37 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 @@ -108,4 +108,14 @@ open class Project : BaseEntity() { inverseJoinColumns = [JoinColumn(name = "workNaturesId")] ) open var workNatures: MutableSet = 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 } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt index cb0c28f..2cd303e 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt @@ -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 { fun findProjectSearchInfoByOrderByCreatedDesc(): List @@ -13,4 +14,6 @@ interface ProjectRepository : AbstractRepository { fun findInvoiceSearchInfoById(id: Long): List fun findInvoiceInfoSearchInfoById(id: Long): List + + fun findFirstByIsClpProjectAndIdNotOrderByIdDesc(isClpProject: Boolean, id: Serializable?): Project? } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectSearchInfo.kt b/src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectSearchInfo.kt index 75a520d..72dfde5 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectSearchInfo.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectSearchInfo.kt @@ -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? 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 2aeebb5..14afc36 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 @@ -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 { 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 { 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() 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 ) } diff --git a/src/main/java/com/ffii/tsms/modules/project/web/models/EditProjectDetails.kt b/src/main/java/com/ffii/tsms/modules/project/web/models/EditProjectDetails.kt index 7bd324b..c7ea88c 100644 --- a/src/main/java/com/ffii/tsms/modules/project/web/models/EditProjectDetails.kt +++ b/src/main/java/com/ffii/tsms/modules/project/web/models/EditProjectDetails.kt @@ -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, val workNatureIds: List, + val taskTemplateId: Long?, // Client details val clientId: Long?, 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 26f11f4..18bcfab 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 @@ -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, val workNatureIds: List, + val taskTemplateId: Long?, + val isSubsidiaryContact: Boolean?, // Client details val clientId: Long, diff --git a/src/main/resources/db/changelog/changes/20240513_01_cyril/01_update_project.sql b/src/main/resources/db/changelog/changes/20240513_01_cyril/01_update_project.sql new file mode 100644 index 0000000..a3288b9 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240513_01_cyril/01_update_project.sql @@ -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; diff --git a/src/main/resources/db/changelog/changes/20240513_01_cyril/02_update_project.sql b/src/main/resources/db/changelog/changes/20240513_01_cyril/02_update_project.sql new file mode 100644 index 0000000..d676c66 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240513_01_cyril/02_update_project.sql @@ -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`; diff --git a/src/main/resources/db/changelog/changes/20240514_01_cyril/01_update_project.sql b/src/main/resources/db/changelog/changes/20240514_01_cyril/01_update_project.sql new file mode 100644 index 0000000..4b47fc2 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240514_01_cyril/01_update_project.sql @@ -0,0 +1,5 @@ +-- liquibase formatted sql +-- changeset cyril:project + +ALTER TABLE `project` +ADD COLUMN `isClpProject` TINYINT NULL DEFAULT 0 AFTER `status`;