From e5cd79f15da500750734574fba326801c7c982e1 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Tue, 9 Jul 2024 11:35:38 +0800 Subject: [PATCH] update staff, project, report --- .../modules/data/entity/StaffRepository.java | 4 +- .../modules/data/service/StaffsService.kt | 6 +- .../tsms/modules/project/entity/Project.kt | 1 + .../project/entity/ProjectRepository.kt | 6 +- .../project/service/ProjectsService.kt | 65 +++++++++++++------ .../project/web/models/NewProjectResponse.kt | 2 + .../modules/report/service/ReportService.kt | 48 ++++++++++++-- .../modules/report/web/ReportController.kt | 2 +- .../modules/user/service/GroupService.java | 2 +- .../20240708_01_cyril/01_update_quthority.sql | 7 ++ 10 files changed, 109 insertions(+), 34 deletions(-) create mode 100644 src/main/resources/db/changelog/changes/20240708_01_cyril/01_update_quthority.sql diff --git a/src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java b/src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java index f6e8a46..0559e1c 100644 --- a/src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java +++ b/src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java @@ -11,8 +11,8 @@ import java.util.Map; import java.util.Optional; public interface StaffRepository extends AbstractRepository { - List findStaffSearchInfoByAndDeletedFalse(); - List findStaffSearchInfoByAndDeletedFalseAndTeamIdIsNull(); + List findStaffSearchInfoByAndDeletedFalseOrderByStaffIdAsc(); + List findStaffSearchInfoByAndDeletedFalseAndTeamIdIsNullOrderByStaffIdAsc(); List findAllStaffSearchInfoByIdIn(List ids); Optional findByStaffId(@Param("staffId") String staffId); 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 ba2d1b4..059013f 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 @@ -41,10 +41,10 @@ open class StaffsService( } open fun allStaff(): List { - return staffRepository.findStaffSearchInfoByAndDeletedFalse(); + return staffRepository.findStaffSearchInfoByAndDeletedFalseOrderByStaffIdAsc(); } open fun StaffWithoutTeam(): List { - return staffRepository.findStaffSearchInfoByAndDeletedFalseAndTeamIdIsNull(); + return staffRepository.findStaffSearchInfoByAndDeletedFalseAndTeamIdIsNullOrderByStaffIdAsc(); } open fun getStaff(id: Long): Staff { @@ -115,7 +115,7 @@ open class StaffsService( @Transactional(rollbackFor = [Exception::class]) open fun saveStaff(req: NewStaffRequest): Staff { - val checkStaffIdList: List = staffRepository.findStaffSearchInfoByAndDeletedFalse() + val checkStaffIdList: List = staffRepository.findStaffSearchInfoByAndDeletedFalseOrderByStaffIdAsc() checkStaffIdList.forEach{ s -> if (s.staffId == req.staffId) { throw UnprocessableEntityException("Duplicated StaffId Found") 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 e52f80b..0793275 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 @@ -20,6 +20,7 @@ open class Project : BaseEntity() { @NotNull @Column(name = "code", length = 30) + @OrderBy("code asc") open var code: String? = null @ManyToOne 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 4fdf2dc..abc61dd 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 @@ -12,7 +12,7 @@ import java.io.Serializable import java.time.LocalDate interface ProjectRepository : AbstractRepository { - fun findProjectSearchInfoByOrderByCreatedDesc(): List + fun findProjectSearchInfoByDeletedIsFalseOrderByCodeDesc(): List fun findInvoiceSearchInfoBy(): List @@ -24,9 +24,9 @@ interface ProjectRepository : AbstractRepository { fun findAllByPlanStartLessThanEqualAndPlanEndGreaterThanEqual(remainedDateFrom: LocalDate?, remainedDateTo: 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" + + @Query("SELECT max(cast(substring_index(substring_index(p.code, '-', 2), '-', -1) as long)) FROM Project p WHERE p.id != ?1" + "") - fun getLatestCodeNumberByMainProject(isClpProject: Boolean, id: Serializable?): Long? + fun getLatestCodeNumberByMainProject(id: Serializable?): Long? @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? 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 66f2a71..d043718 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 @@ -4,6 +4,7 @@ import com.ffii.core.utils.ExcelUtils import com.ffii.tsms.modules.common.SecurityUtils import com.ffii.tsms.modules.data.entity.* import com.ffii.tsms.modules.data.service.* +import com.ffii.tsms.modules.data.web.models.SaveCustomerResponse import com.ffii.tsms.modules.project.entity.* import com.ffii.tsms.modules.project.entity.Milestone import com.ffii.tsms.modules.project.entity.projections.InvoiceInfoSearchInfo @@ -12,12 +13,18 @@ import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo import com.ffii.tsms.modules.project.web.models.* import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository import org.apache.commons.logging.LogFactory +import org.apache.poi.ss.usermodel.CellType +import org.apache.poi.ss.usermodel.DataFormatter +import org.apache.poi.ss.usermodel.DateUtil import org.apache.poi.ss.usermodel.Sheet import org.apache.poi.ss.usermodel.Workbook import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.time.LocalDate +import java.time.ZoneId import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle +import java.util.Locale import kotlin.jvm.optionals.getOrNull @@ -53,8 +60,7 @@ open class ProjectsService( private val customerSubsidiaryService: CustomerSubsidiaryService, ) { open fun allProjects(): List { - return projectRepository.findProjectSearchInfoByOrderByCreatedDesc() - .sortedByDescending { it.status?.lowercase() != "deleted" } + return projectRepository.findProjectSearchInfoByDeletedIsFalseOrderByCodeDesc() } open fun allInvoices(): List { @@ -132,7 +138,7 @@ open class ProjectsService( val checkIsClpProject = isClpProject != null && isClpProject val prefix = if (checkIsClpProject) "C" else "M" - val latestProjectCode = projectRepository.getLatestCodeNumberByMainProject(checkIsClpProject, project.id ?: -1) + val latestProjectCode = projectRepository.getLatestCodeNumberByMainProject(project.id ?: -1) if (latestProjectCode != null) { val lastFix = latestProjectCode @@ -186,17 +192,31 @@ open class ProjectsService( val project = if (request.projectId != null && request.projectId > 0) projectRepository.findById(request.projectId) .orElseThrow() else Project() + + val duplicateProject = + if (request.projectCode != null) projectRepository.findByCode(request.projectCode) else null + + //check duplicate project + if (duplicateProject != null && !duplicateProject.deleted && duplicateProject.id?.equals(request.projectId) == false) { + return NewProjectResponse( + id = request.projectId, + code = request.projectCode, + name = request.projectName, + client = null, + category = null, + team = null, + message = "The project code has already existed", + errorPosition = "projectCode" + ); + } + project.apply { name = request.projectName description = request.projectDescription - code = request.projectCode - ?: 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 + code = if (request.mainProjectId != null && mainProject != null) createSubProjectCode( + mainProject, + project + ) else request.projectCode expectedTotalFee = request.expectedProjectFee subContractFee = request.subContractFee totalManhour = request.totalManhour @@ -305,7 +325,8 @@ open class ProjectsService( val milestonesToDelete = milestoneRepository.findAllByProject(project).subtract(milestones.toSet()) val mapTasksToSave = tasksToSave.map { it.task!!.id } - val tasksToDelete = projectTaskRepository.findAllByProject(project).filter { !mapTasksToSave.contains(it.task!!.id) } + val tasksToDelete = + projectTaskRepository.findAllByProject(project).filter { !mapTasksToSave.contains(it.task!!.id) } val gradeAllocationsToDelete = gradeAllocationRepository.findByProject(project).subtract(gradeAllocations.toSet()) milestoneRepository.deleteAll(milestonesToDelete) @@ -336,7 +357,9 @@ open class ProjectsService( name = it.name, client = it.customer?.name, category = it.projectCategory?.name, - team = it.teamLead?.team?.code + team = it.teamLead?.team?.code, + message = "Success", + errorPosition = null, ) } } @@ -523,14 +546,15 @@ open class ProjectsService( val splitMainProjectCode = splitProjectCode[0].split('-') logger.info("splitProjectCode: " + splitProjectCode[1].split(')')[0]) + val mainProjectCode = splitMainProjectCode[0] + '-' + String.format( + "%04d", + splitMainProjectCode[1].toInt() + ) projectCode = - splitMainProjectCode[0] + '-' + String.format( - "%04d", - splitMainProjectCode[1].toInt() - ) + '-' + String.format("%03d", splitProjectCode[1].split(')')[0].toInt()) + mainProjectCode + '-' + String.format("%03d", splitProjectCode[1].split(')')[0].toInt()) val mainProject = - projectRepository.findByCode(splitProjectCode[0]) ?: projectRepository.saveAndFlush( + projectRepository.findByCode(mainProjectCode) ?: projectRepository.saveAndFlush( Project().apply { name = row.getCell(1).stringCellValue description = row.getCell(1).stringCellValue @@ -642,7 +666,8 @@ open class ProjectsService( keySelector = { it.taskGroup!!.id!! }, valueTransform = { it.percentage!! } ) - val projectTasks = if (project != null) projectTaskRepository.findAllByProject(project) else mutableListOf() + val projectTasks = + if (project != null) projectTaskRepository.findAllByProject(project) else mutableListOf() var groupedProjectTasks = mapOf>() if (projectTasks.isNotEmpty()) { groupedProjectTasks = projectTasks.groupBy { @@ -652,7 +677,7 @@ open class ProjectsService( } } val taskGroups = mutableMapOf() - groups.entries.forEach{ (key, value) -> + groups.entries.forEach { (key, value) -> var taskIds = tasks[key]!!.map { it.id!! } if (groupedProjectTasks.isNotEmpty() && !groupedProjectTasks[key].isNullOrEmpty()) { taskIds = (taskIds + groupedProjectTasks[key]!!).distinct() diff --git a/src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectResponse.kt b/src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectResponse.kt index 0043d7d..79145e3 100644 --- a/src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectResponse.kt +++ b/src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectResponse.kt @@ -7,4 +7,6 @@ data class NewProjectResponse( val category: String?, val team: String?, val client: String?, + val message: String?, + val errorPosition: String?, ) 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 7224a7e..19a3a83 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,6 +1,7 @@ package com.ffii.tsms.modules.report.service import com.ffii.core.support.JdbcDao +import com.ffii.core.utils.ExcelUtils import com.ffii.tsms.modules.data.entity.Customer import com.ffii.tsms.modules.data.entity.Grade import com.ffii.tsms.modules.data.entity.Salary @@ -59,6 +60,20 @@ open class ReportService( private val COMPLETION_PROJECT = "templates/report/AR05_Project Completion Report.xlsx" private val CROSS_TEAM_CHARGE_REPORT = "templates/report/Cross Team Charge Report.xlsx" + private fun conditionalFormattingNegative(sheet: Sheet) { + // Create a conditional formatting rule + val sheetCF = sheet.sheetConditionalFormatting + val ruleNegative = sheetCF.createConditionalFormattingRule(ComparisonOperator.LT, "0").apply { + createFontFormatting().apply { + fontColorIndex = IndexedColors.RED.index + } + } + + val lastCell = sheet.maxOf { it.lastCellNum - 1 } + val regions = arrayOf(CellRangeAddress(0, sheet.lastRowNum, 0, lastCell)) + sheetCF.addConditionalFormatting(regions, ruleNegative) + } + // ==============================|| GENERATE REPORT ||============================== // fun generalCreateReportIndexed( // just loop through query records one by one, return rowIndex sheet: Sheet, @@ -655,6 +670,8 @@ open class ReportService( cellStyle.dataFormat = accountingStyle } + conditionalFormattingNegative(sheet) + return workbook } @@ -760,6 +777,7 @@ open class ReportService( cellStyle.dataFormat = accountingStyle } } + var regions = arrayOf(CellRangeAddress(12, 12, 1, 2)) rowIndex = 16 @@ -814,7 +832,7 @@ open class ReportService( combinedResults.forEach { result: String -> if (groupedInvoices.containsKey(result)) { - groupedInvoices[result]!!.forEachIndexed { _, invoice -> +// groupedInvoices[result]!!.forEachIndexed { _, invoice -> sheet.createRow(rowIndex++).apply { createCell(0).apply { setCellValue(result) @@ -826,7 +844,7 @@ open class ReportService( } createCell(2).apply { - setCellValue(invoice["paidAmount"] as Double) + setCellValue(groupedInvoices[result]?.sumOf { it["paidAmount"] as Double } ?: 0.0) cellStyle.dataFormat = accountingStyle } @@ -847,9 +865,9 @@ open class ReportService( createCell(4)?.apply { // setCellValue(invoice["description"].toString()) - setCellValue("Invoice Receipt: " + (invoice["invoiceNo"] ?: "N/A").toString()) + setCellValue("Invoice Receipt: " + (groupedInvoices[result]?.map { it["invoiceNo"] }?.joinToString() ?: "N/A").toString()) } - } +// } } } @@ -892,6 +910,8 @@ open class ReportService( } + conditionalFormattingNegative(sheet) + return workbook } @@ -1053,6 +1073,8 @@ open class ReportService( } } + conditionalFormattingNegative(sheet) + return workbook } @@ -1348,6 +1370,9 @@ open class ReportService( CellUtil.setCellStyleProperties(tempCell, DoubleBorderBottom) } } + + conditionalFormattingNegative(sheet) + return workbook } @@ -1403,6 +1428,8 @@ open class ReportService( } // println(salarys.size) + conditionalFormattingNegative(sheet) + return workbook } @@ -1435,6 +1462,9 @@ open class ReportService( columnIndex = 0 generalCreateReportIndexed(sheet, result.distinct(), rowIndex, columnIndex) + + conditionalFormattingNegative(sheet) + return workbook } @@ -1492,6 +1522,8 @@ open class ReportService( val regions = arrayOf(CellRangeAddress.valueOf("\$K7:\$L${rowIndex + 1}")) sheetCF.addConditionalFormatting(regions, cfRules); + conditionalFormattingNegative(sheet) + return workbook } @@ -1526,6 +1558,8 @@ open class ReportService( // Automatically adjust column widths to fit content autoSizeColumns(sheet) + conditionalFormattingNegative(sheet) + return workbook } @@ -2416,6 +2450,8 @@ open class ReportService( CellUtil.setCellStyleProperty(panlCellTitle, "borderBottom", BorderStyle.DOUBLE) CellUtil.setCellStyleProperty(panlCell, "borderBottom", BorderStyle.DOUBLE) + conditionalFormattingNegative(sheet) + return workbook } @@ -2650,6 +2686,8 @@ open class ReportService( sheet.setRowBreak(rowNum++); } + conditionalFormattingNegative(sheet) + return workbook } @@ -2961,6 +2999,8 @@ open class ReportService( } // } + conditionalFormattingNegative(sheet) + return workbook } } \ No newline at end of file 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 fd6b347..93e7643 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 @@ -90,7 +90,7 @@ class ReportController( // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") return ResponseEntity.ok() // .contentType(mediaType) - .header("filename", "Project Cash Flow Report - " + LocalDate.now() + ".xlsx") + .header("filename", "Project Cash Flow Report (${if(request.dateType == "Date") "by Date" else "by Month"}) - " + LocalDate.now() + ".xlsx") .body(ByteArrayResource(reportResult)) } diff --git a/src/main/java/com/ffii/tsms/modules/user/service/GroupService.java b/src/main/java/com/ffii/tsms/modules/user/service/GroupService.java index d0fb138..42e11aa 100644 --- a/src/main/java/com/ffii/tsms/modules/user/service/GroupService.java +++ b/src/main/java/com/ffii/tsms/modules/user/service/GroupService.java @@ -182,7 +182,7 @@ public class GroupService extends AbstractBaseEntityService