| @@ -11,8 +11,8 @@ import java.util.Map; | |||
| import java.util.Optional; | |||
| public interface StaffRepository extends AbstractRepository<Staff, Long> { | |||
| List<StaffSearchInfo> findStaffSearchInfoByAndDeletedFalse(); | |||
| List<StaffSearchInfo> findStaffSearchInfoByAndDeletedFalseAndTeamIdIsNull(); | |||
| List<StaffSearchInfo> findStaffSearchInfoByAndDeletedFalseOrderByStaffIdAsc(); | |||
| List<StaffSearchInfo> findStaffSearchInfoByAndDeletedFalseAndTeamIdIsNullOrderByStaffIdAsc(); | |||
| List<StaffSearchInfo> findAllStaffSearchInfoByIdIn(List<Serializable> ids); | |||
| Optional<Staff> findByStaffId(@Param("staffId") String staffId); | |||
| @@ -41,10 +41,10 @@ open class StaffsService( | |||
| } | |||
| open fun allStaff(): List<StaffSearchInfo> { | |||
| return staffRepository.findStaffSearchInfoByAndDeletedFalse(); | |||
| return staffRepository.findStaffSearchInfoByAndDeletedFalseOrderByStaffIdAsc(); | |||
| } | |||
| open fun StaffWithoutTeam(): List<StaffSearchInfo> { | |||
| 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<StaffSearchInfo> = staffRepository.findStaffSearchInfoByAndDeletedFalse() | |||
| val checkStaffIdList: List<StaffSearchInfo> = staffRepository.findStaffSearchInfoByAndDeletedFalseOrderByStaffIdAsc() | |||
| checkStaffIdList.forEach{ s -> | |||
| if (s.staffId == req.staffId) { | |||
| throw UnprocessableEntityException("Duplicated StaffId Found") | |||
| @@ -20,6 +20,7 @@ open class Project : BaseEntity<Long>() { | |||
| @NotNull | |||
| @Column(name = "code", length = 30) | |||
| @OrderBy("code asc") | |||
| open var code: String? = null | |||
| @ManyToOne | |||
| @@ -12,7 +12,7 @@ import java.io.Serializable | |||
| import java.time.LocalDate | |||
| interface ProjectRepository : AbstractRepository<Project, Long> { | |||
| fun findProjectSearchInfoByOrderByCreatedDesc(): List<ProjectSearchInfo> | |||
| fun findProjectSearchInfoByDeletedIsFalseOrderByCodeDesc(): List<ProjectSearchInfo> | |||
| fun findInvoiceSearchInfoBy(): List<InvoiceSearchInfo> | |||
| @@ -24,9 +24,9 @@ interface ProjectRepository : AbstractRepository<Project, Long> { | |||
| fun findAllByPlanStartLessThanEqualAndPlanEndGreaterThanEqual(remainedDateFrom: LocalDate?, remainedDateTo: LocalDate?):List<Project> | |||
| @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? | |||
| @@ -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<ProjectSearchInfo> { | |||
| return projectRepository.findProjectSearchInfoByOrderByCreatedDesc() | |||
| .sortedByDescending { it.status?.lowercase() != "deleted" } | |||
| return projectRepository.findProjectSearchInfoByDeletedIsFalseOrderByCodeDesc() | |||
| } | |||
| open fun allInvoices(): List<InvoiceSearchInfo> { | |||
| @@ -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<Long, List<Long>>() | |||
| if (projectTasks.isNotEmpty()) { | |||
| groupedProjectTasks = projectTasks.groupBy { | |||
| @@ -652,7 +677,7 @@ open class ProjectsService( | |||
| } | |||
| } | |||
| val taskGroups = mutableMapOf<Long, TaskGroupAllocation>() | |||
| 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() | |||
| @@ -7,4 +7,6 @@ data class NewProjectResponse( | |||
| val category: String?, | |||
| val team: String?, | |||
| val client: String?, | |||
| val message: String?, | |||
| val errorPosition: String?, | |||
| ) | |||
| @@ -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 | |||
| } | |||
| @@ -1368,25 +1393,43 @@ open class ReportService( | |||
| salarys.forEachIndexed { index, salary -> | |||
| sheet.getRow(rowIndex++).apply { | |||
| val row = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex) | |||
| row?.apply { | |||
| getCell(0).setCellValue(salary.salaryPoint.toDouble()) | |||
| val cell = getCell(0) ?: createCell(0) | |||
| cell.setCellValue(salary.salaryPoint.toDouble()) | |||
| when (index) { | |||
| 0 -> getCell(1).setCellValue(salary.lowerLimit.toDouble()) | |||
| 0 -> { | |||
| val cell1 = getCell(1) ?: createCell(1) | |||
| cell1.setCellValue(salary.lowerLimit.toDouble()) | |||
| } | |||
| else -> getCell(1).cellFormula = | |||
| "(C{previousRow}+1)".replace("{previousRow}", (rowIndex - 1).toString()) | |||
| else -> { | |||
| val cell1 = getCell(1) ?: createCell(1) | |||
| cell1.cellFormula = | |||
| "(C{previousRow}+1)".replace("{previousRow}", (rowIndex).toString()) | |||
| } | |||
| } | |||
| getCell(2).cellFormula = "(B{currentRow}+D{currentRow})-1".replace("{currentRow}", rowIndex.toString()) | |||
| val cell2 = getCell(2) ?: createCell(2) | |||
| cell2.cellFormula = "(B{currentRow}+D{currentRow})-1".replace("{currentRow}", (rowIndex+1).toString()) | |||
| // getCell(2).cellStyle.dataFormat = accountingStyle | |||
| getCell(3).setCellValue(salary.increment.toDouble()) | |||
| getCell(4).cellFormula = | |||
| "(((C{currentRow}+B{currentRow})/2)/20)/8".replace("{currentRow}", rowIndex.toString()) | |||
| val cell3 = getCell(3)?:createCell(3) | |||
| cell3.setCellValue(salary.increment.toDouble()) | |||
| val cell4 = getCell(4)?:createCell(4) | |||
| cell4.cellFormula = | |||
| "(((C{currentRow}+B{currentRow})/2)/20)/8".replace("{currentRow}", (rowIndex+1).toString()) | |||
| // getCell(4).cellStyle.dataFormat = accountingStyle | |||
| cell4.cellStyle.dataFormat = accountingStyle | |||
| } | |||
| rowIndex++; | |||
| } | |||
| // println(salarys.size) | |||
| conditionalFormattingNegative(sheet) | |||
| return workbook | |||
| } | |||
| @@ -1419,6 +1462,9 @@ open class ReportService( | |||
| columnIndex = 0 | |||
| generalCreateReportIndexed(sheet, result.distinct(), rowIndex, columnIndex) | |||
| conditionalFormattingNegative(sheet) | |||
| return workbook | |||
| } | |||
| @@ -1476,6 +1522,8 @@ open class ReportService( | |||
| val regions = arrayOf(CellRangeAddress.valueOf("\$K7:\$L${rowIndex + 1}")) | |||
| sheetCF.addConditionalFormatting(regions, cfRules); | |||
| conditionalFormattingNegative(sheet) | |||
| return workbook | |||
| } | |||
| @@ -1510,6 +1558,8 @@ open class ReportService( | |||
| // Automatically adjust column widths to fit content | |||
| autoSizeColumns(sheet) | |||
| conditionalFormattingNegative(sheet) | |||
| return workbook | |||
| } | |||
| @@ -2396,6 +2446,8 @@ open class ReportService( | |||
| CellUtil.setCellStyleProperty(panlCellTitle, "borderBottom", BorderStyle.DOUBLE) | |||
| CellUtil.setCellStyleProperty(panlCell, "borderBottom", BorderStyle.DOUBLE) | |||
| conditionalFormattingNegative(sheet) | |||
| return workbook | |||
| } | |||
| @@ -2630,6 +2682,8 @@ open class ReportService( | |||
| sheet.setRowBreak(rowNum++); | |||
| } | |||
| conditionalFormattingNegative(sheet) | |||
| return workbook | |||
| } | |||
| @@ -2941,6 +2995,8 @@ open class ReportService( | |||
| } | |||
| // } | |||
| conditionalFormattingNegative(sheet) | |||
| return workbook | |||
| } | |||
| } | |||
| @@ -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)) | |||
| } | |||
| @@ -182,7 +182,7 @@ public class GroupService extends AbstractBaseEntityService<Group, Long, GroupRe | |||
| + " where g.deleted = false " | |||
| + " and u.id = :userId" | |||
| ); | |||
| return jdbcDao.queryForString(sql.toString(), args); | |||
| return jdbcDao.queryForList(sql.toString(), args).stream().map(String::valueOf).collect(Collectors.joining(",")); | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset cyril:authority, user_authority | |||
| INSERT INTO authority (authority,name) | |||
| VALUES ('VIEW_PROJECT_RESOURCE_CONSUMPTION_RANKING','View Project Resource Consumption Ranking in Dashboard'); | |||
| INSERT INTO `user_authority` VALUES (1,41); | |||