@@ -3,10 +3,12 @@ package com.ffii.tsms.config.security.jwt.web; | |||
import java.time.Instant; | |||
import java.util.HashSet; | |||
import java.util.Map; | |||
import java.util.Objects; | |||
import java.util.Set; | |||
import com.ffii.tsms.modules.data.entity.Staff; | |||
import com.ffii.tsms.modules.data.entity.StaffRepository; | |||
import com.ffii.tsms.modules.data.service.StaffsService; | |||
import com.ffii.tsms.modules.user.service.GroupService; | |||
import org.apache.commons.lang3.exception.ExceptionUtils; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
@@ -58,6 +60,9 @@ public class JwtAuthenticationController { | |||
@Autowired | |||
private GroupService groupService; | |||
@Autowired | |||
private StaffsService staffsService; | |||
@Autowired | |||
private StaffRepository staffRepository; | |||
@@ -102,7 +107,7 @@ public class JwtAuthenticationController { | |||
final Map<String, Object> args = Map.of("userId", user.getId()); | |||
final String role = groupService.getGroupName(args); | |||
final Staff staff = staffRepository.findIdAndNameByUserIdAndDeletedFalse(user.getId()).orElse(null); | |||
final Map<String, Object> staff = Objects.requireNonNull(staffsService.getCurrentStaff(user.getId())).orElse(null); | |||
Set<AbilityModel> abilities = new HashSet<>(); | |||
userAuthorityService.getUserAuthority(user).forEach(auth -> abilities.add(new AbilityModel(auth.getAuthority()))); | |||
@@ -1,6 +1,7 @@ | |||
package com.ffii.tsms.model; | |||
import java.io.Serializable; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import com.ffii.tsms.modules.data.entity.Staff; | |||
@@ -16,11 +17,9 @@ public class JwtResponse implements Serializable { | |||
private final String refreshToken; | |||
private final String role; | |||
private final Set<AbilityModel> abilities; | |||
private final Staff staff; | |||
private final Map<String, Object> staff; | |||
public JwtResponse(String accessToken, String refreshToken, String role, User user, Set<AbilityModel> abilities, Staff staff) { | |||
public JwtResponse(String accessToken, String refreshToken, String role, User user, Set<AbilityModel> abilities, Map<String, Object> staff) { | |||
this.accessToken = accessToken; | |||
this.refreshToken = refreshToken; | |||
this.role = role; | |||
@@ -55,7 +54,7 @@ public class JwtResponse implements Serializable { | |||
return email; | |||
} | |||
public Staff getStaff() { return staff; } | |||
public Map<String, Object> getStaff() { return staff; } | |||
public Set<AbilityModel> getAbilities() { | |||
@@ -93,6 +93,27 @@ open class StaffsService( | |||
return jdbcDao.queryForMap(sql.toString(), args) | |||
} | |||
open fun getCurrentStaff(userId: Long): Optional<MutableMap<String, Any>>? { | |||
val staff = staffRepository.findByUserId(userId).orElse(null) | |||
logger.info(staff) | |||
if (staff == null) { | |||
return Optional.ofNullable(null) | |||
} | |||
val sql = StringBuilder("select " + | |||
" s.id as id, " + | |||
" t.id as teamId, " + | |||
" case when t.teamLead = s.id then true else false end as isTeamLead " + | |||
" from staff s " + | |||
" left join team t on t.id = s.teamId " + | |||
" where s.deleted = false " | |||
+ " and s.id = " + staff.id | |||
) | |||
return jdbcDao.queryForMap(sql.toString()) | |||
} | |||
@Transactional(rollbackFor = [Exception::class]) | |||
open fun saveStaff(req: NewStaffRequest): Staff { | |||
val checkStaffIdList: List<StaffSearchInfo> = staffRepository.findStaffSearchInfoByAndDeletedFalse() | |||
@@ -3,10 +3,12 @@ package com.ffii.tsms.modules.project.entity; | |||
import com.ffii.core.support.AbstractRepository | |||
import com.ffii.tsms.modules.data.entity.Staff | |||
import com.ffii.tsms.modules.data.entity.projections.StaffSearchInfo | |||
import org.springframework.data.jpa.repository.Query | |||
import java.time.LocalDate | |||
interface StaffAllocationRepository : AbstractRepository<StaffAllocation, Long> { | |||
fun findAssignedProjectsByStaff(staff: Staff): List<StaffAllocation> | |||
@Query("SELECT sa.project FROM StaffAllocation sa WHERE sa.staff = ?1 AND sa.project.status = 'On-going'") | |||
fun findOnGoingAssignedProjectsByStaff(staff: Staff): List<Project> | |||
fun findByProject(project: Project): List<StaffAllocation> | |||
} |
@@ -3,4 +3,5 @@ package com.ffii.tsms.modules.project.entity; | |||
import com.ffii.core.support.AbstractRepository | |||
interface TaskTemplateRepository : AbstractRepository<TaskTemplate, Long> { | |||
fun findByCode(code: String): TaskTemplate? | |||
} |
@@ -75,29 +75,27 @@ open class ProjectsService( | |||
open fun allAssignedProjects(): List<AssignedProject> { | |||
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.findOnGoingAssignedProjectsByStaff(staff).map { 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() | |||
@@ -108,6 +106,7 @@ open class ProjectsService( | |||
ProjectWithTasks(id = project.id!!, | |||
code = project.code!!, | |||
name = project.name!!, | |||
status = project.status, | |||
tasks = projectTaskRepository.findAllByProject(project).mapNotNull { pt -> pt.task }, | |||
milestones = milestoneRepository.findAllByProject(project) | |||
.filter { milestone -> milestone.taskGroup?.id != null } | |||
@@ -3,10 +3,7 @@ package com.ffii.tsms.modules.project.service | |||
import com.ffii.core.support.JdbcDao | |||
import com.ffii.tsms.modules.data.entity.GradeRepository | |||
import com.ffii.tsms.modules.project.entity.* | |||
import com.ffii.tsms.modules.project.web.models.EditTaskTemplateDetails | |||
import com.ffii.tsms.modules.project.web.models.NewProjectRequest | |||
import com.ffii.tsms.modules.project.web.models.NewTaskTemplateRequest | |||
import com.ffii.tsms.modules.project.web.models.TaskGroupAllocation | |||
import com.ffii.tsms.modules.project.web.models.* | |||
import org.springframework.stereotype.Service | |||
import kotlin.jvm.optionals.getOrNull | |||
@@ -36,6 +33,10 @@ class TasksService( | |||
taskTemplateRepository.deleteById(id) | |||
} | |||
fun findTaskTemplateByCode(code: String): TaskTemplate? { | |||
return taskTemplateRepository.findByCode(code) | |||
} | |||
fun getTaskTemplateDetails(id: Long): EditTaskTemplateDetails? { | |||
val taskTemplate = taskTemplateRepository.findById(id) | |||
@@ -63,8 +64,19 @@ class TasksService( | |||
} | |||
} | |||
fun saveTaskTemplate(request: NewTaskTemplateRequest): TaskTemplate { | |||
fun saveTaskTemplate(request: NewTaskTemplateRequest): NewTaskTemplateResponse { | |||
val id = request.id | |||
val duplicateTaskTemplate = findTaskTemplateByCode(request.code) | |||
//check duplicate customer | |||
if (duplicateTaskTemplate != null && duplicateTaskTemplate.id?.equals(id) == false) { | |||
return NewTaskTemplateResponse( | |||
taskTemplate = duplicateTaskTemplate, | |||
message = "The task template code has already existed" | |||
); | |||
} | |||
val taskTemplate = if (id != null && id > 0) findTaskTemplate(id) else TaskTemplate() | |||
taskTemplate.apply { | |||
this.name = request.name | |||
@@ -96,17 +108,13 @@ class TasksService( | |||
val savedTaskTemplate = taskTemplateRepository.save<TaskTemplate>(taskTemplate) | |||
println(taskTemplateGroupAllocationRepository.findAllByTaskTemplate(taskTemplate).size) | |||
println(groupAllocations.size) | |||
println(taskTemplateGroupAllocationRepository.findAllByTaskTemplate(taskTemplate).subtract(gradeAllocations.toSet()).size) | |||
taskTemplateGradeAllocationRepository.deleteAll(taskTemplateGradeAllocationRepository.findAllByTaskTemplate(taskTemplate).subtract(gradeAllocations.toSet())) | |||
taskTemplateGroupAllocationRepository.deleteAll(taskTemplateGroupAllocationRepository.findAllByTaskTemplate(taskTemplate).subtract(groupAllocations.toSet())) | |||
taskTemplateGradeAllocationRepository.saveAll<TaskTemplateGradeAllocation>(gradeAllocations) | |||
taskTemplateGroupAllocationRepository.saveAll<TaskTemplateGroupAllocation>(groupAllocations) | |||
return savedTaskTemplate | |||
return NewTaskTemplateResponse(taskTemplate = savedTaskTemplate, message = "Success"); | |||
} | |||
fun allTaskGroups(): List<TaskGroup> { | |||
@@ -6,6 +6,7 @@ import com.ffii.tsms.modules.project.entity.TaskTemplate | |||
import com.ffii.tsms.modules.project.service.TasksService | |||
import com.ffii.tsms.modules.project.web.models.EditTaskTemplateDetails | |||
import com.ffii.tsms.modules.project.web.models.NewTaskTemplateRequest | |||
import com.ffii.tsms.modules.project.web.models.NewTaskTemplateResponse | |||
import jakarta.validation.Valid | |||
import org.springframework.http.HttpStatus | |||
import org.springframework.web.bind.annotation.* | |||
@@ -40,7 +41,7 @@ class TasksController(private val tasksService: TasksService) { | |||
} | |||
@PostMapping("/templates/save") | |||
fun saveTaskTemplate(@Valid @RequestBody newTaskTemplate: NewTaskTemplateRequest): TaskTemplate { | |||
fun saveTaskTemplate(@Valid @RequestBody newTaskTemplate: NewTaskTemplateRequest): NewTaskTemplateResponse { | |||
return tasksService.saveTaskTemplate(newTaskTemplate) | |||
} | |||
} |
@@ -0,0 +1,8 @@ | |||
package com.ffii.tsms.modules.project.web.models | |||
import com.ffii.tsms.modules.project.entity.TaskTemplate | |||
data class NewTaskTemplateResponse ( | |||
val taskTemplate: TaskTemplate, | |||
val message: String, | |||
) |
@@ -5,6 +5,7 @@ import com.ffii.tsms.modules.project.entity.Task | |||
data class ProjectWithTasks( | |||
val id: Long, | |||
val code: String, | |||
val status: String?, | |||
val name: String, | |||
val tasks: List<Task>, | |||
val milestones: Map<Long, MilestoneInfo> | |||
@@ -1721,9 +1721,11 @@ open class ReportService( | |||
+ " result.code, " | |||
+ " result.name, " | |||
+ " result.teamCode, " | |||
+ " result.custCode, " ) | |||
+ " result.custCode, " | |||
+ " result.subCode, " ) | |||
if (args.get("outstanding") as Boolean) { | |||
sql.append(" result.projectFee - (result.totalBudget - COALESCE(i.issueAmount , 0) + COALESCE(i.issueAmount, 0) - COALESCE(i.paidAmount, 0)) as `Receivable Remained`, ") | |||
sql.append(" result.projectFee - COALESCE(i.paidAmount, 0) as `Receivable Remained`, ") | |||
// sql.append(" result.projectFee - (result.totalBudget - COALESCE(i.issueAmount , 0) + COALESCE(i.issueAmount, 0) - COALESCE(i.paidAmount, 0)) as `Receivable Remained`, ") | |||
} | |||
sql.append( | |||
" DATE_FORMAT(result.actualEnd, '%d/%m/%Y') as actualEnd " | |||
@@ -1734,6 +1736,7 @@ open class ReportService( | |||
+ " min(p.name) as name, " | |||
+ " min(t.code) as teamCode, " | |||
+ " min(c.code) as custCode, " | |||
+ " COALESCE(concat(min(ss.code),' - ',min(ss.name)), 'N/A') as subCode, " | |||
+ " min(p.actualEnd) as actualEnd, " | |||
+ " min(p.expectedTotalFee) as projectFee, " | |||
+ " sum(COALESCE(tns.totalConsumed*sal.hourlyRate, 0)) as totalBudget " | |||
@@ -1754,14 +1757,18 @@ open class ReportService( | |||
+ " left join salary sal on s.salaryId = sal.salaryPoint " | |||
+ " left JOIN team t ON s.teamId = t.id " | |||
+ " left join customer c on c.id = p.customerId " | |||
+ " where p.deleted = false " | |||
+ " and p.status = 'Completed' " | |||
+ " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id " | |||
+ " where p.deleted = false ") | |||
if (args.containsKey("teamId")) { | |||
sql.append("t.id = :teamId") | |||
} | |||
sql.append( | |||
" and p.status = 'Completed' " | |||
+ " and p.actualEnd BETWEEN :startDate and :endDate " | |||
+ " group by pt.project_id " | |||
+ " ) as result " | |||
+ " left join invoice i on result.code = i.projectCode " | |||
+ " order by result.actualEnd " | |||
) | |||
+ " order by result.actualEnd ") | |||
return jdbcDao.queryForList(sql.toString(), args) | |||
} | |||
@@ -1794,7 +1801,7 @@ open class ReportService( | |||
+ " p.name, " | |||
+ " t.code as team, " | |||
+ " c.code as client, " | |||
+ " COALESCE(ss.name, 'N/A') as subsidiary, " | |||
+ " COALESCE(ss.code, 'N/A') as subsidiary, " | |||
+ " p.expectedTotalFee * 0.8 as plannedBudget, " | |||
+ " COALESCE(tns.totalBudget, 0) as actualConsumedBudget, " | |||
+ " COALESCE(p.totalManhour, 0) as plannedManhour, " | |||
@@ -2,9 +2,11 @@ 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.common.SecurityUtils | |||
import com.ffii.tsms.modules.data.entity.* | |||
//import com.ffii.tsms.modules.data.entity.projections.FinancialStatusReportInfo | |||
import com.ffii.tsms.modules.data.service.CustomerService | |||
import com.ffii.tsms.modules.data.service.StaffsService | |||
import com.ffii.tsms.modules.data.service.TeamService | |||
import com.ffii.tsms.modules.project.entity.* | |||
import com.ffii.tsms.modules.project.service.InvoiceService | |||
@@ -48,6 +50,7 @@ class ReportController( | |||
private val customerRepository: CustomerRepository, | |||
private val staffRepository: StaffRepository, | |||
private val leaveRepository: LeaveRepository, | |||
private val staffsService: StaffsService, | |||
private val teamService: TeamService, | |||
private val customerService: CustomerService, | |||
private val subsidiaryService: SubsidiaryService, | |||
@@ -184,6 +187,9 @@ class ReportController( | |||
"endDate" to request.endDate, | |||
"outstanding" to request.outstanding | |||
) | |||
if (request.teamId != null) { | |||
args["teamId"] = request.teamId | |||
} | |||
val result = excelReportService.getProjectCompletionReport(args); | |||
val reportResult: ByteArray = excelReportService.generateProjectCompletionReport(args, result) | |||
// val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") | |||
@@ -52,6 +52,7 @@ data class ProjectResourceOverconsumptionReport ( | |||
val lowerLimit: Double | |||
) | |||
data class ProjectCompletionReport ( | |||
val teamId: Long?, | |||
val startDate: LocalDate, | |||
val endDate: LocalDate, | |||
val outstanding: Boolean |