| @@ -1,6 +1,11 @@ | |||
| package com.ffii.tsms.modules.data.entity; | |||
| import com.ffii.core.support.AbstractRepository; | |||
| import org.springframework.data.repository.query.Param; | |||
| import java.util.List; | |||
| 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> { | |||
| 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 org.springframework.beans.BeanUtils | |||
| import org.springframework.stereotype.Service | |||
| import java.math.BigDecimal | |||
| import java.util.Optional | |||
| @Service | |||
| @@ -23,9 +24,7 @@ open class DashboardService( | |||
| fun CustomerSubsidiary(args: Map<String, Any>): List<Map<String, Any>> { | |||
| 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.name as customerName," | |||
| + " c.code as customerCode," | |||
| @@ -45,6 +44,7 @@ open class DashboardService( | |||
| + " left join project p on c.id = p.customerId" | |||
| + " left join subsidiary s on p.customerSubsidiaryId = s.id" | |||
| + " where c.deleted = 0" | |||
| + " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")" | |||
| ) | |||
| if (args != null) { | |||
| if (args.containsKey("customerName")) | |||
| @@ -58,6 +58,7 @@ open class DashboardService( | |||
| fun searchCustomerSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> { | |||
| 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 projectId," | |||
| + " p.code as projectCode," | |||
| @@ -91,6 +92,7 @@ open class DashboardService( | |||
| + " ) milestonePayment on 1=1" | |||
| + " where p.customerId = :customerId" | |||
| + " 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" | |||
| ) | |||
| @@ -99,6 +101,7 @@ open class DashboardService( | |||
| fun searchCustomerNonSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> { | |||
| 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 projectId," | |||
| + " p.code as projectCode," | |||
| @@ -132,11 +135,94 @@ open class DashboardService( | |||
| + " ) milestonePayment on 1=1" | |||
| + " where p.customerId = :customerId" | |||
| + " 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" | |||
| ) | |||
| 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]) | |||
| open fun saveStaff(req: NewStaffRequest): Staff { | |||
| // if (req.staffId) | |||
| val checkStaffIdList: List<StaffSearchInfo> = staffRepository.findStaffSearchInfoByAndDeletedFalse() | |||
| checkStaffIdList.forEach{ s -> | |||
| 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 team = if (req.teamId != null && req.teamId > 0L) teamRepository.findById(req.teamId).orElseThrow() else null | |||
| val salary = salaryRepository.findBySalaryPoint(req.salaryId).orElseThrow() | |||
| // val salaryEffective = salaryEffectiveRepository.findById(req.salaryEffId).orElseThrow() | |||
| val department = departmentRepository.findById(req.departmentId).orElseThrow() | |||
| val user = userRepository.saveAndFlush( | |||
| @@ -117,7 +115,6 @@ open class StaffsService( | |||
| password = passwordEncoder.encode("mms1234") | |||
| name = req.name | |||
| phone1 = req.phone1 | |||
| // phone2 = req.phone2 ?: null | |||
| email = req.email ?: null | |||
| } | |||
| ) | |||
| @@ -142,7 +139,6 @@ open class StaffsService( | |||
| this.company = company | |||
| this.grade = grade | |||
| this.team = team | |||
| // this.skill = skill | |||
| this.salary = salary | |||
| this.department = department | |||
| } | |||
| @@ -158,31 +154,31 @@ open class StaffsService( | |||
| 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!!) | |||
| return staff | |||
| } | |||
| @Transactional(rollbackFor = [Exception::class]) | |||
| 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 joinPosition = positionRepository.findById(req.joinPositionId).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 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 salaryEffective = salaryEffectiveRepository.findById(req.salaryEffId).orElseThrow() | |||
| val department = departmentRepository.findById(req.departmentId).orElseThrow() | |||
| staff.apply { | |||
| @@ -203,7 +199,6 @@ open class StaffsService( | |||
| this.company = company | |||
| this.grade = grade | |||
| this.team = team | |||
| // this.skill = skill | |||
| this.salary = salary | |||
| this.department = department | |||
| } | |||
| @@ -162,4 +162,18 @@ open class TeamService( | |||
| ) | |||
| 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 | |||
| } | |||
| @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.timesheet.entity.Leave | |||
| 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 org.apache.commons.logging.Log | |||
| import org.apache.commons.logging.LogFactory | |||
| @@ -22,6 +25,7 @@ import org.springframework.stereotype.Service | |||
| import java.io.ByteArrayOutputStream | |||
| import java.io.IOException | |||
| import java.math.BigDecimal | |||
| import java.sql.Time | |||
| import java.time.LocalDate | |||
| import java.time.format.DateTimeFormatter | |||
| import java.util.* | |||
| @@ -44,9 +48,9 @@ open class ReportService( | |||
| // ==============================|| 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) | |||
| @@ -55,10 +59,10 @@ open class ReportService( | |||
| for (item in financialStatus){ | |||
| val normalConsumed = item.getValue("normalConsumed") as Double | |||
| 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) | |||
| println("manHourRate------------ $manHourRate") | |||
| // println("manHourRate------------ $manHourRate") | |||
| val otConsumed = item.getValue("otConsumed") as Double | |||
| 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() | |||
| workbook.write(outputStream) | |||
| @@ -105,10 +109,11 @@ open class ReportService( | |||
| fun generateProjectCashFlowReport( | |||
| project: Project, | |||
| invoices: List<Invoice>, | |||
| timesheets: List<Timesheet> | |||
| timesheets: List<Timesheet>, | |||
| dateType: String | |||
| ): ByteArray { | |||
| // 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 | |||
| val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() | |||
| @@ -122,9 +127,8 @@ open class ReportService( | |||
| fun generateStaffMonthlyWorkHourAnalysisReport( | |||
| month: LocalDate, | |||
| staff: Staff, | |||
| timesheets: List<Timesheet>, | |||
| leaves: List<Leave>, | |||
| projectList: List<String> | |||
| timesheets: List<Map<String, Any>>, | |||
| leaves: List<Map<String, Any>>, | |||
| ): ByteArray { | |||
| // Generate the Excel report with query results | |||
| val workbook: Workbook = createStaffMonthlyWorkHourAnalysisReport( | |||
| @@ -132,7 +136,6 @@ open class ReportService( | |||
| staff, | |||
| timesheets, | |||
| leaves, | |||
| projectList, | |||
| MONTHLY_WORK_HOURS_ANALYSIS_REPORT | |||
| ) | |||
| @@ -180,6 +183,7 @@ open class ReportService( | |||
| private fun createFinancialStatusReport( | |||
| templatePath: String, | |||
| projects: List<Map<String, Any>>, | |||
| teamLeadId: Long | |||
| ) : Workbook { | |||
| val resource = ClassPathResource(templatePath) | |||
| @@ -190,14 +194,50 @@ open class ReportService( | |||
| 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() | |||
| boldFont.bold = true | |||
| val boldFontCellStyle = workbook.createCellStyle() | |||
| 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){ | |||
| 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") | |||
| 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) | |||
| teamLeadCell.setCellValue(if (item["teamLead"] != null) item.getValue("teamLead").toString() else "N/A") | |||
| @@ -227,7 +270,6 @@ open class ReportService( | |||
| cellStyle.dataFormat = accountingStyle | |||
| } | |||
| val budgetCell = row.createCell(7) | |||
| budgetCell.apply { | |||
| cellFormula = "G${rowNum} * 80%" | |||
| @@ -259,14 +301,14 @@ open class ReportService( | |||
| val uninvoiceCell = row.createCell(11) | |||
| 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.dataFormat = accountingStyle | |||
| } | |||
| val cpiCell = row.createCell(12) | |||
| cpiCell.apply { | |||
| cellFormula = "K${rowNum}/I${rowNum}" | |||
| cellFormula = "IF(K${rowNum} = 0, 0, K${rowNum}/I${rowNum})" | |||
| } | |||
| val receivedAmountCell = row.createCell(13) | |||
| @@ -276,15 +318,14 @@ open class ReportService( | |||
| cellStyle.dataFormat = accountingStyle | |||
| } | |||
| val unsettledAmountCell = row.createCell(14) | |||
| unsettledAmountCell.apply { | |||
| cellFormula = "K${rowNum}-N${rowNum}" | |||
| cellStyle = boldFontCellStyle | |||
| cellStyle.dataFormat = accountingStyle | |||
| } | |||
| } | |||
| // Last row calculate the sum | |||
| val lastRowNum = rowNum + 1 | |||
| val row: Row = sheet.createRow(rowNum) | |||
| @@ -373,14 +414,27 @@ open class ReportService( | |||
| CellUtil.setCellStyleProperty(sumUnSettleCell,"borderTop", BorderStyle.THIN) | |||
| CellUtil.setCellStyleProperty(sumUnSettleCell,"borderBottom", BorderStyle.DOUBLE) | |||
| // 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) | |||
| if (teamLeadId < 0) { | |||
| row2Cell.setCellValue("All") | |||
| }else{ | |||
| row2Cell.apply { | |||
| cellFormula = "D15" | |||
| } | |||
| } | |||
| rowNum = 4 | |||
| val row4: Row = sheet.getRow(rowNum) | |||
| val row4Cell = row4.createCell(2) | |||
| row4Cell.setCellValue(projects.size.toString()) | |||
| rowNum = 5 | |||
| val row5: Row = sheet.getRow(rowNum) | |||
| @@ -446,6 +500,7 @@ open class ReportService( | |||
| project: Project, | |||
| invoices: List<Invoice>, | |||
| timesheets: List<Timesheet>, | |||
| dateType: String, | |||
| templatePath: String, | |||
| ): Workbook { | |||
| // please create a new function for each report template | |||
| @@ -531,7 +586,7 @@ open class ReportService( | |||
| 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 = | |||
| (invoices.map { it.receiptDate } + timesheets.map { it.recordDate }).filterNotNull().sortedBy { it } | |||
| .map { it.format(dateFormatter) }.distinct() | |||
| @@ -675,21 +730,35 @@ open class ReportService( | |||
| private fun createStaffMonthlyWorkHourAnalysisReport( | |||
| month: LocalDate, | |||
| staff: Staff, | |||
| timesheets: List<Timesheet>, | |||
| leaves: List<Leave>, | |||
| projectList: List<String>, | |||
| timesheets: List<Map<String, Any>>, | |||
| leaves: List<Map<String, Any>>, | |||
| templatePath: String, | |||
| ): 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 templateInputStream = resource.inputStream | |||
| val workbook: Workbook = XSSFWorkbook(templateInputStream) | |||
| 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 boldStyle = workbook.createCellStyle() | |||
| @@ -705,8 +774,6 @@ open class ReportService( | |||
| 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 columnIndex = 1 | |||
| @@ -723,7 +790,6 @@ open class ReportService( | |||
| rowIndex = 2 | |||
| sheet.getRow(rowIndex).getCell(columnIndex).apply { | |||
| setCellValue(month) | |||
| // cellStyle.setFont(boldStyle) | |||
| cellStyle.dataFormat = monthStyle | |||
| } | |||
| @@ -758,11 +824,9 @@ open class ReportService( | |||
| tempCell.setCellValue(dayInfo.date) | |||
| tempCell.cellStyle = boldStyle | |||
| tempCell.cellStyle.dataFormat = dateStyle | |||
| // cellStyle.alignment = HorizontalAlignment.LEFT | |||
| tempCell = sheet.getRow(rowIndex).createCell(1) | |||
| tempCell.setCellValue(dayInfo.weekday) | |||
| tempCell.cellStyle = boldStyle | |||
| // cellStyle.alignment = HorizontalAlignment.LEFT | |||
| } | |||
| rowIndex += 1 | |||
| @@ -784,10 +848,11 @@ open class ReportService( | |||
| var normalConsumed = 0.0 | |||
| var otConsumed = 0.0 | |||
| var leaveHours = 0.0 | |||
| // normalConsumed data | |||
| if (timesheets.isNotEmpty()) { | |||
| 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) | |||
| @@ -815,9 +880,10 @@ open class ReportService( | |||
| tempCell.cellStyle = boldStyle | |||
| CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) | |||
| sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1)) | |||
| // cal total leave hour | |||
| if (leaves.isNotEmpty()) { | |||
| leaves.forEach { l -> | |||
| leaveHours += l.leaveHours!! | |||
| leaveHours += l["leaveHours"] as Double | |||
| } | |||
| } | |||
| tempCell = sheet.getRow(rowIndex).createCell(2) | |||
| @@ -862,28 +928,30 @@ open class ReportService( | |||
| tempCell.setCellValue(0.0) | |||
| 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++ | |||
| } | |||
| } | |||
| // dates | |||
| // leave hours data | |||
| if (leaves.isNotEmpty()) { | |||
| leaves.forEach { leave -> | |||
| for (i in 0 until rowSize) { | |||
| tempCell = sheet.getRow(8 + i).createCell(columnIndex) | |||
| tempCell.setCellValue(0.0) | |||
| tempCell.cellStyle.dataFormat = accountingStyle | |||
| } | |||
| dayInt = leave.recordDate!!.dayOfMonth | |||
| dayInt = leave["recordDate"].toString().toInt() | |||
| tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) | |||
| tempCell.setCellValue(leave.leaveHours!!) | |||
| tempCell.setCellValue(leave["leaveHours"] as Double) | |||
| } | |||
| } | |||
| ///////////////////////////////////////////////////////// Leave Hours //////////////////////////////////////////////////////////////////// | |||
| @@ -1040,33 +1108,73 @@ open class ReportService( | |||
| return workbook | |||
| } | |||
| open fun getFinancialStatus(projectId: Long?): List<Map<String, Any>> { | |||
| open fun getFinancialStatus(teamLeadId: Long?): List<Map<String, Any>> { | |||
| 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" | |||
| + " left join project p on p.code = i.projectCode" | |||
| + " 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 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" | |||
| + " 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") | |||
| val args = mapOf("projectId" to projectId) | |||
| val args = mapOf("teamLeadId" to teamLeadId) | |||
| 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.timesheet.entity.LeaveRepository | |||
| import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository | |||
| import com.ffii.tsms.modules.timesheet.entity.projections.ProjectMonthlyHoursWithDate | |||
| import jakarta.validation.Valid | |||
| import org.springframework.core.io.ByteArrayResource | |||
| import org.springframework.core.io.Resource | |||
| @@ -57,8 +58,8 @@ class ReportController( | |||
| @Throws(ServletRequestBindingException::class, IOException::class) | |||
| 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() | |||
| .header("filename", "Financial Status Report - " + LocalDate.now() + ".xlsx") | |||
| @@ -74,7 +75,7 @@ class ReportController( | |||
| val invoices = invoiceService.findAllByProjectAndPaidAmountIsNotNull(project) | |||
| 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") | |||
| return ResponseEntity.ok() | |||
| // .contentType(mediaType) | |||
| @@ -89,14 +90,15 @@ class ReportController( | |||
| val nextMonth = request.yearMonth.plusMonths(1).atDay(1) | |||
| 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") | |||
| return ResponseEntity.ok() | |||
| // .contentType(mediaType) | |||
| @@ -166,7 +168,7 @@ fun downloadLateStartReport(@RequestBody @Valid request: LateStartReportRequest) | |||
| @GetMapping("/financialReport/{id}") | |||
| fun getFinancialReport(@PathVariable id: Long): List<Map<String, Any>> { | |||
| println(excelReportService.genFinancialStatusReport(id)) | |||
| // println(excelReportService.genFinancialStatusReport(id)) | |||
| return excelReportService.getFinancialStatus(id) | |||
| } | |||
| @@ -4,10 +4,11 @@ import java.time.LocalDate | |||
| import java.time.YearMonth | |||
| data class FinancialStatusReportRequest ( | |||
| val projectId: Long | |||
| val teamLeadId: Long | |||
| ) | |||
| data class ProjectCashFlowReportRequest ( | |||
| val projectId: Long | |||
| val projectId: Long, | |||
| val dateType: String, // "Date", "Month" | |||
| ) | |||
| data class StaffMonthlyWorkHourAnalysisReportRequest ( | |||
| @@ -2,7 +2,16 @@ package com.ffii.tsms.modules.timesheet.entity.projections | |||
| import java.time.LocalDate | |||
| data class MonthlyHours( | |||
| data class MonthlyLeave( | |||
| 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.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.ProjectTaskRepository | |||
| import com.ffii.tsms.modules.project.entity.TaskRepository | |||
| import com.ffii.tsms.modules.timesheet.entity.Timesheet | |||
| 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 org.springframework.stereotype.Service | |||
| import org.springframework.transaction.annotation.Transactional | |||
| import java.time.LocalDate | |||
| import java.time.format.DateTimeFormatter | |||
| import kotlin.jvm.optionals.getOrDefault | |||
| import kotlin.jvm.optionals.getOrNull | |||
| @Service | |||
| @@ -20,7 +23,8 @@ open class TimesheetsService( | |||
| private val projectTaskRepository: ProjectTaskRepository, | |||
| private val projectRepository: ProjectRepository, | |||
| private val taskRepository: TaskRepository, | |||
| private val staffsService: StaffsService | |||
| private val staffsService: StaffsService, | |||
| private val teamService: TeamService | |||
| ) { | |||
| @Transactional | |||
| open fun saveTimesheet(recordTimeEntry: Map<LocalDate, List<TimeEntry>>): Map<String, List<TimeEntry>> { | |||
| @@ -52,12 +56,56 @@ open class TimesheetsService( | |||
| 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>> { | |||
| // Need to be associated with a staff | |||
| val currentStaff = staffsService.currentStaff() ?: return emptyMap() | |||
| 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>> { | |||
| return timesheets | |||
| .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.TimesheetsService | |||
| 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 jakarta.validation.Valid | |||
| import org.springframework.web.bind.annotation.GetMapping | |||
| @@ -43,6 +45,18 @@ class TimesheetsController(private val timesheetsService: TimesheetsService, pri | |||
| 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") | |||
| fun getLeaveEntry(): Map<String, List<LeaveEntry>> { | |||
| 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 remarks; | |||
| @NotBlank | |||
| // @NotBlank | |||
| private String email; | |||
| // @NotBlank | |||
| @@ -153,19 +153,10 @@ public class UserController{ | |||
| @PatchMapping("/admin-change-password") | |||
| @ResponseStatus(HttpStatus.NO_CONTENT) | |||
| @PreAuthorize("hasAuthority('MAINTAIN_USER')") | |||
| public void adminChangePassword(@RequestBody @Valid ChangePwdReq req) { | |||
| public void adminChangePassword(@RequestBody @Valid AdminChangePwdReq req) { | |||
| long id = req.getId(); | |||
| 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())); | |||
| userService.save(instance); | |||
| } | |||
| @@ -188,6 +179,20 @@ public class UserController{ | |||
| 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 { | |||
| private Long id; | |||
| @NotBlank | |||