Ver código fonte

update staff, project, report

tags/Baseline_30082024_BACKEND_UAT
cyril.tsui 1 ano atrás
pai
commit
e5cd79f15d
10 arquivos alterados com 109 adições e 34 exclusões
  1. +2
    -2
      src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java
  2. +3
    -3
      src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt
  3. +1
    -0
      src/main/java/com/ffii/tsms/modules/project/entity/Project.kt
  4. +3
    -3
      src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt
  5. +45
    -20
      src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt
  6. +2
    -0
      src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectResponse.kt
  7. +44
    -4
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  8. +1
    -1
      src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt
  9. +1
    -1
      src/main/java/com/ffii/tsms/modules/user/service/GroupService.java
  10. +7
    -0
      src/main/resources/db/changelog/changes/20240708_01_cyril/01_update_quthority.sql

+ 2
- 2
src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java Ver arquivo

@@ -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);


+ 3
- 3
src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt Ver arquivo

@@ -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")


+ 1
- 0
src/main/java/com/ffii/tsms/modules/project/entity/Project.kt Ver arquivo

@@ -20,6 +20,7 @@ open class Project : BaseEntity<Long>() {

@NotNull
@Column(name = "code", length = 30)
@OrderBy("code asc")
open var code: String? = null

@ManyToOne


+ 3
- 3
src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt Ver arquivo

@@ -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?


+ 45
- 20
src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt Ver arquivo

@@ -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()


+ 2
- 0
src/main/java/com/ffii/tsms/modules/project/web/models/NewProjectResponse.kt Ver arquivo

@@ -7,4 +7,6 @@ data class NewProjectResponse(
val category: String?,
val team: String?,
val client: String?,
val message: String?,
val errorPosition: String?,
)

+ 44
- 4
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt Ver arquivo

@@ -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
}
}

+ 1
- 1
src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt Ver arquivo

@@ -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))
}



+ 1
- 1
src/main/java/com/ffii/tsms/modules/user/service/GroupService.java Ver arquivo

@@ -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(","));
}




+ 7
- 0
src/main/resources/db/changelog/changes/20240708_01_cyril/01_update_quthority.sql Ver arquivo

@@ -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);

Carregando…
Cancelar
Salvar