| @@ -1,6 +1,11 @@ | |||||
| package com.ffii.tsms.modules.data.entity; | package com.ffii.tsms.modules.data.entity; | ||||
| import com.ffii.core.support.AbstractRepository; | import com.ffii.core.support.AbstractRepository; | ||||
| import org.springframework.data.repository.query.Param; | |||||
| import java.util.List; | |||||
| public interface StaffSkillsetRepository extends AbstractRepository<StaffSkillset, Long> { | public interface StaffSkillsetRepository extends AbstractRepository<StaffSkillset, Long> { | ||||
| List<StaffSkillset> findByStaff(@Param("staff") Staff staff); | |||||
| } | } | ||||
| @@ -7,4 +7,6 @@ import java.util.List; | |||||
| public interface TeamRepository extends AbstractRepository<Team, Long> { | public interface TeamRepository extends AbstractRepository<Team, Long> { | ||||
| List<Team> findByDeletedFalse(); | List<Team> findByDeletedFalse(); | ||||
| Team findByStaff(Staff staff); | |||||
| } | } | ||||
| @@ -10,6 +10,7 @@ import com.ffii.tsms.modules.data.web.models.SaveCustomerResponse | |||||
| import com.ffii.tsms.modules.project.web.models.SaveCustomerRequest | import com.ffii.tsms.modules.project.web.models.SaveCustomerRequest | ||||
| import org.springframework.beans.BeanUtils | import org.springframework.beans.BeanUtils | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import java.math.BigDecimal | |||||
| import java.util.Optional | import java.util.Optional | ||||
| @Service | @Service | ||||
| @@ -23,9 +24,7 @@ open class DashboardService( | |||||
| fun CustomerSubsidiary(args: Map<String, Any>): List<Map<String, Any>> { | fun CustomerSubsidiary(args: Map<String, Any>): List<Map<String, Any>> { | ||||
| val sql = StringBuilder("select" | val sql = StringBuilder("select" | ||||
| + " row_number()OVER (" | |||||
| + " ORDER BY c.id" | |||||
| + " ) as id," | |||||
| + " ROW_NUMBER() OVER (ORDER BY c.id, c.name, c.code, c.address, c.district, c.brNo, c.typeId, s.id, s.name, s.code, s.address, s.district, s.brNo, s.typeId) AS RowNum," | |||||
| + " c.id as customerId," | + " c.id as customerId," | ||||
| + " c.name as customerName," | + " c.name as customerName," | ||||
| + " c.code as customerCode," | + " c.code as customerCode," | ||||
| @@ -45,6 +44,7 @@ open class DashboardService( | |||||
| + " left join project p on c.id = p.customerId" | + " left join project p on c.id = p.customerId" | ||||
| + " left join subsidiary s on p.customerSubsidiaryId = s.id" | + " left join subsidiary s on p.customerSubsidiaryId = s.id" | ||||
| + " where c.deleted = 0" | + " where c.deleted = 0" | ||||
| + " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")" | |||||
| ) | ) | ||||
| if (args != null) { | if (args != null) { | ||||
| if (args.containsKey("customerName")) | if (args.containsKey("customerName")) | ||||
| @@ -58,6 +58,7 @@ open class DashboardService( | |||||
| fun searchCustomerSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> { | fun searchCustomerSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> { | ||||
| val sql = StringBuilder("select" | val sql = StringBuilder("select" | ||||
| + " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id," | |||||
| + " p.id as id," | + " p.id as id," | ||||
| + " p.id as projectId," | + " p.id as projectId," | ||||
| + " p.code as projectCode," | + " p.code as projectCode," | ||||
| @@ -91,6 +92,7 @@ open class DashboardService( | |||||
| + " ) milestonePayment on 1=1" | + " ) milestonePayment on 1=1" | ||||
| + " where p.customerId = :customerId" | + " where p.customerId = :customerId" | ||||
| + " and p.customerSubsidiaryId = :subsidiaryId" | + " and p.customerSubsidiaryId = :subsidiaryId" | ||||
| + " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")" | |||||
| + " group by p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone" | + " group by p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone" | ||||
| ) | ) | ||||
| @@ -99,6 +101,7 @@ open class DashboardService( | |||||
| fun searchCustomerNonSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> { | fun searchCustomerNonSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> { | ||||
| val sql = StringBuilder("select" | val sql = StringBuilder("select" | ||||
| + " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id," | |||||
| + " p.id as id," | + " p.id as id," | ||||
| + " p.id as projectId," | + " p.id as projectId," | ||||
| + " p.code as projectCode," | + " p.code as projectCode," | ||||
| @@ -132,11 +135,94 @@ open class DashboardService( | |||||
| + " ) milestonePayment on 1=1" | + " ) milestonePayment on 1=1" | ||||
| + " where p.customerId = :customerId" | + " where p.customerId = :customerId" | ||||
| + " and isNull(p.customerSubsidiaryId)" | + " and isNull(p.customerSubsidiaryId)" | ||||
| + " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")" | |||||
| + " group by p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone" | + " group by p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone" | ||||
| ) | ) | ||||
| return jdbcDao.queryForList(sql.toString(), args) | return jdbcDao.queryForList(sql.toString(), args) | ||||
| } | } | ||||
| open fun getFinancialStatus(): List<Map<String, Any>> { | |||||
| val sql = StringBuilder( | |||||
| " with cte_timesheet as (" | |||||
| + " Select p.code, s.name as staff, IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate" | |||||
| + " from timesheet t" | |||||
| + " left join project_task pt on pt.id = t.projectTaskId" | |||||
| + " left join project p ON p.id = pt.project_id" | |||||
| + " left join staff s on s.id = t.staffId" | |||||
| + " left join salary s2 on s.salaryId = s2.salaryPoint" | |||||
| + " left join team t2 on t2.id = s.teamId" | |||||
| + " )," | |||||
| + " cte_invoice as (" | |||||
| + " select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount" | |||||
| + " from invoice i" | |||||
| + " left join project p on p.code = i.projectCode" | |||||
| + " group by p.code" | |||||
| + " )" | |||||
| + " select p.code, p.description, c.name as client, concat(t.code, \' - \', t.name) as teamLead, p.planStart , p.planEnd , p.expectedTotalFee," | |||||
| + " IFNULL(cte_ts.normalConsumed, 0) as normalConsumed, IFNULL(cte_ts.otConsumed, 0) as otConsumed," | |||||
| + " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate, IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount" | |||||
| + " from project p" | |||||
| + " left join cte_timesheet cte_ts on p.code = cte_ts.code" | |||||
| + " left join customer c on c.id = p.customerId" | |||||
| + " left join tsmsdb.team t on t.teamLead = p.teamLead" | |||||
| + " left join cte_invoice cte_i on cte_i.code = p.code" | |||||
| + " where p.status = \'On-going\'" | |||||
| ) | |||||
| sql.append(" order by p.code") | |||||
| return jdbcDao.queryForList(sql.toString()) | |||||
| } | |||||
| fun searchFinancialSummary(): List<Map<String, Any>> { | |||||
| val financialStatus: List<Map<String, Any>> = getFinancialStatus() | |||||
| val otFactor = BigDecimal(1) | |||||
| val tempList = mutableListOf<Map<String, Any>>() | |||||
| for (item in financialStatus) { | |||||
| val normalConsumed = item.getValue("normalConsumed") as Double | |||||
| val hourlyRate = item.getValue("hourlyRate") as BigDecimal | |||||
| // println("normalConsumed------------- $normalConsumed") | |||||
| // println("hourlyRate------------- $hourlyRate") | |||||
| val manHourRate = normalConsumed.toBigDecimal().multiply(hourlyRate) | |||||
| // println("manHourRate------------ $manHourRate") | |||||
| val otConsumed = item.getValue("otConsumed") as Double | |||||
| val manOtHourRate = otConsumed.toBigDecimal().multiply(hourlyRate).multiply(otFactor) | |||||
| if (!tempList.any { it.containsValue(item.getValue("code")) }) { | |||||
| tempList.add( | |||||
| mapOf( | |||||
| "code" to item.getValue("code"), | |||||
| "description" to item.getValue("description"), | |||||
| "client" to item.getValue("client"), | |||||
| "teamLead" to item.getValue("teamLead"), | |||||
| "planStart" to item.getValue("planStart"), | |||||
| "planEnd" to item.getValue("planEnd"), | |||||
| "expectedTotalFee" to item.getValue("expectedTotalFee"), | |||||
| "normalConsumed" to manHourRate, | |||||
| "otConsumed" to manOtHourRate, | |||||
| "issuedAmount" to item.getValue("sumIssuedAmount"), | |||||
| "paidAmount" to item.getValue("sumPaidAmount"), | |||||
| ) | |||||
| ) | |||||
| } else { | |||||
| // Find the existing Map in the tempList that has the same "code" value | |||||
| val existingMap = tempList.find { it.containsValue(item.getValue("code")) }!! | |||||
| // Update the existing Map with the new manHourRate and manOtHourRate values | |||||
| tempList[tempList.indexOf(existingMap)] = existingMap.toMutableMap().apply { | |||||
| put("normalConsumed", (get("normalConsumed") as BigDecimal).add(manHourRate)) | |||||
| put("otConsumed", (get("otConsumed") as BigDecimal).add(manOtHourRate)) | |||||
| } | |||||
| } | |||||
| } | |||||
| return tempList | |||||
| } | |||||
| } | } | ||||
| @@ -95,7 +95,6 @@ open class StaffsService( | |||||
| @Transactional(rollbackFor = [Exception::class]) | @Transactional(rollbackFor = [Exception::class]) | ||||
| open fun saveStaff(req: NewStaffRequest): Staff { | open fun saveStaff(req: NewStaffRequest): Staff { | ||||
| // if (req.staffId) | |||||
| val checkStaffIdList: List<StaffSearchInfo> = staffRepository.findStaffSearchInfoByAndDeletedFalse() | val checkStaffIdList: List<StaffSearchInfo> = staffRepository.findStaffSearchInfoByAndDeletedFalse() | ||||
| checkStaffIdList.forEach{ s -> | checkStaffIdList.forEach{ s -> | ||||
| if (s.staffId == req.staffId) { | if (s.staffId == req.staffId) { | ||||
| @@ -108,7 +107,6 @@ open class StaffsService( | |||||
| val grade = if (req.gradeId != null && req.gradeId > 0L) gradeRepository.findById(req.gradeId).orElseThrow() else null | val grade = if (req.gradeId != null && req.gradeId > 0L) gradeRepository.findById(req.gradeId).orElseThrow() else null | ||||
| val team = if (req.teamId != null && req.teamId > 0L) teamRepository.findById(req.teamId).orElseThrow() else null | val team = if (req.teamId != null && req.teamId > 0L) teamRepository.findById(req.teamId).orElseThrow() else null | ||||
| val salary = salaryRepository.findBySalaryPoint(req.salaryId).orElseThrow() | val salary = salaryRepository.findBySalaryPoint(req.salaryId).orElseThrow() | ||||
| // val salaryEffective = salaryEffectiveRepository.findById(req.salaryEffId).orElseThrow() | |||||
| val department = departmentRepository.findById(req.departmentId).orElseThrow() | val department = departmentRepository.findById(req.departmentId).orElseThrow() | ||||
| val user = userRepository.saveAndFlush( | val user = userRepository.saveAndFlush( | ||||
| @@ -117,7 +115,6 @@ open class StaffsService( | |||||
| password = passwordEncoder.encode("mms1234") | password = passwordEncoder.encode("mms1234") | ||||
| name = req.name | name = req.name | ||||
| phone1 = req.phone1 | phone1 = req.phone1 | ||||
| // phone2 = req.phone2 ?: null | |||||
| email = req.email ?: null | email = req.email ?: null | ||||
| } | } | ||||
| ) | ) | ||||
| @@ -142,7 +139,6 @@ open class StaffsService( | |||||
| this.company = company | this.company = company | ||||
| this.grade = grade | this.grade = grade | ||||
| this.team = team | this.team = team | ||||
| // this.skill = skill | |||||
| this.salary = salary | this.salary = salary | ||||
| this.department = department | this.department = department | ||||
| } | } | ||||
| @@ -158,31 +154,31 @@ open class StaffsService( | |||||
| staffSkillsetRepository.save(ss) | staffSkillsetRepository.save(ss) | ||||
| } | } | ||||
| } | } | ||||
| // val skillBatchInsertValues: MutableList<MutableMap<String, Any>> | |||||
| // if (!req.skillSetId.isNullOrEmpty()) { | |||||
| // skillBatchInsertValues = req.skillSetId.stream() | |||||
| // .map { skillId -> mutableMapOf<String, Any>(("staffId" to staff.id) as Pair<String, Any>, "skillId" to skillId) } | |||||
| // .collect(Collectors.toList()) | |||||
| // jdbcDao.batchUpdate( | |||||
| // "INSERT IGNORE INTO staff_skillset (staffId, skillId)" | |||||
| // + " VALUES (:staffId, :skillId)", | |||||
| // skillBatchInsertValues); | |||||
| // } | |||||
| salaryEffectiveService.saveSalaryEffective(staff.id!!, salary.id!!) | salaryEffectiveService.saveSalaryEffective(staff.id!!, salary.id!!) | ||||
| return staff | return staff | ||||
| } | } | ||||
| @Transactional(rollbackFor = [Exception::class]) | @Transactional(rollbackFor = [Exception::class]) | ||||
| open fun updateStaff(req: NewStaffRequest, staff: Staff): Staff { | open fun updateStaff(req: NewStaffRequest, staff: Staff): Staff { | ||||
| val args = java.util.Map.of<String, Any>("staffId", staff.id) | |||||
| if(!req.skillSetId.isNullOrEmpty()) { | |||||
| // remove all skills of the staff | |||||
| jdbcDao.executeUpdate("DELETE FROM staff_skillset WHERE staffId = :staffId", args); | |||||
| // add skiils | |||||
| for (skillId in req.skillSetId) { | |||||
| val skill = skillRepository.findById(skillId).orElseThrow() | |||||
| val ss = StaffSkillset().apply { | |||||
| this.staff = staff | |||||
| this.skill = skill | |||||
| } | |||||
| staffSkillsetRepository.save(ss) | |||||
| } | |||||
| } | |||||
| val currentPosition = positionRepository.findById(req.currentPositionId).orElseThrow() | val currentPosition = positionRepository.findById(req.currentPositionId).orElseThrow() | ||||
| val joinPosition = positionRepository.findById(req.joinPositionId).orElseThrow() | val joinPosition = positionRepository.findById(req.joinPositionId).orElseThrow() | ||||
| val company = companyRepository.findById(req.companyId).orElseThrow() | val company = companyRepository.findById(req.companyId).orElseThrow() | ||||
| val grade = if (req.gradeId != null && req.gradeId > 0L) gradeRepository.findById(req.gradeId).orElseThrow() else null | val grade = if (req.gradeId != null && req.gradeId > 0L) gradeRepository.findById(req.gradeId).orElseThrow() else null | ||||
| val team = if (req.teamId != null && req.teamId > 0L) teamRepository.findById(req.teamId).orElseThrow() else null | val team = if (req.teamId != null && req.teamId > 0L) teamRepository.findById(req.teamId).orElseThrow() else null | ||||
| // val skill = if (req.skillSetId != null && req.skillSetId > 0L) skillRepository.findById(req.skillSetId).orElseThrow() else null | |||||
| val salary = salaryRepository.findById(req.salaryId).orElseThrow() | val salary = salaryRepository.findById(req.salaryId).orElseThrow() | ||||
| // val salaryEffective = salaryEffectiveRepository.findById(req.salaryEffId).orElseThrow() | |||||
| val department = departmentRepository.findById(req.departmentId).orElseThrow() | val department = departmentRepository.findById(req.departmentId).orElseThrow() | ||||
| staff.apply { | staff.apply { | ||||
| @@ -203,7 +199,6 @@ open class StaffsService( | |||||
| this.company = company | this.company = company | ||||
| this.grade = grade | this.grade = grade | ||||
| this.team = team | this.team = team | ||||
| // this.skill = skill | |||||
| this.salary = salary | this.salary = salary | ||||
| this.department = department | this.department = department | ||||
| } | } | ||||
| @@ -162,4 +162,18 @@ open class TeamService( | |||||
| ) | ) | ||||
| return jdbcDao.queryForList(sql.toString(), args) | return jdbcDao.queryForList(sql.toString(), args) | ||||
| } | } | ||||
| open fun combo2(): List<Map<String, Any>> { | |||||
| val sql = StringBuilder("select" | |||||
| + " t.teamLead as id," | |||||
| + " t.name, t.code " | |||||
| + " from team t" | |||||
| + " where t.deleted = false " | |||||
| ) | |||||
| return jdbcDao.queryForList(sql.toString()) | |||||
| } | |||||
| open fun getMyTeamForStaff(staff: Staff): Team? { | |||||
| return teamRepository.findByStaff(staff) | |||||
| } | |||||
| } | } | ||||
| @@ -69,4 +69,9 @@ class DashboardController( | |||||
| } | } | ||||
| return result | return result | ||||
| } | } | ||||
| @GetMapping("/searchFinancialSummary") | |||||
| fun searchFinancialSummary(): List<Map<String, Any>>{ | |||||
| return dashboardService.searchFinancialSummary() | |||||
| } | |||||
| } | } | ||||
| @@ -53,4 +53,10 @@ class TeamController(private val teamService: TeamService) { | |||||
| ) | ) | ||||
| ) | ) | ||||
| } | } | ||||
| @GetMapping("/combo2") | |||||
| @Throws(ServletRequestBindingException::class) | |||||
| fun combo2(): List<Map<String, Any>> { | |||||
| return teamService.combo2() | |||||
| } | |||||
| } | } | ||||
| @@ -9,6 +9,9 @@ import com.ffii.tsms.modules.project.entity.Invoice | |||||
| import com.ffii.tsms.modules.project.entity.Project | import com.ffii.tsms.modules.project.entity.Project | ||||
| import com.ffii.tsms.modules.timesheet.entity.Leave | import com.ffii.tsms.modules.timesheet.entity.Leave | ||||
| import com.ffii.tsms.modules.timesheet.entity.Timesheet | import com.ffii.tsms.modules.timesheet.entity.Timesheet | ||||
| import com.ffii.tsms.modules.timesheet.entity.projections.MonthlyLeave | |||||
| import com.ffii.tsms.modules.timesheet.entity.projections.ProjectMonthlyHoursWithDate | |||||
| import com.ffii.tsms.modules.timesheet.entity.projections.TimesheetHours | |||||
| import com.ffii.tsms.modules.timesheet.web.models.LeaveEntry | import com.ffii.tsms.modules.timesheet.web.models.LeaveEntry | ||||
| import org.apache.commons.logging.Log | import org.apache.commons.logging.Log | ||||
| import org.apache.commons.logging.LogFactory | import org.apache.commons.logging.LogFactory | ||||
| @@ -22,6 +25,7 @@ import org.springframework.stereotype.Service | |||||
| import java.io.ByteArrayOutputStream | import java.io.ByteArrayOutputStream | ||||
| import java.io.IOException | import java.io.IOException | ||||
| import java.math.BigDecimal | import java.math.BigDecimal | ||||
| import java.sql.Time | |||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| import java.util.* | import java.util.* | ||||
| @@ -44,9 +48,9 @@ open class ReportService( | |||||
| // ==============================|| GENERATE REPORT ||============================== // | // ==============================|| GENERATE REPORT ||============================== // | ||||
| fun genFinancialStatusReport(projectId: Long): ByteArray { | |||||
| fun genFinancialStatusReport(teamLeadId: Long): ByteArray { | |||||
| val financialStatus: List<Map<String, Any>> = getFinancialStatus(projectId) | |||||
| val financialStatus: List<Map<String, Any>> = getFinancialStatus(teamLeadId) | |||||
| val otFactor = BigDecimal(1) | val otFactor = BigDecimal(1) | ||||
| @@ -55,10 +59,10 @@ open class ReportService( | |||||
| for (item in financialStatus){ | for (item in financialStatus){ | ||||
| val normalConsumed = item.getValue("normalConsumed") as Double | val normalConsumed = item.getValue("normalConsumed") as Double | ||||
| val hourlyRate = item.getValue("hourlyRate") as BigDecimal | val hourlyRate = item.getValue("hourlyRate") as BigDecimal | ||||
| println("normalConsumed------------- $normalConsumed") | |||||
| println("hourlyRate------------- $hourlyRate") | |||||
| // println("normalConsumed------------- $normalConsumed") | |||||
| // println("hourlyRate------------- $hourlyRate") | |||||
| val manHourRate = normalConsumed.toBigDecimal().multiply(hourlyRate) | val manHourRate = normalConsumed.toBigDecimal().multiply(hourlyRate) | ||||
| println("manHourRate------------ $manHourRate") | |||||
| // println("manHourRate------------ $manHourRate") | |||||
| val otConsumed = item.getValue("otConsumed") as Double | val otConsumed = item.getValue("otConsumed") as Double | ||||
| val manOtHourRate = otConsumed.toBigDecimal().multiply(hourlyRate).multiply(otFactor) | val manOtHourRate = otConsumed.toBigDecimal().multiply(hourlyRate).multiply(otFactor) | ||||
| @@ -90,9 +94,9 @@ open class ReportService( | |||||
| } | } | ||||
| } | } | ||||
| println("tempList---------------------- $tempList") | |||||
| // println("tempList---------------------- $tempList") | |||||
| val workbook: Workbook = createFinancialStatusReport(FINANCIAL_STATUS_REPORT, tempList) | |||||
| val workbook: Workbook = createFinancialStatusReport(FINANCIAL_STATUS_REPORT, tempList, teamLeadId) | |||||
| val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() | val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() | ||||
| workbook.write(outputStream) | workbook.write(outputStream) | ||||
| @@ -105,10 +109,11 @@ open class ReportService( | |||||
| fun generateProjectCashFlowReport( | fun generateProjectCashFlowReport( | ||||
| project: Project, | project: Project, | ||||
| invoices: List<Invoice>, | invoices: List<Invoice>, | ||||
| timesheets: List<Timesheet> | |||||
| timesheets: List<Timesheet>, | |||||
| dateType: String | |||||
| ): ByteArray { | ): ByteArray { | ||||
| // Generate the Excel report with query results | // Generate the Excel report with query results | ||||
| val workbook: Workbook = createProjectCashFlowReport(project, invoices, timesheets, PROJECT_CASH_FLOW_REPORT) | |||||
| val workbook: Workbook = createProjectCashFlowReport(project, invoices, timesheets, dateType, PROJECT_CASH_FLOW_REPORT) | |||||
| // Write the workbook to a ByteArrayOutputStream | // Write the workbook to a ByteArrayOutputStream | ||||
| val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() | val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() | ||||
| @@ -122,9 +127,8 @@ open class ReportService( | |||||
| fun generateStaffMonthlyWorkHourAnalysisReport( | fun generateStaffMonthlyWorkHourAnalysisReport( | ||||
| month: LocalDate, | month: LocalDate, | ||||
| staff: Staff, | staff: Staff, | ||||
| timesheets: List<Timesheet>, | |||||
| leaves: List<Leave>, | |||||
| projectList: List<String> | |||||
| timesheets: List<Map<String, Any>>, | |||||
| leaves: List<Map<String, Any>>, | |||||
| ): ByteArray { | ): ByteArray { | ||||
| // Generate the Excel report with query results | // Generate the Excel report with query results | ||||
| val workbook: Workbook = createStaffMonthlyWorkHourAnalysisReport( | val workbook: Workbook = createStaffMonthlyWorkHourAnalysisReport( | ||||
| @@ -132,7 +136,6 @@ open class ReportService( | |||||
| staff, | staff, | ||||
| timesheets, | timesheets, | ||||
| leaves, | leaves, | ||||
| projectList, | |||||
| MONTHLY_WORK_HOURS_ANALYSIS_REPORT | MONTHLY_WORK_HOURS_ANALYSIS_REPORT | ||||
| ) | ) | ||||
| @@ -180,6 +183,7 @@ open class ReportService( | |||||
| private fun createFinancialStatusReport( | private fun createFinancialStatusReport( | ||||
| templatePath: String, | templatePath: String, | ||||
| projects: List<Map<String, Any>>, | projects: List<Map<String, Any>>, | ||||
| teamLeadId: Long | |||||
| ) : Workbook { | ) : Workbook { | ||||
| val resource = ClassPathResource(templatePath) | val resource = ClassPathResource(templatePath) | ||||
| @@ -190,14 +194,50 @@ open class ReportService( | |||||
| val sheet = workbook.getSheetAt(0) | val sheet = workbook.getSheetAt(0) | ||||
| //Set Column 2, 3, 4 to auto width | |||||
| sheet.setColumnWidth(2, 20*256) | |||||
| sheet.setColumnWidth(3, 45*256) | |||||
| sheet.setColumnWidth(4, 15*256) | |||||
| val boldFont = sheet.workbook.createFont() | val boldFont = sheet.workbook.createFont() | ||||
| boldFont.bold = true | boldFont.bold = true | ||||
| val boldFontCellStyle = workbook.createCellStyle() | val boldFontCellStyle = workbook.createCellStyle() | ||||
| boldFontCellStyle.setFont(boldFont) | boldFontCellStyle.setFont(boldFont) | ||||
| var rowNum = 14 | |||||
| if (projects.isEmpty()){ | |||||
| // Fill the cell in Row 2-12 with thr calculated sum | |||||
| rowNum = 1 | |||||
| val row1: Row = sheet.getRow(rowNum) | |||||
| val genDateCell = row1.createCell(2) | |||||
| genDateCell.setCellValue(LocalDate.now().toString()) | |||||
| rowNum = 2 | |||||
| val row2: Row = sheet.getRow(rowNum) | |||||
| val row2Cell = row2.createCell(2) | |||||
| val sql = StringBuilder("select" | |||||
| + " t.teamLead as id," | |||||
| + " t.name, t.code " | |||||
| + " from team t" | |||||
| + " where t.deleted = false " | |||||
| + " and t.teamLead = :teamLead " | |||||
| ) | |||||
| val args = mapOf("teamLead" to teamLeadId) | |||||
| val team = jdbcDao.queryForMap(sql.toString(), args).get() | |||||
| val code = team["code"] | |||||
| val name = team["name"] | |||||
| row2Cell.apply { | |||||
| setCellValue("$code - $name") | |||||
| } | |||||
| var rowNum = 14 | |||||
| rowNum = 4 | |||||
| val row4: Row = sheet.getRow(rowNum) | |||||
| val row4Cell = row4.createCell(2) | |||||
| row4Cell.setCellValue(projects.size.toString()) | |||||
| return workbook | |||||
| } | |||||
| for(item in projects){ | for(item in projects){ | ||||
| val row: Row = sheet.createRow(rowNum++) | val row: Row = sheet.createRow(rowNum++) | ||||
| @@ -209,7 +249,10 @@ open class ReportService( | |||||
| descriptionCell.setCellValue(if (item["description"] != null) item.getValue("description").toString() else "N/A") | descriptionCell.setCellValue(if (item["description"] != null) item.getValue("description").toString() else "N/A") | ||||
| val clientCell = row.createCell(2) | val clientCell = row.createCell(2) | ||||
| clientCell.setCellValue(if (item["client"] != null) item.getValue("client").toString() else "N/A") | |||||
| clientCell.apply { | |||||
| setCellValue(if (item["client"] != null) item.getValue("client").toString() else "N/A") | |||||
| } | |||||
| CellUtil.setAlignment(clientCell, HorizontalAlignment.CENTER) | |||||
| val teamLeadCell = row.createCell(3) | val teamLeadCell = row.createCell(3) | ||||
| teamLeadCell.setCellValue(if (item["teamLead"] != null) item.getValue("teamLead").toString() else "N/A") | teamLeadCell.setCellValue(if (item["teamLead"] != null) item.getValue("teamLead").toString() else "N/A") | ||||
| @@ -227,7 +270,6 @@ open class ReportService( | |||||
| cellStyle.dataFormat = accountingStyle | cellStyle.dataFormat = accountingStyle | ||||
| } | } | ||||
| val budgetCell = row.createCell(7) | val budgetCell = row.createCell(7) | ||||
| budgetCell.apply { | budgetCell.apply { | ||||
| cellFormula = "G${rowNum} * 80%" | cellFormula = "G${rowNum} * 80%" | ||||
| @@ -259,14 +301,14 @@ open class ReportService( | |||||
| val uninvoiceCell = row.createCell(11) | val uninvoiceCell = row.createCell(11) | ||||
| uninvoiceCell.apply { | uninvoiceCell.apply { | ||||
| cellFormula = " IF(H${rowNum}<I${rowNum},H${rowNum}-K${rowNum},I${rowNum}-K${rowNum}) " | |||||
| cellFormula = " IF(H${rowNum}<=I${rowNum}, H${rowNum}-K${rowNum}, IF(AND(H${rowNum}>I${rowNum}, I${rowNum}<K${rowNum}), 0, IF(AND(H${rowNum}>I${rowNum}, I${rowNum}>=K${rowNum}), I${rowNum}-K${rowNum}, 0))) " | |||||
| cellStyle = boldFontCellStyle | cellStyle = boldFontCellStyle | ||||
| cellStyle.dataFormat = accountingStyle | cellStyle.dataFormat = accountingStyle | ||||
| } | } | ||||
| val cpiCell = row.createCell(12) | val cpiCell = row.createCell(12) | ||||
| cpiCell.apply { | cpiCell.apply { | ||||
| cellFormula = "K${rowNum}/I${rowNum}" | |||||
| cellFormula = "IF(K${rowNum} = 0, 0, K${rowNum}/I${rowNum})" | |||||
| } | } | ||||
| val receivedAmountCell = row.createCell(13) | val receivedAmountCell = row.createCell(13) | ||||
| @@ -276,15 +318,14 @@ open class ReportService( | |||||
| cellStyle.dataFormat = accountingStyle | cellStyle.dataFormat = accountingStyle | ||||
| } | } | ||||
| val unsettledAmountCell = row.createCell(14) | val unsettledAmountCell = row.createCell(14) | ||||
| unsettledAmountCell.apply { | unsettledAmountCell.apply { | ||||
| cellFormula = "K${rowNum}-N${rowNum}" | cellFormula = "K${rowNum}-N${rowNum}" | ||||
| cellStyle = boldFontCellStyle | cellStyle = boldFontCellStyle | ||||
| cellStyle.dataFormat = accountingStyle | cellStyle.dataFormat = accountingStyle | ||||
| } | } | ||||
| } | } | ||||
| // Last row calculate the sum | |||||
| val lastRowNum = rowNum + 1 | val lastRowNum = rowNum + 1 | ||||
| val row: Row = sheet.createRow(rowNum) | val row: Row = sheet.createRow(rowNum) | ||||
| @@ -373,14 +414,27 @@ open class ReportService( | |||||
| CellUtil.setCellStyleProperty(sumUnSettleCell,"borderTop", BorderStyle.THIN) | CellUtil.setCellStyleProperty(sumUnSettleCell,"borderTop", BorderStyle.THIN) | ||||
| CellUtil.setCellStyleProperty(sumUnSettleCell,"borderBottom", BorderStyle.DOUBLE) | CellUtil.setCellStyleProperty(sumUnSettleCell,"borderBottom", BorderStyle.DOUBLE) | ||||
| // Fill the cell in Row 2-12 with thr calculated sum | |||||
| rowNum = 1 | rowNum = 1 | ||||
| val row1: Row = sheet.getRow(rowNum) | val row1: Row = sheet.getRow(rowNum) | ||||
| val genDateCell = row1.createCell(2) | val genDateCell = row1.createCell(2) | ||||
| genDateCell.setCellValue(LocalDate.now().toString()) | genDateCell.setCellValue(LocalDate.now().toString()) | ||||
| rowNum = 2 | rowNum = 2 | ||||
| val row2: Row = sheet.getRow(rowNum) | |||||
| val row2Cell = row2.createCell(2) | |||||
| if (teamLeadId < 0) { | |||||
| row2Cell.setCellValue("All") | |||||
| }else{ | |||||
| row2Cell.apply { | |||||
| cellFormula = "D15" | |||||
| } | |||||
| } | |||||
| rowNum = 4 | rowNum = 4 | ||||
| val row4: Row = sheet.getRow(rowNum) | |||||
| val row4Cell = row4.createCell(2) | |||||
| row4Cell.setCellValue(projects.size.toString()) | |||||
| rowNum = 5 | rowNum = 5 | ||||
| val row5: Row = sheet.getRow(rowNum) | val row5: Row = sheet.getRow(rowNum) | ||||
| @@ -446,6 +500,7 @@ open class ReportService( | |||||
| project: Project, | project: Project, | ||||
| invoices: List<Invoice>, | invoices: List<Invoice>, | ||||
| timesheets: List<Timesheet>, | timesheets: List<Timesheet>, | ||||
| dateType: String, | |||||
| templatePath: String, | templatePath: String, | ||||
| ): Workbook { | ): Workbook { | ||||
| // please create a new function for each report template | // please create a new function for each report template | ||||
| @@ -531,7 +586,7 @@ open class ReportService( | |||||
| rowIndex = 15 | rowIndex = 15 | ||||
| val dateFormatter = DateTimeFormatter.ofPattern("MMM YYYY") | |||||
| val dateFormatter = if (dateType == "Date") DateTimeFormatter.ofPattern("yyyy/MM/dd") else DateTimeFormatter.ofPattern("MMM YYYY") | |||||
| val combinedResults = | val combinedResults = | ||||
| (invoices.map { it.receiptDate } + timesheets.map { it.recordDate }).filterNotNull().sortedBy { it } | (invoices.map { it.receiptDate } + timesheets.map { it.recordDate }).filterNotNull().sortedBy { it } | ||||
| .map { it.format(dateFormatter) }.distinct() | .map { it.format(dateFormatter) }.distinct() | ||||
| @@ -675,21 +730,35 @@ open class ReportService( | |||||
| private fun createStaffMonthlyWorkHourAnalysisReport( | private fun createStaffMonthlyWorkHourAnalysisReport( | ||||
| month: LocalDate, | month: LocalDate, | ||||
| staff: Staff, | staff: Staff, | ||||
| timesheets: List<Timesheet>, | |||||
| leaves: List<Leave>, | |||||
| projectList: List<String>, | |||||
| timesheets: List<Map<String, Any>>, | |||||
| leaves: List<Map<String, Any>>, | |||||
| templatePath: String, | templatePath: String, | ||||
| ): Workbook { | ): Workbook { | ||||
| // val yearMonth = YearMonth.of(2022, 5) // May 2022 | |||||
| println("t $timesheets") | |||||
| println("l $leaves") | |||||
| println("p $projectList") | |||||
| var projectList: List<String> = listOf() | |||||
| println("----timesheets-----") | |||||
| println(timesheets) | |||||
| // result = timesheet record mapped | |||||
| var result: Map<String, Any> = mapOf() | |||||
| if (timesheets.isNotEmpty()) { | |||||
| projectList = timesheets.map{ "${it["code"]}\n ${it["name"]}"}.toList() | |||||
| result = timesheets.groupBy( | |||||
| { it["id"].toString() }, | |||||
| { mapOf( | |||||
| "date" to it["recordDate"], | |||||
| "normalConsumed" to it["normalConsumed"], | |||||
| "otConsumed" to it["otConsumed"], | |||||
| ) } | |||||
| ) | |||||
| } | |||||
| println("---result---") | |||||
| println(result) | |||||
| println("l $projectList") | |||||
| val resource = ClassPathResource(templatePath) | val resource = ClassPathResource(templatePath) | ||||
| val templateInputStream = resource.inputStream | val templateInputStream = resource.inputStream | ||||
| val workbook: Workbook = XSSFWorkbook(templateInputStream) | val workbook: Workbook = XSSFWorkbook(templateInputStream) | ||||
| val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") | val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") | ||||
| val monthStyle = workbook.createDataFormat().getFormat("mmm, yyyy") | |||||
| val monthStyle = workbook.createDataFormat().getFormat("MMM YYYY") | |||||
| val dateStyle = workbook.createDataFormat().getFormat("dd/mm/yyyy") | val dateStyle = workbook.createDataFormat().getFormat("dd/mm/yyyy") | ||||
| val boldStyle = workbook.createCellStyle() | val boldStyle = workbook.createCellStyle() | ||||
| @@ -705,8 +774,6 @@ open class ReportService( | |||||
| val sheet: Sheet = workbook.getSheetAt(0) | val sheet: Sheet = workbook.getSheetAt(0) | ||||
| // sheet.forceFormulaRecalculation = true; //Calculate formulas | |||||
| var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field | var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field | ||||
| var columnIndex = 1 | var columnIndex = 1 | ||||
| @@ -723,7 +790,6 @@ open class ReportService( | |||||
| rowIndex = 2 | rowIndex = 2 | ||||
| sheet.getRow(rowIndex).getCell(columnIndex).apply { | sheet.getRow(rowIndex).getCell(columnIndex).apply { | ||||
| setCellValue(month) | setCellValue(month) | ||||
| // cellStyle.setFont(boldStyle) | |||||
| cellStyle.dataFormat = monthStyle | cellStyle.dataFormat = monthStyle | ||||
| } | } | ||||
| @@ -758,11 +824,9 @@ open class ReportService( | |||||
| tempCell.setCellValue(dayInfo.date) | tempCell.setCellValue(dayInfo.date) | ||||
| tempCell.cellStyle = boldStyle | tempCell.cellStyle = boldStyle | ||||
| tempCell.cellStyle.dataFormat = dateStyle | tempCell.cellStyle.dataFormat = dateStyle | ||||
| // cellStyle.alignment = HorizontalAlignment.LEFT | |||||
| tempCell = sheet.getRow(rowIndex).createCell(1) | tempCell = sheet.getRow(rowIndex).createCell(1) | ||||
| tempCell.setCellValue(dayInfo.weekday) | tempCell.setCellValue(dayInfo.weekday) | ||||
| tempCell.cellStyle = boldStyle | tempCell.cellStyle = boldStyle | ||||
| // cellStyle.alignment = HorizontalAlignment.LEFT | |||||
| } | } | ||||
| rowIndex += 1 | rowIndex += 1 | ||||
| @@ -784,10 +848,11 @@ open class ReportService( | |||||
| var normalConsumed = 0.0 | var normalConsumed = 0.0 | ||||
| var otConsumed = 0.0 | var otConsumed = 0.0 | ||||
| var leaveHours = 0.0 | var leaveHours = 0.0 | ||||
| // normalConsumed data | |||||
| if (timesheets.isNotEmpty()) { | if (timesheets.isNotEmpty()) { | ||||
| timesheets.forEach { t -> | timesheets.forEach { t -> | ||||
| normalConsumed += t.normalConsumed!! | |||||
| otConsumed += t.otConsumed ?: 0.0 | |||||
| normalConsumed += t["normalConsumed"] as Double | |||||
| otConsumed += t["otConsumed"] as Double | |||||
| } | } | ||||
| } | } | ||||
| tempCell = sheet.getRow(rowIndex).createCell(2) | tempCell = sheet.getRow(rowIndex).createCell(2) | ||||
| @@ -815,9 +880,10 @@ open class ReportService( | |||||
| tempCell.cellStyle = boldStyle | tempCell.cellStyle = boldStyle | ||||
| CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) | CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) | ||||
| sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1)) | sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1)) | ||||
| // cal total leave hour | |||||
| if (leaves.isNotEmpty()) { | if (leaves.isNotEmpty()) { | ||||
| leaves.forEach { l -> | leaves.forEach { l -> | ||||
| leaveHours += l.leaveHours!! | |||||
| leaveHours += l["leaveHours"] as Double | |||||
| } | } | ||||
| } | } | ||||
| tempCell = sheet.getRow(rowIndex).createCell(2) | tempCell = sheet.getRow(rowIndex).createCell(2) | ||||
| @@ -862,28 +928,30 @@ open class ReportService( | |||||
| tempCell.setCellValue(0.0) | tempCell.setCellValue(0.0) | ||||
| tempCell.cellStyle.dataFormat = accountingStyle | tempCell.cellStyle.dataFormat = accountingStyle | ||||
| } | } | ||||
| timesheets.forEach { timesheet -> | |||||
| dayInt = timesheet.recordDate!!.dayOfMonth | |||||
| tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) | |||||
| tempCell.setCellValue(timesheet.normalConsumed!!) | |||||
| result.forEach{(id, list) -> | |||||
| for (i in 0 until id.toInt()) { | |||||
| val temp: List<Map<String, Any>> = list as List<Map<String, Any>> | |||||
| temp.forEachIndexed { i, _ -> | |||||
| dayInt = temp[i]["date"].toString().toInt() | |||||
| tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) | |||||
| tempCell.setCellValue(temp[i]["normalConsumed"] as Double) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| columnIndex++ | columnIndex++ | ||||
| } | } | ||||
| } | } | ||||
| // dates | |||||
| // leave hours data | |||||
| if (leaves.isNotEmpty()) { | if (leaves.isNotEmpty()) { | ||||
| leaves.forEach { leave -> | leaves.forEach { leave -> | ||||
| for (i in 0 until rowSize) { | for (i in 0 until rowSize) { | ||||
| tempCell = sheet.getRow(8 + i).createCell(columnIndex) | tempCell = sheet.getRow(8 + i).createCell(columnIndex) | ||||
| tempCell.setCellValue(0.0) | tempCell.setCellValue(0.0) | ||||
| tempCell.cellStyle.dataFormat = accountingStyle | tempCell.cellStyle.dataFormat = accountingStyle | ||||
| } | } | ||||
| dayInt = leave.recordDate!!.dayOfMonth | |||||
| dayInt = leave["recordDate"].toString().toInt() | |||||
| tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) | tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) | ||||
| tempCell.setCellValue(leave.leaveHours!!) | |||||
| tempCell.setCellValue(leave["leaveHours"] as Double) | |||||
| } | } | ||||
| } | } | ||||
| ///////////////////////////////////////////////////////// Leave Hours //////////////////////////////////////////////////////////////////// | ///////////////////////////////////////////////////////// Leave Hours //////////////////////////////////////////////////////////////////// | ||||
| @@ -1040,33 +1108,73 @@ open class ReportService( | |||||
| return workbook | return workbook | ||||
| } | } | ||||
| open fun getFinancialStatus(projectId: Long?): List<Map<String, Any>> { | |||||
| open fun getFinancialStatus(teamLeadId: Long?): List<Map<String, Any>> { | |||||
| val sql = StringBuilder( | val sql = StringBuilder( | ||||
| " with cte_invoice as (select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount" | |||||
| " with cte_timesheet as (" | |||||
| + " Select p.code, s.name as staff, IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate" | |||||
| + " from timesheet t" | |||||
| + " left join project_task pt on pt.id = t.projectTaskId" | |||||
| + " left join project p ON p.id = pt.project_id" | |||||
| + " left join staff s on s.id = t.staffId" | |||||
| + " left join salary s2 on s.salaryId = s2.salaryPoint" | |||||
| + " left join team t2 on t2.id = s.teamId" | |||||
| + " )," | |||||
| + " cte_invoice as (" | |||||
| + " select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount" | |||||
| + " from invoice i" | + " from invoice i" | ||||
| + " left join project p on p.code = i.projectCode" | + " left join project p on p.code = i.projectCode" | ||||
| + " group by p.code" | + " group by p.code" | ||||
| + ")" | |||||
| + " Select p.code, p.description, c.name, t2.name, p.planStart , p.planEnd , p.expectedTotalFee ," | |||||
| + " s.name , IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate," | |||||
| + " cte_i.sumIssuedAmount, cte_i.sumPaidAmount" | |||||
| + " from timesheet t" | |||||
| + " left join project_task pt on pt.id = t.projectTaskId" | |||||
| + " left join project p ON p.id = pt.project_id" | |||||
| + " left join staff s on s.id = t.staffId" | |||||
| + " left join salary s2 on s.salaryId = s2.salaryPoint" | |||||
| + " )" | |||||
| + " select p.code, p.description, c.name as client, concat(t.code, \' - \', t.name) as teamLead, p.planStart , p.planEnd , p.expectedTotalFee," | |||||
| + " IFNULL(cte_ts.normalConsumed, 0) as normalConsumed, IFNULL(cte_ts.otConsumed, 0) as otConsumed," | |||||
| + " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate, IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount" | |||||
| + " from project p" | |||||
| + " left join cte_timesheet cte_ts on p.code = cte_ts.code" | |||||
| + " left join customer c on c.id = p.customerId" | + " left join customer c on c.id = p.customerId" | ||||
| + " left join team t2 on t2.id = s.teamId" | |||||
| + " left join tsmsdb.team t on t.teamLead = p.teamLead" | |||||
| + " left join cte_invoice cte_i on cte_i.code = p.code" | + " left join cte_invoice cte_i on cte_i.code = p.code" | ||||
| + " where p.status = \'On-going\'" | |||||
| ) | ) | ||||
| if (projectId!! > 0) { | |||||
| sql.append(" where p.id = :projectId ") | |||||
| if (teamLeadId!! > 0) { | |||||
| sql.append(" and p.teamLead = :teamLeadId ") | |||||
| } | } | ||||
| sql.append(" order by p.code") | sql.append(" order by p.code") | ||||
| val args = mapOf("projectId" to projectId) | |||||
| val args = mapOf("teamLeadId" to teamLeadId) | |||||
| return jdbcDao.queryForList(sql.toString(), args) | return jdbcDao.queryForList(sql.toString(), args) | ||||
| } | } | ||||
| } | |||||
| open fun getTimesheet(args: Map<String, Any>): List<Map<String, Any>> { | |||||
| val sql = StringBuilder( | |||||
| "SELECT" | |||||
| + " p.id," | |||||
| + " p.name," | |||||
| + " p.code," | |||||
| + " CAST(DATE_FORMAT(t.recordDate, '%d') AS SIGNED) AS recordDate," | |||||
| + " sum(t.normalConsumed) as normalConsumed," | |||||
| + " IFNULL(sum(t.otConsumed), 0.0) as otConsumed" | |||||
| + " from timesheet t" | |||||
| + " left join project_task pt on t.projectTaskId = pt.id" | |||||
| + " left join project p on p.id = pt.project_id" | |||||
| + " where t.staffId = :staffId" | |||||
| + " group by p.id, t.recordDate" | |||||
| + " order by p.id, t.recordDate" | |||||
| + " and t.recordDate BETWEEN :startDate and :endDate" | |||||
| ) | |||||
| return jdbcDao.queryForList(sql.toString(), args) | |||||
| } | |||||
| open fun getLeaves(args: Map<String, Any>): List<Map<String, Any>> { | |||||
| val sql = StringBuilder( | |||||
| " SELECT " | |||||
| + " sum(leaveHours) as leaveHours, " | |||||
| + " CAST(DATE_FORMAT(recordDate, '%d') AS SIGNED) AS recordDate " | |||||
| + " from `leave` " | |||||
| + " where staffId = :staffId " | |||||
| + " and recordDate BETWEEN :startDate and :endDate " | |||||
| + " group by recordDate " | |||||
| + " order by recordDate " | |||||
| ) | |||||
| return jdbcDao.queryForList(sql.toString(), args) | |||||
| } | |||||
| } | |||||
| @@ -13,6 +13,7 @@ import com.ffii.tsms.modules.report.web.model.LateStartReportRequest | |||||
| import com.ffii.tsms.modules.project.entity.ProjectRepository | import com.ffii.tsms.modules.project.entity.ProjectRepository | ||||
| import com.ffii.tsms.modules.timesheet.entity.LeaveRepository | import com.ffii.tsms.modules.timesheet.entity.LeaveRepository | ||||
| import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository | import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository | ||||
| import com.ffii.tsms.modules.timesheet.entity.projections.ProjectMonthlyHoursWithDate | |||||
| import jakarta.validation.Valid | import jakarta.validation.Valid | ||||
| import org.springframework.core.io.ByteArrayResource | import org.springframework.core.io.ByteArrayResource | ||||
| import org.springframework.core.io.Resource | import org.springframework.core.io.Resource | ||||
| @@ -57,8 +58,8 @@ class ReportController( | |||||
| @Throws(ServletRequestBindingException::class, IOException::class) | @Throws(ServletRequestBindingException::class, IOException::class) | ||||
| fun getFinancialStatusReport(@RequestBody @Valid request: FinancialStatusReportRequest): ResponseEntity<Resource> { | fun getFinancialStatusReport(@RequestBody @Valid request: FinancialStatusReportRequest): ResponseEntity<Resource> { | ||||
| val reportResult: ByteArray = excelReportService.genFinancialStatusReport(request.projectId) | |||||
| println(request.teamLeadId) | |||||
| val reportResult: ByteArray = excelReportService.genFinancialStatusReport(request.teamLeadId) | |||||
| return ResponseEntity.ok() | return ResponseEntity.ok() | ||||
| .header("filename", "Financial Status Report - " + LocalDate.now() + ".xlsx") | .header("filename", "Financial Status Report - " + LocalDate.now() + ".xlsx") | ||||
| @@ -74,7 +75,7 @@ class ReportController( | |||||
| val invoices = invoiceService.findAllByProjectAndPaidAmountIsNotNull(project) | val invoices = invoiceService.findAllByProjectAndPaidAmountIsNotNull(project) | ||||
| val timesheets = timesheetRepository.findAllByProjectTaskIn(projectTasks) | val timesheets = timesheetRepository.findAllByProjectTaskIn(projectTasks) | ||||
| val reportResult: ByteArray = excelReportService.generateProjectCashFlowReport(project, invoices, timesheets) | |||||
| val reportResult: ByteArray = excelReportService.generateProjectCashFlowReport(project, invoices, timesheets, request.dateType) | |||||
| // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") | // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") | ||||
| return ResponseEntity.ok() | return ResponseEntity.ok() | ||||
| // .contentType(mediaType) | // .contentType(mediaType) | ||||
| @@ -89,14 +90,15 @@ class ReportController( | |||||
| val nextMonth = request.yearMonth.plusMonths(1).atDay(1) | val nextMonth = request.yearMonth.plusMonths(1).atDay(1) | ||||
| val staff = staffRepository.findById(request.id).orElseThrow() | val staff = staffRepository.findById(request.id).orElseThrow() | ||||
| val timesheets = timesheetRepository.findByStaffAndRecordDateBetweenOrderByRecordDate(staff, thisMonth, nextMonth) | |||||
| val leaves = leaveRepository.findByStaffAndRecordDateBetweenOrderByRecordDate(staff, thisMonth, nextMonth) | |||||
| val projects = timesheetRepository.findDistinctProjectTaskByStaffAndRecordDateBetweenOrderByRecordDate(staff, thisMonth, nextMonth) | |||||
| val projectList: List<String> = projects.map { p -> "${p.projectTask!!.project!!.code}\n ${p.projectTask!!.project!!.name}" } | |||||
| val reportResult: ByteArray = excelReportService.generateStaffMonthlyWorkHourAnalysisReport(thisMonth, staff, timesheets, leaves, projectList) | |||||
| val args: Map<String, Any> = mutableMapOf( | |||||
| "staffId" to request.id, | |||||
| "startDate" to thisMonth, | |||||
| "endDate" to nextMonth, | |||||
| ) | |||||
| val timesheets= excelReportService.getTimesheet(args) | |||||
| val leaves= excelReportService.getLeaves(args) | |||||
| val reportResult: ByteArray = excelReportService.generateStaffMonthlyWorkHourAnalysisReport(thisMonth, staff, timesheets, leaves) | |||||
| // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") | // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") | ||||
| return ResponseEntity.ok() | return ResponseEntity.ok() | ||||
| // .contentType(mediaType) | // .contentType(mediaType) | ||||
| @@ -166,7 +168,7 @@ fun downloadLateStartReport(@RequestBody @Valid request: LateStartReportRequest) | |||||
| @GetMapping("/financialReport/{id}") | @GetMapping("/financialReport/{id}") | ||||
| fun getFinancialReport(@PathVariable id: Long): List<Map<String, Any>> { | fun getFinancialReport(@PathVariable id: Long): List<Map<String, Any>> { | ||||
| println(excelReportService.genFinancialStatusReport(id)) | |||||
| // println(excelReportService.genFinancialStatusReport(id)) | |||||
| return excelReportService.getFinancialStatus(id) | return excelReportService.getFinancialStatus(id) | ||||
| } | } | ||||
| @@ -4,10 +4,11 @@ import java.time.LocalDate | |||||
| import java.time.YearMonth | import java.time.YearMonth | ||||
| data class FinancialStatusReportRequest ( | data class FinancialStatusReportRequest ( | ||||
| val projectId: Long | |||||
| val teamLeadId: Long | |||||
| ) | ) | ||||
| data class ProjectCashFlowReportRequest ( | data class ProjectCashFlowReportRequest ( | ||||
| val projectId: Long | |||||
| val projectId: Long, | |||||
| val dateType: String, // "Date", "Month" | |||||
| ) | ) | ||||
| data class StaffMonthlyWorkHourAnalysisReportRequest ( | data class StaffMonthlyWorkHourAnalysisReportRequest ( | ||||
| @@ -2,7 +2,16 @@ package com.ffii.tsms.modules.timesheet.entity.projections | |||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| data class MonthlyHours( | |||||
| data class MonthlyLeave( | |||||
| val date: LocalDate, | val date: LocalDate, | ||||
| val nomralConsumed: Number | |||||
| val leaveHours: Double | |||||
| ) | ) | ||||
| data class ProjectMonthlyHoursWithDate( | |||||
| val id: Long, | |||||
| val name: String, | |||||
| val code: String, | |||||
| val date: LocalDate, | |||||
| val normalConsumed: Double, | |||||
| val otConsumed: Double, | |||||
| ) | |||||
| @@ -2,16 +2,19 @@ package com.ffii.tsms.modules.timesheet.service | |||||
| import com.ffii.core.exception.BadRequestException | import com.ffii.core.exception.BadRequestException | ||||
| import com.ffii.tsms.modules.data.service.StaffsService | import com.ffii.tsms.modules.data.service.StaffsService | ||||
| import com.ffii.tsms.modules.data.service.TeamService | |||||
| import com.ffii.tsms.modules.project.entity.ProjectRepository | import com.ffii.tsms.modules.project.entity.ProjectRepository | ||||
| import com.ffii.tsms.modules.project.entity.ProjectTaskRepository | import com.ffii.tsms.modules.project.entity.ProjectTaskRepository | ||||
| import com.ffii.tsms.modules.project.entity.TaskRepository | import com.ffii.tsms.modules.project.entity.TaskRepository | ||||
| import com.ffii.tsms.modules.timesheet.entity.Timesheet | import com.ffii.tsms.modules.timesheet.entity.Timesheet | ||||
| import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository | import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository | ||||
| import com.ffii.tsms.modules.timesheet.web.models.TeamMemberTimeEntries | |||||
| import com.ffii.tsms.modules.timesheet.web.models.TimeEntry | import com.ffii.tsms.modules.timesheet.web.models.TimeEntry | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import org.springframework.transaction.annotation.Transactional | import org.springframework.transaction.annotation.Transactional | ||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| import kotlin.jvm.optionals.getOrDefault | |||||
| import kotlin.jvm.optionals.getOrNull | import kotlin.jvm.optionals.getOrNull | ||||
| @Service | @Service | ||||
| @@ -20,7 +23,8 @@ open class TimesheetsService( | |||||
| private val projectTaskRepository: ProjectTaskRepository, | private val projectTaskRepository: ProjectTaskRepository, | ||||
| private val projectRepository: ProjectRepository, | private val projectRepository: ProjectRepository, | ||||
| private val taskRepository: TaskRepository, | private val taskRepository: TaskRepository, | ||||
| private val staffsService: StaffsService | |||||
| private val staffsService: StaffsService, | |||||
| private val teamService: TeamService | |||||
| ) { | ) { | ||||
| @Transactional | @Transactional | ||||
| open fun saveTimesheet(recordTimeEntry: Map<LocalDate, List<TimeEntry>>): Map<String, List<TimeEntry>> { | open fun saveTimesheet(recordTimeEntry: Map<LocalDate, List<TimeEntry>>): Map<String, List<TimeEntry>> { | ||||
| @@ -52,12 +56,56 @@ open class TimesheetsService( | |||||
| return transformToTimeEntryMap(savedTimesheets) | return transformToTimeEntryMap(savedTimesheets) | ||||
| } | } | ||||
| @Transactional | |||||
| open fun saveMemberTimeEntry(staffId: Long, entry: TimeEntry, recordDate: LocalDate?): Map<String, List<TimeEntry>> { | |||||
| val currentStaff = staffsService.currentStaff() ?: throw BadRequestException() | |||||
| // Make sure current staff is a team lead | |||||
| teamService.getMyTeamForStaff(currentStaff) ?: throw BadRequestException() | |||||
| val memberStaff = staffsService.getStaff(staffId) | |||||
| val timesheet = timesheetRepository.findById(entry.id).getOrDefault(Timesheet()).apply { | |||||
| val task = entry.taskId?.let { taskRepository.findById(it).getOrNull() } | |||||
| val project = entry.projectId?.let { projectRepository.findById(it).getOrNull() } | |||||
| val projectTask = project?.let { p -> task?.let { t -> projectTaskRepository.findByProjectAndTask(p, t) } } | |||||
| this.normalConsumed = entry.inputHours | |||||
| this.otConsumed = entry.otHours | |||||
| this.projectTask = projectTask | |||||
| this.remark = entry.remark | |||||
| this.recordDate = this.recordDate ?: recordDate | |||||
| this.staff = this.staff ?: memberStaff | |||||
| } | |||||
| timesheetRepository.save(timesheet) | |||||
| return transformToTimeEntryMap(timesheetRepository.findAllByStaff(memberStaff)) | |||||
| } | |||||
| open fun getTimesheet(): Map<String, List<TimeEntry>> { | open fun getTimesheet(): Map<String, List<TimeEntry>> { | ||||
| // Need to be associated with a staff | // Need to be associated with a staff | ||||
| val currentStaff = staffsService.currentStaff() ?: return emptyMap() | val currentStaff = staffsService.currentStaff() ?: return emptyMap() | ||||
| return transformToTimeEntryMap(timesheetRepository.findAllByStaff(currentStaff)) | return transformToTimeEntryMap(timesheetRepository.findAllByStaff(currentStaff)) | ||||
| } | } | ||||
| open fun getTimeMemberTimesheet(): Map<Long, TeamMemberTimeEntries> { | |||||
| val currentStaff = staffsService.currentStaff() ?: return emptyMap() | |||||
| // Get team where current staff is team lead | |||||
| val myTeam = teamService.getMyTeamForStaff(currentStaff) ?: return emptyMap() | |||||
| val teamMembers = staffsService.findAllByTeamId(myTeam.id!!).getOrDefault(emptyList()) | |||||
| return teamMembers.associate { member -> | |||||
| Pair( | |||||
| member.id!!, | |||||
| TeamMemberTimeEntries( | |||||
| staffId = member.staffId, | |||||
| name = member.name, | |||||
| timeEntries = transformToTimeEntryMap(timesheetRepository.findAllByStaff(member)) | |||||
| ) | |||||
| ) | |||||
| } | |||||
| } | |||||
| private fun transformToTimeEntryMap(timesheets: List<Timesheet>): Map<String, List<TimeEntry>> { | private fun transformToTimeEntryMap(timesheets: List<Timesheet>): Map<String, List<TimeEntry>> { | ||||
| return timesheets | return timesheets | ||||
| .groupBy { timesheet -> timesheet.recordDate!!.format(DateTimeFormatter.ISO_LOCAL_DATE) } | .groupBy { timesheet -> timesheet.recordDate!!.format(DateTimeFormatter.ISO_LOCAL_DATE) } | ||||
| @@ -5,6 +5,8 @@ import com.ffii.tsms.modules.timesheet.entity.LeaveType | |||||
| import com.ffii.tsms.modules.timesheet.service.LeaveService | import com.ffii.tsms.modules.timesheet.service.LeaveService | ||||
| import com.ffii.tsms.modules.timesheet.service.TimesheetsService | import com.ffii.tsms.modules.timesheet.service.TimesheetsService | ||||
| import com.ffii.tsms.modules.timesheet.web.models.LeaveEntry | import com.ffii.tsms.modules.timesheet.web.models.LeaveEntry | ||||
| import com.ffii.tsms.modules.timesheet.web.models.TeamMemberTimeEntries | |||||
| import com.ffii.tsms.modules.timesheet.web.models.TeamTimeEntry | |||||
| import com.ffii.tsms.modules.timesheet.web.models.TimeEntry | import com.ffii.tsms.modules.timesheet.web.models.TimeEntry | ||||
| import jakarta.validation.Valid | import jakarta.validation.Valid | ||||
| import org.springframework.web.bind.annotation.GetMapping | import org.springframework.web.bind.annotation.GetMapping | ||||
| @@ -43,6 +45,18 @@ class TimesheetsController(private val timesheetsService: TimesheetsService, pri | |||||
| return timesheetsService.getTimesheet() | return timesheetsService.getTimesheet() | ||||
| } | } | ||||
| @GetMapping("/teamTimesheets") | |||||
| fun getTeamMemberTimesheetEntries(): Map<Long, TeamMemberTimeEntries> { | |||||
| return timesheetsService.getTimeMemberTimesheet() | |||||
| } | |||||
| @PostMapping("/saveMemberEntry") | |||||
| fun saveMemberEntry(@Valid @RequestBody request: TeamTimeEntry): Map<String, List<TimeEntry>> { | |||||
| return timesheetsService.saveMemberTimeEntry(request.staffId, request.entry, runCatching { | |||||
| LocalDate.parse(request.recordDate, DateTimeFormatter.ISO_LOCAL_DATE) | |||||
| }.getOrNull()) | |||||
| } | |||||
| @GetMapping("/leaves") | @GetMapping("/leaves") | ||||
| fun getLeaveEntry(): Map<String, List<LeaveEntry>> { | fun getLeaveEntry(): Map<String, List<LeaveEntry>> { | ||||
| return leaveService.getLeaves() | return leaveService.getLeaves() | ||||
| @@ -0,0 +1,7 @@ | |||||
| package com.ffii.tsms.modules.timesheet.web.models | |||||
| data class TeamMemberTimeEntries( | |||||
| val timeEntries: Map<String, List<TimeEntry>>, | |||||
| val staffId: String?, | |||||
| val name: String?, | |||||
| ) | |||||
| @@ -0,0 +1,7 @@ | |||||
| package com.ffii.tsms.modules.timesheet.web.models | |||||
| data class TeamTimeEntry( | |||||
| val staffId: Long, | |||||
| val entry: TimeEntry, | |||||
| val recordDate: String, | |||||
| ) | |||||
| @@ -24,7 +24,7 @@ public class UpdateUserReq { | |||||
| private String locale; | private String locale; | ||||
| private String remarks; | private String remarks; | ||||
| @NotBlank | |||||
| // @NotBlank | |||||
| private String email; | private String email; | ||||
| // @NotBlank | // @NotBlank | ||||
| @@ -153,19 +153,10 @@ public class UserController{ | |||||
| @PatchMapping("/admin-change-password") | @PatchMapping("/admin-change-password") | ||||
| @ResponseStatus(HttpStatus.NO_CONTENT) | @ResponseStatus(HttpStatus.NO_CONTENT) | ||||
| @PreAuthorize("hasAuthority('MAINTAIN_USER')") | @PreAuthorize("hasAuthority('MAINTAIN_USER')") | ||||
| public void adminChangePassword(@RequestBody @Valid ChangePwdReq req) { | |||||
| public void adminChangePassword(@RequestBody @Valid AdminChangePwdReq req) { | |||||
| long id = req.getId(); | long id = req.getId(); | ||||
| User instance = userService.find(id).orElseThrow(NotFoundException::new); | User instance = userService.find(id).orElseThrow(NotFoundException::new); | ||||
| logger.info("TEST req: "+req.getPassword()); | |||||
| logger.info("TEST instance: "+instance.getPassword()); | |||||
| // if (!passwordEncoder.matches(req.getPassword(), instance.getPassword())) { | |||||
| // throw new BadRequestException(); | |||||
| // } | |||||
| PasswordRule rule = new PasswordRule(settingsService); | |||||
| if (!PasswordUtils.checkPwd(req.getNewPassword(), rule)) { | |||||
| throw new UnprocessableEntityException(ErrorCodes.USER_WRONG_NEW_PWD); | |||||
| } | |||||
| instance.setPassword(passwordEncoder.encode(req.getNewPassword())); | instance.setPassword(passwordEncoder.encode(req.getNewPassword())); | ||||
| userService.save(instance); | userService.save(instance); | ||||
| } | } | ||||
| @@ -188,6 +179,20 @@ public class UserController{ | |||||
| return new PasswordRule(settingsService); | return new PasswordRule(settingsService); | ||||
| } | } | ||||
| public static class AdminChangePwdReq { | |||||
| private Long id; | |||||
| @NotBlank | |||||
| private String newPassword; | |||||
| public Long getId() { return id; } | |||||
| public Long setId(Long id) { return this.id = id; } | |||||
| public String getNewPassword() { | |||||
| return newPassword; | |||||
| } | |||||
| public void setNewPassword(String newPassword) { | |||||
| this.newPassword = newPassword; | |||||
| } | |||||
| } | |||||
| public static class ChangePwdReq { | public static class ChangePwdReq { | ||||
| private Long id; | private Long id; | ||||
| @NotBlank | @NotBlank | ||||