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