From 417f197fef454b73019769fde90ff537190e6d1d Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Tue, 21 May 2024 13:56:25 +0800 Subject: [PATCH 1/7] update work hour report --- .../projections/ProjectResourceReport.kt | 15 ++ .../modules/report/service/ReportService.kt | 205 +++++++++++++++--- .../modules/report/web/ReportController.kt | 58 +++-- .../modules/report/web/model/ReportRequest.kt | 5 + .../report/AR03_Resource Overconsumption.xlsx | Bin 14726 -> 13568 bytes 5 files changed, 243 insertions(+), 40 deletions(-) create mode 100644 src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectResourceReport.kt diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectResourceReport.kt b/src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectResourceReport.kt new file mode 100644 index 0000000..d949faa --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectResourceReport.kt @@ -0,0 +1,15 @@ +package com.ffii.tsms.modules.project.entity.projections + +data class ProjectResourceReport ( + val code: String, + val name: String, + val team: String, + val client: String, + val plannedBudget: Double, + val actualConsumedBudget: Double, + val plannedManhour: Double, + val actualConsumedManhour: Double, + val budgetConsumptionRate: Double, + val manhourConsumptionRate: Double, + val status: String +) \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt index 666e109..863eb90 100644 --- a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt @@ -7,12 +7,7 @@ import com.ffii.tsms.modules.data.entity.Team import com.ffii.tsms.modules.data.entity.Customer import com.ffii.tsms.modules.project.entity.Invoice import com.ffii.tsms.modules.project.entity.Project -import com.ffii.tsms.modules.timesheet.entity.Leave import com.ffii.tsms.modules.timesheet.entity.Timesheet -import com.ffii.tsms.modules.timesheet.entity.projections.MonthlyLeave -import com.ffii.tsms.modules.timesheet.entity.projections.ProjectMonthlyHoursWithDate -import com.ffii.tsms.modules.timesheet.entity.projections.TimesheetHours -import com.ffii.tsms.modules.timesheet.web.models.LeaveEntry import org.apache.commons.logging.Log import org.apache.commons.logging.LogFactory import org.apache.poi.ss.usermodel.* @@ -25,13 +20,17 @@ import org.springframework.stereotype.Service import java.io.ByteArrayOutputStream import java.io.IOException import java.math.BigDecimal -import java.sql.Time import java.time.LocalDate import java.time.format.DateTimeFormatter import java.util.* data class DayInfo(val date: String?, val weekday: String?) + +//private operator fun Map.component2(): Any { +// +//} + @Service open class ReportService( private val jdbcDao: JdbcDao, @@ -45,6 +44,7 @@ open class ReportService( private val MONTHLY_WORK_HOURS_ANALYSIS_REPORT = "templates/report/AR08_Monthly Work Hours Analysis Report.xlsx" private val SALART_LIST_TEMPLATE = "templates/report/Salary Template.xlsx" private val LATE_START_REPORT = "templates/report/AR01_Late Start Report v01.xlsx" + private val RESOURCE_OVERCONSUMPTION_REPORT = "templates/report/AR03_Resource Overconsumption.xlsx" // ==============================|| GENERATE REPORT ||============================== // @@ -147,6 +147,27 @@ open class ReportService( return outputStream.toByteArray() } + @Throws(IOException::class) + fun generateProjectResourceOverconsumptionReport( + team: String, + customer: String, + result: List> + ): ByteArray { + // Generate the Excel report with query results + val workbook: Workbook = createProjectResourceOverconsumptionReport( + team, + customer, + result, + RESOURCE_OVERCONSUMPTION_REPORT + ) + // Write the workbook to a ByteArrayOutputStream + val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() + workbook.write(outputStream) + workbook.close() + + return outputStream.toByteArray() + } + @Throws(IOException::class) fun exportSalaryList(salarys: List): ByteArray { // Generate the Excel report with query results @@ -737,10 +758,13 @@ open class ReportService( var projectList: List = listOf() println("----timesheets-----") println(timesheets) + + println("----leaves-----") + println(leaves) // result = timesheet record mapped var result: Map = mapOf() if (timesheets.isNotEmpty()) { - projectList = timesheets.map{ "${it["code"]}\n ${it["name"]}"}.toList() + projectList = timesheets.map{ "${it["code"]}\n ${it["name"]}"}.toList().distinct() result = timesheets.groupBy( { it["id"].toString() }, { mapOf( @@ -920,41 +944,40 @@ open class ReportService( columnSize++ } println(columnSize) - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////// main data ////////////////////////////////////////////////////////////// if (timesheets.isNotEmpty()) { - projectList.forEach { _ -> + println("2") + projectList.forEachIndexed { index, _ -> for (i in 0 until rowSize) { - tempCell = sheet.getRow(8 + i).createCell(columnIndex) + tempCell = sheet.getRow(8 + i).createCell(columnIndex + index) tempCell.setCellValue(0.0) tempCell.cellStyle.dataFormat = accountingStyle } - result.forEach{(id, list) -> - for (i in 0 until id.toInt()) { - val temp: List> = list as List> - temp.forEachIndexed { i, _ -> - dayInt = temp[i]["date"].toString().toInt() - tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) - tempCell.setCellValue(temp[i]["normalConsumed"] as Double) - } + } + result.forEach{ (id, list) -> + val temp: List> = list as List> + temp.forEachIndexed { index, _ -> + dayInt = temp[index]["date"].toString().toInt() + tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) + tempCell.setCellValue(temp[index]["normalConsumed"] as Double) } - } columnIndex++ } } - // leave hours data + /////////////////////////////////////////////////////////////// leave hours data ////////////////////////////////////////////////////////////// + for (i in 0 until rowSize) { + tempCell = sheet.getRow(8 + i).createCell(columnIndex) + tempCell.setCellValue(0.0) + tempCell.cellStyle.dataFormat = accountingStyle + } if (leaves.isNotEmpty()) { leaves.forEach { leave -> - for (i in 0 until rowSize) { - tempCell = sheet.getRow(8 + i).createCell(columnIndex) - tempCell.setCellValue(0.0) - tempCell.cellStyle.dataFormat = accountingStyle - } dayInt = leave["recordDate"].toString().toInt() tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) tempCell.setCellValue(leave["leaveHours"] as Double) } } - ///////////////////////////////////////////////////////// Leave Hours //////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////// Leave Hours title //////////////////////////////////////////////////////////////////// tempCell = sheet.getRow(rowIndex).createCell(columnIndex) tempCell.setCellValue("Leave Hours") tempCell.cellStyle = boldStyle @@ -1031,6 +1054,62 @@ open class ReportService( return workbook } + + private fun createProjectResourceOverconsumptionReport( + team: String, + customer: String, + result: List>, + templatePath: String + ):Workbook { + val resource = ClassPathResource(templatePath) + val templateInputStream = resource.inputStream + val workbook: Workbook = XSSFWorkbook(templateInputStream) + + val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") + + val sheet: Sheet = workbook.getSheetAt(0) + + val alignCenterStyle = workbook.createCellStyle() + alignCenterStyle.alignment = HorizontalAlignment.CENTER // Set the alignment to center + + var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field + var columnIndex = 2 + var tempRow = sheet.getRow(rowIndex) + var tempCell = tempRow.getCell(columnIndex) + tempCell.setCellValue(FORMATTED_TODAY) + + rowIndex = 2 + tempCell = sheet.getRow(rowIndex).getCell(columnIndex) + tempCell.setCellValue(team) + + rowIndex = 3 + tempCell = sheet.getRow(rowIndex).getCell(columnIndex) + tempCell.setCellValue(customer) + + rowIndex = 6 + columnIndex = 0 + result.forEachIndexed { index, obj -> + tempCell = sheet.getRow(rowIndex).getCell(columnIndex) + tempCell.setCellValue((index + 1).toDouble()) + val keys = obj.keys.toList() + keys.forEachIndexed { keyIndex, key -> + tempCell = sheet.getRow(rowIndex).getCell(columnIndex + keyIndex + 1) + when (obj[key]) { + is String -> tempCell.setCellValue(obj[key] as String) + is Double -> tempCell.setCellValue(obj[key] as Double) +// else -> tempCell.setCellValue(obj[key] as { + +// }) + } + } + rowIndex++ + } +// tempCell = sheet.getRow(rowIndex).getCell(columnIndex) +// tempCell.setCellValue() + + return workbook + } + //createLateStartReport private fun createLateStartReport( team: Team, @@ -1177,4 +1256,78 @@ open class ReportService( return jdbcDao.queryForList(sql.toString(), args) } + open fun getProjectResourceOverconsumptionReport(args: Map): List> { + val sql = StringBuilder("WITH teamNormalConsumed AS (" + + " SELECT " + + " s.teamId, " + + " pt.project_id, " + + " SUM(tns.totalConsumed) AS totalConsumed " + + " FROM ( " + + " SELECT " + + " t.staffId, " + + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, " + + " t.projectTaskId AS taskId " + + " FROM timesheet t " + + " LEFT JOIN staff s ON t.staffId = s.id " + + " GROUP BY t.staffId, t.projectTaskId " + + " ) AS tns " + + " INNER JOIN project_task pt ON tns.taskId = pt.id " + + " JOIN staff s ON tns.staffId = s.id " + + " JOIN team t ON s.teamId = t.id " + + " GROUP BY teamId, project_id " + + " ) " + + " SELECT " + + " p.code, " + + " -- p.status, " + + " p.name, " + + " t.code as team, " + + " c.code as client, " + + " p.expectedTotalFee as plannedBudget, " + + " COALESCE((tns.totalConsumed * sa.hourlyRate), 0) as actualConsumedBudget, " + + " COALESCE(p.totalManhour, 0) as plannedManhour, " + + " COALESCE(tns.totalConsumed, 0) as actualConsumedManhour, " + + " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) as budgetConsumptionRate, " + + " (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) as manhourConsumptionRate, " + + " CASE " + + " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 0.9 and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 0.9 and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1 " + + " then 'Potential Overconsumption' " + + " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1 " + + " then 'Overconsumption' " + + " else 'Within Budget' " + + " END as status " + + " FROM project p " + + " LEFT JOIN team t ON p.teamLead = t.teamLead " + + " LEFT JOIN staff s ON p.teamLead = s.id " + + " LEFT JOIN salary sa ON s.salaryId = sa.salaryPoint " + + " LEFT JOIN customer c ON p.customerId = c.id " + + " left join teamNormalConsumed tns on tns.project_id = p.id " + + " WHERE p.deleted = false " + + " and p.status = 'On-going' " + ) + if (args != null) { + var statusFilter: String = "" + if (args.containsKey("teamId")) + sql.append("and t.id = :teamId") + if (args.containsKey("custId")) + sql.append("and c.id = :custId") + if (args.containsKey("status")) + statusFilter = when (args.get("status")) { + "Potential Overconsumption" -> " ( and " + + "((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 0.9 " + + "and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + + "or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 0.9 " + + "and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1)" + + " ) " + "Overconsumption" -> " ( and " + + "((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + + "or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1)" + + " ) " + else -> "" + } + sql.append(statusFilter) + } + return jdbcDao.queryForList(sql.toString(), args) + } } diff --git a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt index 11f16de..b4a4bee 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt @@ -1,33 +1,34 @@ package com.ffii.tsms.modules.report.web +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.ffii.tsms.modules.data.entity.Customer import com.ffii.tsms.modules.data.entity.StaffRepository //import com.ffii.tsms.modules.data.entity.projections.FinancialStatusReportInfo import com.ffii.tsms.modules.data.entity.projections.StaffSearchInfo +import com.ffii.tsms.modules.data.entity.Team +import com.ffii.tsms.modules.data.service.CustomerService +import com.ffii.tsms.modules.data.service.TeamService import com.ffii.tsms.modules.project.entity.* -import com.ffii.tsms.modules.report.service.ReportService +import com.ffii.tsms.modules.project.entity.projections.ProjectResourceReport import com.ffii.tsms.modules.project.service.InvoiceService +import com.ffii.tsms.modules.report.service.ReportService import com.ffii.tsms.modules.report.web.model.FinancialStatusReportRequest import com.ffii.tsms.modules.report.web.model.ProjectCashFlowReportRequest +import com.ffii.tsms.modules.report.web.model.ProjectResourceOverconsumptionReport import com.ffii.tsms.modules.report.web.model.StaffMonthlyWorkHourAnalysisReportRequest import com.ffii.tsms.modules.report.web.model.LateStartReportRequest import com.ffii.tsms.modules.project.entity.ProjectRepository import com.ffii.tsms.modules.timesheet.entity.LeaveRepository import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository -import com.ffii.tsms.modules.timesheet.entity.projections.ProjectMonthlyHoursWithDate import jakarta.validation.Valid import org.springframework.core.io.ByteArrayResource import org.springframework.core.io.Resource -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.ServletRequestBindingException -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.RestController import org.springframework.http.HttpHeaders import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.ServletRequestBindingException +import org.springframework.web.bind.annotation.* import java.io.IOException import java.time.LocalDate import java.net.URLEncoder @@ -36,7 +37,6 @@ import org.springframework.stereotype.Controller import com.ffii.tsms.modules.data.entity.TeamRepository import com.ffii.tsms.modules.data.entity.CustomerRepository import org.springframework.data.jpa.repository.JpaRepository -import com.ffii.tsms.modules.data.entity.Customer import com.ffii.tsms.modules.project.entity.Project @RestController @@ -52,6 +52,8 @@ class ReportController( private val customerRepository: CustomerRepository, private val staffRepository: StaffRepository, private val leaveRepository: LeaveRepository, + private val teamService: TeamService, + private val customerService: CustomerService, private val invoiceService: InvoiceService) { @PostMapping("/fetchProjectsFinancialStatusReport") @@ -90,13 +92,16 @@ class ReportController( val nextMonth = request.yearMonth.plusMonths(1).atDay(1) val staff = staffRepository.findById(request.id).orElseThrow() + println(thisMonth) + println(nextMonth) val args: Map = mutableMapOf( "staffId" to request.id, "startDate" to thisMonth, "endDate" to nextMonth, ) - val timesheets= excelReportService.getTimesheet(args) - val leaves= excelReportService.getLeaves(args) + val timesheets = excelReportService.getTimesheet(args) + val leaves = excelReportService.getLeaves(args) + println(leaves) val reportResult: ByteArray = excelReportService.generateStaffMonthlyWorkHourAnalysisReport(thisMonth, staff, timesheets, leaves) // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") @@ -105,6 +110,31 @@ class ReportController( .header("filename", "Monthly Work Hours Analysis Report - " + staff.name + " - " + LocalDate.now() + ".xlsx") .body(ByteArrayResource(reportResult)) } + private val mapper = ObjectMapper().registerModule(KotlinModule()) + + @PostMapping("/ProjectResourceOverconsumptionReport") + @Throws(ServletRequestBindingException::class, IOException::class) + fun ProjectResourceOverconsumptionReport(@RequestBody @Valid request: ProjectResourceOverconsumptionReport): ResponseEntity { + +// val staff = staffRepository.findById(request.id).orElseThrow() + val args: Map = mutableMapOf( + "teamId" to request.teamId, + "custId" to request.custId, + "status" to request.status + ) + val team: String = teamService.find(request.teamId).orElseThrow().name + val customer: String = customerService.find(request.custId).orElseThrow().name + val result: List> = excelReportService.getProjectResourceOverconsumptionReport(args); +// val obj: ProjectResourceReport = mapper.readValue(mapper.writeValueAsBytes(result)) + + + val reportResult: ByteArray = excelReportService.generateProjectResourceOverconsumptionReport(team, customer, result) +// val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + return ResponseEntity.ok() +// .contentType(mediaType) + .header("filename", "Project Resource Overconsumption Report - " + " - " + LocalDate.now() + ".xlsx") + .body(ByteArrayResource(reportResult)) + } @GetMapping("/test/{id}") fun test(@PathVariable id: Long): List { diff --git a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt index 8a85bc5..54954c7 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt @@ -23,4 +23,9 @@ data class LateStartReportRequest ( val remainedDateTo: LocalDate, val customer: String, val reportDate: LocalDate +) +data class ProjectResourceOverconsumptionReport ( + val teamId: Long, + val custId: Long, + val status: String, ) \ No newline at end of file diff --git a/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx b/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx index dc4f048174101be1f4a7c801e9f7e8f533b5c609..8ef3799c357d1ba14b447f1c01177aeeefcdfb30 100644 GIT binary patch delta 5893 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 SLVr Date: Tue, 21 May 2024 16:12:49 +0800 Subject: [PATCH 2/7] update --- .../java/com/ffii/tsms/modules/data/service/StaffsService.kt | 2 +- .../com/ffii/tsms/modules/report/service/ReportService.kt | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) 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 b9532c9..26d2695 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 @@ -178,7 +178,7 @@ open class StaffsService( val company = companyRepository.findById(req.companyId).orElseThrow() val grade = if (req.gradeId != null && req.gradeId > 0L) gradeRepository.findById(req.gradeId).orElseThrow() else null val team = if (req.teamId != null && req.teamId > 0L) teamRepository.findById(req.teamId).orElseThrow() else null - val salary = salaryRepository.findById(req.salaryId).orElseThrow() + val salary = salaryRepository.findBySalaryPoint(req.salaryId).orElseThrow() val department = departmentRepository.findById(req.departmentId).orElseThrow() staff.apply { diff --git a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt index 863eb90..a01212c 100644 --- a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt @@ -1095,11 +1095,8 @@ open class ReportService( keys.forEachIndexed { keyIndex, key -> tempCell = sheet.getRow(rowIndex).getCell(columnIndex + keyIndex + 1) when (obj[key]) { - is String -> tempCell.setCellValue(obj[key] as String) is Double -> tempCell.setCellValue(obj[key] as Double) -// else -> tempCell.setCellValue(obj[key] as { - -// }) + else -> tempCell.setCellValue(obj[key] as String ) } } rowIndex++ From f9584e7b55c8d72b763650738f5ee9b0ef3361bb Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Tue, 21 May 2024 16:42:13 +0800 Subject: [PATCH 3/7] update --- .../java/com/ffii/tsms/modules/report/service/ReportService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt index a01212c..f66b0b7 100644 --- a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt @@ -959,7 +959,7 @@ open class ReportService( temp.forEachIndexed { index, _ -> dayInt = temp[index]["date"].toString().toInt() tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) - tempCell.setCellValue(temp[index]["normalConsumed"] as Double) + tempCell.setCellValue((temp[index]["normalConsumed"] as Double) + (temp[index]["otConsumed"] as Double)) } columnIndex++ } From ad929b03234a9a22f0c2a49199f2527a0c0567b4 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Tue, 21 May 2024 18:41:46 +0800 Subject: [PATCH 4/7] add "create sub project" --- .../project/entity/ProjectRepository.kt | 4 +- .../project/service/ProjectsService.kt | 174 +++++++++++------- .../modules/project/web/ProjectsController.kt | 9 +- .../project/web/models/MainProjectDetails.kt | 30 +++ .../project/web/models/NewProjectRequest.kt | 1 + 5 files changed, 150 insertions(+), 68 deletions(-) create mode 100644 src/main/java/com/ffii/tsms/modules/project/web/models/MainProjectDetails.kt 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 0f4b06b..b1c38f9 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 @@ -21,12 +21,12 @@ interface ProjectRepository : AbstractRepository { fun findAllByPlanStartLessThanEqualAndPlanEndGreaterThanEqual(remainedDateFrom: LocalDate?, remainedDateTo: LocalDate?):List - //fun findAllByDateRange(start: LocalDate, end: LocalDate): List - @Query("SELECT max(cast(substring_index(substring_index(p.code, '-', 2), '-', -1) as long)) FROM Project p WHERE p.isClpProject = ?1 and p.id != ?2" + "") fun getLatestCodeNumberByMainProject(isClpProject: Boolean, id: Serializable?): Long? @Query("SELECT max(cast(substring_index(p.code, '-', -1) as long)) FROM Project p WHERE p.code like ?1 and p.id != ?2") fun getLatestCodeNumberBySubProject(code: String, id: Serializable?): Long? + + fun findAllByStatusIsNotAndMainProjectIsNull(status: String): List } \ 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 ad4e206..73ba11f 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 @@ -6,6 +6,7 @@ import com.ffii.tsms.modules.data.service.CustomerContactService import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo import com.ffii.tsms.modules.data.service.CustomerService import com.ffii.tsms.modules.data.service.GradeService +import com.ffii.tsms.modules.data.service.SubsidiaryContactService import com.ffii.tsms.modules.project.entity.* import com.ffii.tsms.modules.project.entity.Milestone import com.ffii.tsms.modules.project.entity.projections.InvoiceInfoSearchInfo @@ -44,10 +45,12 @@ open class ProjectsService( private val taskGroupRepository: TaskGroupRepository, private val timesheetRepository: TimesheetRepository, private val taskTemplateRepository: TaskTemplateRepository, - private val subsidiaryRepository: SubsidiaryRepository, private val subsidiaryContactRepository: SubsidiaryContactRepository + private val subsidiaryContactService: SubsidiaryContactService, + private val subsidiaryContactRepository: SubsidiaryContactRepository ) { open fun allProjects(): List { - return projectRepository.findProjectSearchInfoByOrderByCreatedDesc().sortedByDescending { it.status?.lowercase() != "deleted" } + return projectRepository.findProjectSearchInfoByOrderByCreatedDesc() + .sortedByDescending { it.status?.lowercase() != "deleted" } } open fun allInvoices(): List { @@ -63,63 +66,57 @@ open class ProjectsService( } open fun markDeleted(id: Long) { - projectRepository.save(projectRepository.findById(id).orElseThrow() - .apply { - deleted = true - status = "Deleted" - }) + 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 - ) - } + 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 + ) } + } } } ?: emptyList() } open fun allProjectWithTasks(): List { return projectRepository.findAll().map { project -> - ProjectWithTasks( - id = project.id!!, + ProjectWithTasks(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) -> + .associateBy { milestone -> milestone.taskGroup!!.id!! }.mapValues { (_, milestone) -> MilestoneInfo( startDate = milestone.startDate?.format(DateTimeFormatter.ISO_LOCAL_DATE), endDate = milestone.endDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ) - } - ) + }) } } @@ -135,15 +132,28 @@ open class ProjectsService( if (latestProjectCode != null) { val lastFix = latestProjectCode - return "$prefix-" + String.format("%04d", lastFix + 1L) + return "$prefix-" + String.format("%04d", lastFix + 1L) } else { return "$prefix-0001" } } + + open fun createSubProjectCode(mainProject: Project, project: Project): String { + val prefix = mainProject.code + + val latestProjectCode = projectRepository.getLatestCodeNumberBySubProject(prefix!!, project.id ?: -1) + + if (latestProjectCode != null) { + val lastFix = latestProjectCode + return "$prefix-" + String.format("%03d", lastFix + 1L) + } else { + return "$prefix-001" + } + } + @Transactional open fun saveProject(request: NewProjectRequest): NewProjectResponse { - val projectCategory = - projectCategoryRepository.findById(request.projectCategoryId).orElseThrow() + val projectCategory = projectCategoryRepository.findById(request.projectCategoryId).orElseThrow() val fundingType = fundingTypeRepository.findById(request.fundingTypeId).orElseThrow() val serviceType = serviceTypeRepository.findById(request.serviceTypeId).orElseThrow() val contractType = contractTypeRepository.findById(request.contractTypeId).orElseThrow() @@ -158,6 +168,7 @@ open class ProjectsService( val subsidiaryContact = subsidiaryContactRepository.findById(request.clientContactId).orElse(null) val clientContact = customerContactService.findByContactId(request.clientContactId) val customerSubsidiary = request.clientSubsidiaryId?.let { subsidiaryService.findSubsidiary(it) } + val mainProject = if (request.mainProjectId != null && request.mainProjectId > 0) projectRepository.findById(request.mainProjectId).orElse(null) else null val allTasksMap = tasksService.allTasks().associateBy { it.id } val taskGroupMap = tasksService.allTaskGroups().associateBy { it.id } @@ -169,7 +180,7 @@ open class ProjectsService( project.apply { name = request.projectName description = request.projectDescription - code = if (this.code.isNullOrEmpty()) createProjectCode(request.isClpProject, project) else this.code + code = if (this.code.isNullOrEmpty() && request.mainProjectId == null) createProjectCode(request.isClpProject, project) else if (this.code.isNullOrEmpty() && request.mainProjectId != null && mainProject != null) createSubProjectCode(mainProject, project) else this.code expectedTotalFee = request.expectedProjectFee totalManhour = request.totalManhour actualStart = request.projectActualStart @@ -179,6 +190,7 @@ open class ProjectsService( else if (this.actualStart != null) "On-going" else "Pending To Start" isClpProject = request.isClpProject + this.mainProject = mainProject this.projectCategory = projectCategory this.fundingType = fundingType @@ -191,9 +203,12 @@ open class ProjectsService( this.teamLead = teamLead this.customer = customer - 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 + 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 } @@ -203,8 +218,7 @@ open class ProjectsService( 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 + project, taskGroup ) ?: Milestone() else Milestone() milestone.apply { val newMilestone = this @@ -233,8 +247,7 @@ open class ProjectsService( taskGroupAllocation.taskIds.map { taskId -> val projectTask = if (project.id != null && project.id!! > 0L) projectTaskRepository.findByProjectAndTask( - project, - allTasksMap[taskId]!! + project, allTasksMap[taskId]!! ) ?: ProjectTask() else ProjectTask() projectTask.apply { @@ -252,8 +265,7 @@ open class ProjectsService( val gradeAllocations = request.manhourPercentageByGrade.entries.map { val gradeAllocation = if (project.id != null && project.id!! > 0L) gradeAllocationRepository.findByProjectAndGrade( - project, - gradeMap[it.key]!! + project, gradeMap[it.key]!! ) ?: GradeAllocation() else GradeAllocation() gradeAllocation.apply { @@ -313,16 +325,16 @@ open class ProjectsService( val project = projectRepository.findById(projectId) return project.getOrNull()?.let { - val customerContact = - it.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) } - ?: emptyList() + val subsidiaryContact = it.customerSubsidiary?.id?.let { subsidiaryId -> + subsidiaryContactService.findAllBySubsidiaryId(subsidiaryId) + } ?: emptyList() + val customerContact = it.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) } + ?: emptyList() - val milestoneMap = it.milestones - .filter { milestone -> milestone.taskGroup?.id != null } + val milestoneMap = it.milestones.filter { milestone -> milestone.taskGroup?.id != null } .associateBy { milestone -> milestone.taskGroup!!.id!! } - EditProjectDetails( - projectId = it.id, + EditProjectDetails(projectId = it.id, projectDeleted = it.deleted, projectCode = it.code, projectName = it.name, @@ -341,7 +353,7 @@ open class ProjectsService( 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, + clientContactId = subsidiaryContact.find { contact -> contact.name == it.custLeadName }?.id ?: customerContact.find { contact -> contact.name == it.custLeadName }?.id, clientSubsidiaryId = it.customerSubsidiary?.id, totalManhour = it.totalManhour, manhourPercentageByGrade = gradeAllocationRepository.findByProject(it) @@ -349,8 +361,7 @@ open class ProjectsService( .associate { allocation -> Pair(allocation.grade!!.id!!, allocation.manhour ?: 0.0) }, taskGroups = projectTaskRepository.findAllByProject(it) .mapNotNull { projectTask -> if (projectTask.task?.taskGroup?.id != null) projectTask.task else null } - .groupBy { task -> task.taskGroup!!.id!! } - .mapValues { (taskGroupId, tasks) -> + .groupBy { task -> task.taskGroup!!.id!! }.mapValues { (taskGroupId, tasks) -> TaskGroupAllocation( taskIds = tasks.mapNotNull { task -> task.id }, percentAllocation = milestoneMap[taskGroupId]?.stagePercentAllocation ?: 0.0 @@ -359,8 +370,9 @@ open class ProjectsService( 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), + 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( @@ -369,8 +381,7 @@ open class ProjectsService( description = payment.description!!, date = payment.date!!.format(DateTimeFormatter.ISO_LOCAL_DATE) ) - } - ) + }) }, expectedProjectFee = it.expectedTotalFee ) @@ -400,4 +411,37 @@ open class ProjectsService( open fun allWorkNatures(): List { return workNatureRepository.findAll() } + + open fun allMainProjects(): List { + val mainProjects: List = projectRepository.findAllByStatusIsNotAndMainProjectIsNull("Deleted") + + return mainProjects.map { project: Project -> + val subsidiaryContact = project.customerSubsidiary?.id?.let { subsidiaryId -> + subsidiaryContactService.findAllBySubsidiaryId(subsidiaryId) + } ?: emptyList() + val customerContact = project.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) } + ?: emptyList() + + MainProjectDetails( + projectId = project.id, + projectCode = project.code, + projectName = project.name, + projectCategoryId = project.projectCategory?.id, + projectDescription = project.description, + projectLeadId = project.teamLead?.id, + projectStatus = project.status, + isClpProject = project.isClpProject, + serviceTypeId = project.serviceType?.id, + fundingTypeId = project.fundingType?.id, + contractTypeId = project.contractType?.id, + locationId = project.location?.id, + buildingTypeIds = project.buildingTypes.mapNotNull { buildingType -> buildingType.id }, + workNatureIds = project.workNatures.mapNotNull { workNature -> workNature.id }, + clientId = project.customer?.id, + clientContactId = subsidiaryContact.find { contact -> contact.name == project.custLeadName }?.id ?: customerContact.find { contact -> contact.name == project.custLeadName }?.id, + clientSubsidiaryId = project.customerSubsidiary?.id, + expectedProjectFee = project.expectedTotalFee + ) + } + } } diff --git a/src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt b/src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt index 0bf696a..447a50b 100644 --- a/src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt +++ b/src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt @@ -2,8 +2,10 @@ package com.ffii.tsms.modules.project.web import com.ffii.core.exception.NotFoundException import com.ffii.tsms.modules.data.entity.* +import com.ffii.tsms.modules.project.entity.Project import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo import com.ffii.tsms.modules.project.entity.ProjectCategory +import com.ffii.tsms.modules.project.entity.ProjectRepository import com.ffii.tsms.modules.project.service.ProjectsService import com.ffii.tsms.modules.project.web.models.* import jakarta.validation.Valid @@ -12,12 +14,17 @@ import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/projects") -class ProjectsController(private val projectsService: ProjectsService) { +class ProjectsController(private val projectsService: ProjectsService, private val projectRepository: ProjectRepository) { @GetMapping fun allProjects(): List { return projectsService.allProjects() } + @GetMapping("/main") + fun allMainProjects(): List { + return projectsService.allMainProjects() + } + @DeleteMapping("/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) fun deleteProject(@PathVariable id: Long) { diff --git a/src/main/java/com/ffii/tsms/modules/project/web/models/MainProjectDetails.kt b/src/main/java/com/ffii/tsms/modules/project/web/models/MainProjectDetails.kt new file mode 100644 index 0000000..3c2a748 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/web/models/MainProjectDetails.kt @@ -0,0 +1,30 @@ +package com.ffii.tsms.modules.project.web.models + +data class MainProjectDetails ( + + // Project details + val projectId: Long?, + val projectCode: String?, + val projectName: String?, + + val projectCategoryId: Long?, + val projectDescription: String?, + + val projectLeadId: Long?, + val projectStatus: String?, + val isClpProject: Boolean?, + + val serviceTypeId: Long?, + val fundingTypeId: Long?, + val contractTypeId: Long?, + val locationId: Long?, + val buildingTypeIds: List, + val workNatureIds: List, + + // Client details + val clientId: Long?, + val clientContactId: Long?, + val clientSubsidiaryId: Long?, + + val expectedProjectFee: Double?, +) \ No newline at end of file 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 18bcfab..1f59b33 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 @@ -17,6 +17,7 @@ data class NewProjectRequest( val projectActualStart: LocalDate?, val projectActualEnd: LocalDate?, val isClpProject: Boolean?, + val mainProjectId: Long?, val serviceTypeId: Long, val fundingTypeId: Long, From 4e2a6df392478b950173ef2b5b4cbeb8d64ca7f3 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Wed, 22 May 2024 13:25:16 +0800 Subject: [PATCH 5/7] update Overconsumption report --- .../modules/report/service/ReportService.kt | 77 +++++++++++------- .../modules/report/web/ReportController.kt | 27 +++--- .../modules/report/web/model/ReportRequest.kt | 5 +- .../report/AR03_Resource Overconsumption.xlsx | Bin 13568 -> 13465 bytes 4 files changed, 65 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt index f66b0b7..68bb5bc 100644 --- a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt @@ -1,10 +1,10 @@ package com.ffii.tsms.modules.report.service import com.ffii.core.support.JdbcDao +import com.ffii.tsms.modules.data.entity.Customer import com.ffii.tsms.modules.data.entity.Salary import com.ffii.tsms.modules.data.entity.Staff import com.ffii.tsms.modules.data.entity.Team -import com.ffii.tsms.modules.data.entity.Customer import com.ffii.tsms.modules.project.entity.Invoice import com.ffii.tsms.modules.project.entity.Project import com.ffii.tsms.modules.timesheet.entity.Timesheet @@ -151,13 +151,15 @@ open class ReportService( fun generateProjectResourceOverconsumptionReport( team: String, customer: String, - result: List> + result: List>, + lowerLimit: Double ): ByteArray { // Generate the Excel report with query results val workbook: Workbook = createProjectResourceOverconsumptionReport( team, customer, result, + lowerLimit, RESOURCE_OVERCONSUMPTION_REPORT ) // Write the workbook to a ByteArrayOutputStream @@ -1059,6 +1061,7 @@ open class ReportService( team: String, customer: String, result: List>, + lowerLimit: Double, templatePath: String ):Workbook { val resource = ClassPathResource(templatePath) @@ -1089,11 +1092,11 @@ open class ReportService( rowIndex = 6 columnIndex = 0 result.forEachIndexed { index, obj -> - tempCell = sheet.getRow(rowIndex).getCell(columnIndex) + tempCell = sheet.getRow(rowIndex).createCell(columnIndex) tempCell.setCellValue((index + 1).toDouble()) val keys = obj.keys.toList() keys.forEachIndexed { keyIndex, key -> - tempCell = sheet.getRow(rowIndex).getCell(columnIndex + keyIndex + 1) + tempCell = sheet.getRow(rowIndex).getCell(columnIndex + keyIndex + 1) ?: sheet.getRow(rowIndex).createCell(columnIndex + keyIndex + 1) when (obj[key]) { is Double -> tempCell.setCellValue(obj[key] as Double) else -> tempCell.setCellValue(obj[key] as String ) @@ -1101,8 +1104,21 @@ open class ReportService( } rowIndex++ } -// tempCell = sheet.getRow(rowIndex).getCell(columnIndex) -// tempCell.setCellValue() + + val sheetCF = sheet.sheetConditionalFormatting + val rule1 = sheetCF.createConditionalFormattingRule("AND(J7 >= $lowerLimit, J7 <= 1)") + val rule2 = sheetCF.createConditionalFormattingRule("J7 > 1") + var fillOrange = rule1.createPatternFormatting() + fillOrange.setFillBackgroundColor(IndexedColors.LIGHT_ORANGE.index); + fillOrange.setFillPattern(PatternFormatting.SOLID_FOREGROUND) + + var fillRed = rule2.createPatternFormatting() + fillRed.setFillBackgroundColor(IndexedColors.ROSE.index); + fillRed.setFillPattern(PatternFormatting.SOLID_FOREGROUND) + + val cfRules = arrayOf(rule1, rule2) + val regions = arrayOf(CellRangeAddress.valueOf("\$J7:\$K${rowIndex+1}")) + sheetCF.addConditionalFormatting(regions, cfRules); return workbook } @@ -1256,26 +1272,25 @@ open class ReportService( open fun getProjectResourceOverconsumptionReport(args: Map): List> { val sql = StringBuilder("WITH teamNormalConsumed AS (" + " SELECT " - + " s.teamId, " + + " s.teamId, " + " pt.project_id, " + " SUM(tns.totalConsumed) AS totalConsumed " - + " FROM ( " - + " SELECT " - + " t.staffId, " + + " FROM ( " + + " SELECT " + + " t.staffId, " + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, " + " t.projectTaskId AS taskId " - + " FROM timesheet t " - + " LEFT JOIN staff s ON t.staffId = s.id " - + " GROUP BY t.staffId, t.projectTaskId " + + " FROM timesheet t " + + " LEFT JOIN staff s ON t.staffId = s.id " + + " GROUP BY t.staffId, t.projectTaskId " + " ) AS tns " - + " INNER JOIN project_task pt ON tns.taskId = pt.id " - + " JOIN staff s ON tns.staffId = s.id " - + " JOIN team t ON s.teamId = t.id " - + " GROUP BY teamId, project_id " + + " INNER JOIN project_task pt ON tns.taskId = pt.id " + + " JOIN staff s ON tns.staffId = s.id " + + " JOIN team t ON s.teamId = t.id " + + " GROUP BY teamId, project_id " + " ) " + " SELECT " + " p.code, " - + " -- p.status, " + " p.name, " + " t.code as team, " + " c.code as client, " @@ -1286,8 +1301,8 @@ open class ReportService( + " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) as budgetConsumptionRate, " + " (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) as manhourConsumptionRate, " + " CASE " - + " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 0.9 and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " - + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 0.9 and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1 " + + " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1 " + " then 'Potential Overconsumption' " + " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1 " @@ -1311,16 +1326,18 @@ open class ReportService( sql.append("and c.id = :custId") if (args.containsKey("status")) statusFilter = when (args.get("status")) { - "Potential Overconsumption" -> " ( and " + - "((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 0.9 " + - "and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + - "or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 0.9 " + - "and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1)" + - " ) " - "Overconsumption" -> " ( and " + - "((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + - "or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1)" + - " ) " + "Potential Overconsumption" -> "and " + + " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 0.9 " + + " and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 0.9 " + + " and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1)" + "Overconsumption" -> "and " + + " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1) " + "Within Budget" -> "and " + + " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) < 0.9 " + + " and " + + " (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 0.9 " else -> "" } sql.append(statusFilter) diff --git a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt index b4a4bee..0037ec6 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt @@ -115,20 +115,23 @@ class ReportController( @PostMapping("/ProjectResourceOverconsumptionReport") @Throws(ServletRequestBindingException::class, IOException::class) fun ProjectResourceOverconsumptionReport(@RequestBody @Valid request: ProjectResourceOverconsumptionReport): ResponseEntity { - -// val staff = staffRepository.findById(request.id).orElseThrow() - val args: Map = mutableMapOf( - "teamId" to request.teamId, - "custId" to request.custId, - "status" to request.status + val lowerLimit = request.lowerLimit + var team: String = "All" + var customer: String = "All" + val args: MutableMap = mutableMapOf( + "status" to request.status, + "lowerLimit" to lowerLimit ) - val team: String = teamService.find(request.teamId).orElseThrow().name - val customer: String = customerService.find(request.custId).orElseThrow().name + if (request.teamId != null) { + args["teamId"] = request.teamId + team = teamService.find(request.teamId).orElseThrow().name + } + if (request.custId != null) { + args["custId"] = request.custId + customer = customerService.find(request.custId).orElseThrow().name + } val result: List> = excelReportService.getProjectResourceOverconsumptionReport(args); -// val obj: ProjectResourceReport = mapper.readValue(mapper.writeValueAsBytes(result)) - - - val reportResult: ByteArray = excelReportService.generateProjectResourceOverconsumptionReport(team, customer, result) + val reportResult: ByteArray = excelReportService.generateProjectResourceOverconsumptionReport(team, customer, result, lowerLimit) // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") return ResponseEntity.ok() // .contentType(mediaType) diff --git a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt index 54954c7..d08d3f1 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt @@ -25,7 +25,8 @@ data class LateStartReportRequest ( val reportDate: LocalDate ) data class ProjectResourceOverconsumptionReport ( - val teamId: Long, - val custId: Long, + val teamId: Long?, + val custId: Long?, val status: String, + val lowerLimit: Double ) \ No newline at end of file diff --git a/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx b/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx index 8ef3799c357d1ba14b447f1c01177aeeefcdfb30..9d492150c45b314a574e2634712ee02d65232cdf 100644 GIT binary patch delta 2452 zcmV;F32XL%YME)U+6Dzxq7+PN?CfQDdLpme^^|z zk`%DAW<19dhWyzSB_}u$DIqe?Oxv=$W{zp0HwYfT1cR;DG$sLytDF?F0pTRW66lMR zmZC+=<4fRj%wMX~h*@3&#ENEAzSW?*mdD=RbHO-XWnlIex!RV)uM2-ZTfe~YFK#M+enQ&CPJcR)g5e-5-b_V7wP zV3}$a&xZQgTYM2<;lhsSqE)?EsN54Jj>%#^o6L+Aj$cTTECi1iDx($v$|@ccv5+J$ zAv@rWlag_{I8rS>Pm%wiTO2F5nc%9%oaX&|w}DmMYqj|xxd!id!9B!hhF>9CpowIc z%y$qD)L9lW_t5fdXW;kTf3VjvM!~>0oPN|b{E=-NPNyGOy}(CdKl}#9@U9m#T*-7N z1O+|R9f6;=a|(U6v_GNN$51P)yi_$4io2@E%J)#vxTlWhcS)TRW{Z}PH7!V@@(Zw! zM!OT1+pNg1ml0Jt6JUu~SP+$VG0tW!JQ$bKBq8dS(QiNgl-0)%SN>JkySl$Z&W(9= zAc*Xg44e=@a7}f!e|`qYvIiPqwjQVW9$TW z==Q=O9JxwS>N@lOkD^{@wAbE~ie-wqoO8%lxLv1YJpwl=Z>T&1zQ<%oZhvHTfT9zj z$Z*iWGDf4WW4J-oaeF8jhi-HrtIS`&v!wRT`fGw^1!Y5(QK5S5sW#fp?kCsH#4he9 z)xw(w%8=dHKifD1>oaoU=IHUljpNUg`Q*aA_u+i`6kWR6pNsi<0t|7wpg zf9TLA;Hs@tRl)44Xb})rx3Ct8otVvry9vfZVy$Z7x;ecRfN~*K$yr&7@{aRWjBIu zDpu*a94(p$R_K3>m1T$;IWr#4tBCnMa}J;K!rIg$fnepD1Y)?uTzw`elm)OaWF*P=6A8ZV|=yN zplayCx^>|s-`_ArVJtwG8=9O{lu$MNcPehi8(d9xHg10p@Sz(gNr1;AfGE8;PKS%9 z8>L0n=C^(B5wL3!5L-lm-5`li36SOt0lZ)d5IaKPj+1c|o`^V)h*|{17J>bDBA{NY z0%C`NlG+xW&mOl&A$CXOlRG*xmQ3k6Hq3T=d-icgV5G=f~*)%ITUGj zA0?pPI2%lz!)I7kw`mm>L6oF7L6XE}P?Xz65GJcQE$3xg#K{-THtRETgB5mNuZ}cW z$svM&?o=Q7`Y}EGi*J9{t|g0Nj*qNPWM${MOHlIeFb7K&*%##AkR1n|Zty{KV{ETF zJb6rbHD7>Y)3m=r$c_He`%ff7=O5Lgu8~I-gJ+Sy`y!_1J<8ye(++uWK0N*!RDaZ+ zf2~44VdOw}IH5oh8?>Uu@0LTSZSAuT{W973cf6}Fx4sfT_bmjXY`y`Lp#v1NTND!* z1@D_UN=>tKEG7YeL2JVx7>4hI{fFQ^F{W!*BuQZ{J*_ZU#>Q@W_ReXFvE_S^%tpSUe7LQRWX>;t%?@kE4{QfGiQ^fT;Fw^J z;T9O`fru_h!3U&1nyLc`j-dhx%2`Yzf`7UWjI@6q=#o)WmhPh&A) zqVO*4p9P delta 2573 zcmV+o3i9=tX@F|5+6D#b0|EWklivm&f8sb4{l3!vhfGvyKBbBC;1Lx@K!~XSxG0@SqrE8>! zSwf4af&OI~8a-VT5*G>1SV0E*D-rsq;qSlQY#D!EGxn?jKp_TtDrM=LrifFLe`9gY zN>aeehVdLr81g4ml$_v1q=d*kGi}T2m^r3}-XQq=5)8K4(3k`)u5wby285FgOQ0`O zT8b7ek1v7CF@LT~BW8IC5Nn!I`C5bOS|0niPX*(6oq^dq?ZVWk{~c$qQ96Jh0-Oce3gnusUyG$oge81k$Rt z;)CGKpGAaU4R`HmKNRqsS z?0`2;O2%b$q*{EQBL6|RI96^m!Bvep&HMLm1FN{-YV%%l4c>2qJBZH$zd*D=6UjcA zZy_AWSrsw&(dw(^jDwzIe3?wqiEWPZ$LaqYxfLe>EqJ%p*#5aRq91 zuN8GdjOLaDT5A-c{Ry=`hFV$WrK*`w+*LhRzK4RweRVXyP3n{|TeO61Xh9N{Ux0ly z+Mlr6Wkr6y3aQGO086~af~d5MakgmT!MKzr2~oF|YpSdDf73^n-B)oUJ9#H$-9su-a42%EQP=9bhBb9O!|nBLqvtxdF>wQX z>UF2VbX+S+U1$FPQPk^<_S<_>u}m?SOAgrzx9gm2#^5I94V6d0_n7S1>y0f3D7qmE z4Hxw-V?6G-h8Kj6*G0i(>V-=F4OwOW=A9+AXVza6EGsA*Q>qNrV_&u5e)cfAX(sk@ zKdBb}JWz)0zy8_A1z4Yv3pa-k7jB$jb0KV0iv6{+)*Q*RYJuU_lg3*8QL?AuI3*<%(hZJ>osG>146Acv^J)h z27#t)CW!WB!)ll*f29({z+bfrbfXw4+$K>4;c%D)g76w02vc_*a!V6{0w z6sw2<{d&zC-O&WS1Sg1k-ZTR$MFWT>uejMeDDniI-mPk>$g+aEcQ_{0+bQ^j@Qu1O zZv0gy!Ksp3mc|EiaM1j^{4_`dLVMEd`Y*!ZI8f|`+Xfy{f8qNi;Ze^DkFe;Su%w6C z<5JTNFEaEe4)gKNZHNbX6h(s==i|ZkIL-(0_2l+bzKCJ&_Ai>xoKNo6eAbW^dtm0N zxmRAv&7-`B0q`mc=Chz*Rd6tbJp@m&GDFvR`ZbL061U<9XIp*p>>09rU=^c=fhLT+ z-%^SPN-Flve;T%eu_0UqYx1R(&vyGAEF4;0ZktU+sNpXxXB8~}f&rp`x!m@-*JihN zI!{$B+{J*B3RaR$WpY{l4hG`1- z0F?QW%}HeiWx}r~xV_I{W3qE`dw`GpBuzs+83RO{f4#Lfbee9ok)qA-#PZ36$XDF%mHfOsRMWJun%2x z|9>`T5e$Mmhu|cd99w*P5BiGiy>he94-f-?d{SLcgFq1cE0=q3fflJYrAdo6){n%f zf8c}#E?#eOS^SLu-KDMC2jd$v*_)Z2WoA0Ym30iH2+CA1lIc|xYxPRg&K9Zu2(#=g z$)d#|ij&hp9FLcS#d38t2;<{%vK%dw#W?=d!F;v>svraBT9sqxIn|LqVE6Ko$oJEK z!SDEd*3{^MZGJIG`4<2H0RR6308mQ<1QYBvQm(t3s|(s@9$Es zDX$DXn@WRm1=zrol%-r}=-`~D7+Zb-$!z2s%7?qkNap;1((JKj{K)oz6GEm~f@6X? zh8tk0dm_3b1@DpiWXcvGIEE4=C}(jT#rV7Hz)1V&fi@X5W#KN`zv-$wWDDMfefw;S zzF60FRF7H>?;qp$&C^T!)UZ&^tT>=N6`XPd%vB~oSuiOcY&63wnROdK=UoBf@sh8= zz%f`Jm`d?T@6^*;`3?yp@H(`Uza?*@10mF$$)qR@iXS&yzng zEgWWc@t73{007n*000;O0000000000000000u7TK4H}b0G!T>Z6A%ml0000000000 z08JE=&N4p&5gU^aGdTgmlSwl{0wyApj5ALHC?k{JA}NywG!PsoUN_NB0RRAB0ssIJ j0000000000000000AMVWGBiB_<&$tUBL>7V00000)K Date: Wed, 22 May 2024 14:29:00 +0800 Subject: [PATCH 6/7] update overconsumption template --- .../report/AR03_Resource Overconsumption.xlsx | Bin 13465 -> 13285 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx b/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx index 9d492150c45b314a574e2634712ee02d65232cdf..ff196684bcd0749acb7fbb232bfe5014990cfa71 100644 GIT binary patch delta 4160 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 delta 4338 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 Un2ZuhxHzU&hL7Tr)IYiZ0tGPyW&i*H From e86edfdd2eecf05a50cde6f74f347b70e43848c4 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Wed, 22 May 2024 16:03:47 +0800 Subject: [PATCH 7/7] update project --- .../data/service/CustomerContactService.kt | 2 +- .../tsms/modules/project/entity/Project.kt | 8 ++++++++ .../project/entity/ProjectRepository.kt | 2 +- .../entity/projections/ProjectSearchInfo.kt | 3 +++ .../project/service/ProjectsService.kt | 16 +++++++++------ .../project/web/models/EditProjectDetails.kt | 1 + .../20240522_01_cyril/01_update_project.sql | 20 +++++++++++++++++++ 7 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 src/main/resources/db/changelog/changes/20240522_01_cyril/01_update_project.sql diff --git a/src/main/java/com/ffii/tsms/modules/data/service/CustomerContactService.kt b/src/main/java/com/ffii/tsms/modules/data/service/CustomerContactService.kt index 350f798..6d80457 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/CustomerContactService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/CustomerContactService.kt @@ -17,7 +17,7 @@ class CustomerContactService( } fun findByContactId(contactId: Long): CustomerContact { - return customerContactRepository.findById(contactId).orElseThrow() + return customerContactRepository.findById(contactId).orElse(null) } fun saveContactsByCustomer(saveCustomerId: Long, saveCustomerContact: List) { 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 9294413..533dcd3 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 @@ -123,4 +123,12 @@ open class Project : BaseEntity() { // @JsonBackReference @JoinColumn(name = "mainProjectId") open var mainProject: Project? = null + + @ManyToOne + @JoinColumn(name = "customerContactId") + open var customerContact: CustomerContact? = null + + @ManyToOne + @JoinColumn(name = "subsidiaryContactId") + open var subsidiaryContact: SubsidiaryContact? = 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 b1c38f9..57d1720 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 @@ -25,7 +25,7 @@ interface ProjectRepository : AbstractRepository { "") fun getLatestCodeNumberByMainProject(isClpProject: Boolean, id: Serializable?): Long? - @Query("SELECT max(cast(substring_index(p.code, '-', -1) as long)) FROM Project p WHERE p.code like ?1 and p.id != ?2") + @Query("SELECT max(case when length(p.code) - length(replace(p.code, '-', '')) > 1 then cast(substring_index(p.code, '-', -1) as long) end) FROM Project p WHERE p.code like ?1 and p.id != ?2") fun getLatestCodeNumberBySubProject(code: String, id: Serializable?): Long? fun findAllByStatusIsNotAndMainProjectIsNull(status: String): List 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 72dfde5..7843a95 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 @@ -11,6 +11,9 @@ interface ProjectSearchInfo { val code: String? val status: String? + @get:Value("#{target.mainProject?.code}") + val mainProject: 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 73ba11f..16012b6 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 @@ -210,6 +210,8 @@ open class ProjectsService( custLeadPhone = if (request.isSubsidiaryContact == null || !request.isSubsidiaryContact) clientContact.phone else subsidiaryContact.phone this.customerSubsidiary = customerSubsidiary + this.customerContact = if (customerSubsidiary == null) clientContact else null + this.subsidiaryContact = if (customerSubsidiary != null) subsidiaryContact else null } @@ -325,11 +327,11 @@ open class ProjectsService( val project = projectRepository.findById(projectId) return project.getOrNull()?.let { - val subsidiaryContact = it.customerSubsidiary?.id?.let { subsidiaryId -> - subsidiaryContactService.findAllBySubsidiaryId(subsidiaryId) - } ?: emptyList() - val customerContact = it.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) } - ?: emptyList() +// val subsidiaryContact = it.customerSubsidiary?.id?.let { subsidiaryId -> +// subsidiaryContactService.findAllBySubsidiaryId(subsidiaryId) +// } ?: emptyList() +// val customerContact = it.customer?.id?.let { customerId -> customerContactService.findAllByCustomerId(customerId) } +// ?: emptyList() val milestoneMap = it.milestones.filter { milestone -> milestone.taskGroup?.id != null } .associateBy { milestone -> milestone.taskGroup!!.id!! } @@ -345,6 +347,7 @@ open class ProjectsService( projectActualEnd = it.actualEnd, projectStatus = it.status, isClpProject = it.isClpProject, + mainProjectId = it.mainProject?.id, serviceTypeId = it.serviceType?.id, fundingTypeId = it.fundingType?.id, contractTypeId = it.contractType?.id, @@ -353,7 +356,8 @@ open class ProjectsService( buildingTypeIds = it.buildingTypes.mapNotNull { buildingType -> buildingType.id }, workNatureIds = it.workNatures.mapNotNull { workNature -> workNature.id }, clientId = it.customer?.id, - clientContactId = subsidiaryContact.find { contact -> contact.name == it.custLeadName }?.id ?: customerContact.find { contact -> contact.name == it.custLeadName }?.id, + clientContactId = it.subsidiaryContact?.id ?: it.customerContact?.id, +// subsidiaryContact.find { contact -> contact.name == it.custLeadName }?.id ?: customerContact.find { contact -> contact.name == it.custLeadName }?.id, clientSubsidiaryId = it.customerSubsidiary?.id, totalManhour = it.totalManhour, manhourPercentageByGrade = gradeAllocationRepository.findByProject(it) 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 c7ea88c..704f0fd 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 @@ -15,6 +15,7 @@ data class EditProjectDetails( val projectActualEnd: LocalDate?, val projectStatus: String?, val isClpProject: Boolean?, + val mainProjectId: Long?, val serviceTypeId: Long?, val fundingTypeId: Long?, diff --git a/src/main/resources/db/changelog/changes/20240522_01_cyril/01_update_project.sql b/src/main/resources/db/changelog/changes/20240522_01_cyril/01_update_project.sql new file mode 100644 index 0000000..96bc4d4 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240522_01_cyril/01_update_project.sql @@ -0,0 +1,20 @@ +-- liquibase formatted sql +-- changeset cyril:project + +ALTER TABLE `project` + ADD COLUMN `customerContactId` INT NULL DEFAULT NULL AFTER `mainProjectId`, +ADD COLUMN `subsidiaryContactId` INT NULL DEFAULT NULL AFTER `customerContactId`, +ADD INDEX `FK_PROJECT_ON_CUSTOMERCONTACTID` (`customerContactId` ASC) VISIBLE, +ADD INDEX `FK_PROJECT_ON_SUBSIDIARYCONTACTID` (`subsidiaryContactId` ASC) VISIBLE; +; +ALTER TABLE `project` + ADD CONSTRAINT `FK_PROJECT_ON_CUSTOMERCONTACTID` + FOREIGN KEY (`customerContactId`) + REFERENCES `customer_contact` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, +ADD CONSTRAINT `FK_PROJECT_ON_SUBSIDIARYCONTACTID` + FOREIGN KEY (`subsidiaryContactId`) + REFERENCES `subsidiary_contact` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION;