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