diff --git a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt index d3b2c09..eeeceba 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt @@ -509,8 +509,8 @@ open class DashboardService( + " p.name as projectName," + " t.code as team," + " s.name as teamLead," - + " p.actualStart as startDate," - + " p.planEnd as targetEndDate," + + " date_format(p.actualStart, '%Y-%m-%d') as startDate," + + " date_format(p.planEnd, '%Y-%m-%d') as targetEndDate," + " c.name as client," + " coalesce(s2.name,'N/A') as subsidiary" + " from project p" @@ -524,6 +524,390 @@ open class DashboardService( return jdbcDao.queryForList(sql.toString(), args) } + fun CashFlowMonthlyIncomeByMonth(args: Map): List> { + val sql = StringBuilder("select" + + " months.month as monthInvoice," + + " coalesce (invoice.invoiceMonth,'-') as invoiceMonth," + + " coalesce (invoice.income,0) as income," + + " SUM(COALESCE(invoice.income, 0)) OVER (ORDER BY months.month) AS cumulativeIncome" + + " FROM (" + + " SELECT 01 AS month" + + " UNION" + + " SELECT 02" + + " UNION" + + " SELECT 03" + + " UNION" + + " SELECT 04" + + " UNION" + + " SELECT 05" + + " UNION" + + " SELECT 06" + + " UNION" + + " SELECT 07" + + " UNION" + + " SELECT 08" + + " UNION" + + " SELECT 09" + + " UNION" + + " SELECT 10" + + " UNION" + + " SELECT 11" + + " union" + + " select 12" + + " ) AS months" + + " left join(" + + " select" + + " month(i.receiptDate) as invoiceMonth," + + " coalesce(sum(i.paidAmount),0) as income" + + " from project p" + + " left join invoice i on p.code = i.projectCode" + + " where p.status = 'On-going'" + + " and p.id in (:projectIds)" + + " and year(i.receiptDate) = :year" + + " and i.id is not null" + + " group by month(i.receiptDate)" + + " ) as invoice on months.month = invoice.invoiceMonth" + ) + + return jdbcDao.queryForList(sql.toString(), args) + } + + fun CashFlowMonthlyExpenditureByMonth(args: Map): List> { + val sql = StringBuilder("select" + + " months.month as monthExpenditure," + + " coalesce (expenditure.recordMonth,'-') as recordMonth," + + " coalesce (expenditure.expenditure,0) as expenditure," + + " SUM(COALESCE(expenditure.expenditure, 0)) OVER (ORDER BY months.month) AS cumulativeExpenditure" + + " FROM (" + + " SELECT 01 AS month" + + " UNION" + + " SELECT 02" + + " UNION" + + " SELECT 03" + + " UNION" + + " SELECT 04" + + " UNION" + + " SELECT 05" + + " UNION" + + " SELECT 06" + + " UNION" + + " SELECT 07" + + " UNION" + + " SELECT 08" + + " UNION" + + " SELECT 09" + + " UNION" + + " SELECT 10" + + " UNION" + + " SELECT 11" + + " union" + + " select 12" + + " ) AS months" + + " left join(" + + " SELECT" + + " r.recordMonth as recordMonth," + + " sum(r.cumulativeExpenditure) as expenditure" + + " from(" + + " select" + + " month(t.recordDate) as recordMonth," + + " (coalesce(sum(t.normalConsumed),0) * s2.hourlyRate) + (coalesce(sum(t.otConsumed),0) * s2.hourlyRate * 1.0) as cumulativeExpenditure" + + " from project p" + + " left join project_task pt on p.id = pt.project_id" + + " left join timesheet t on pt.id = t.projectTaskId" + + " left join staff s on t.staffId = s.id" + + " left join salary s2 on s.salaryId = s2.salaryPoint" + + " where t.id is not null" + + " and p.id in (:projectIds)" + + " and year(t.recordDate) = :year" + + " group by month(t.recordDate),s2.hourlyRate" + + " ) as r" + + " group by r.recordMonth" + + " ) as expenditure on months.month = expenditure.recordMonth" + ) + + return jdbcDao.queryForList(sql.toString(), args) + } + fun CashFlowReceivableAndExpenditure(args: Map): List> { + val sql = StringBuilder("select" + + " coalesce (round(sum(i.paidAmount)/sum(i.issueAmount)*100,0),0) as receivedPercentage," + + " coalesce (round(expenditure.expenditure/(sum(p.expectedTotalFee)*0.8)*100,0),0) as expenditurePercentage," + + " coalesce (sum(i.issueAmount),0) as totalInvoiced," + + " coalesce (sum(i.paidAmount),0) as totalReceived," + + " coalesce (sum(i.issueAmount) - sum(i.paidAmount),0) as receivable," + + " coalesce (round(sum(p.expectedTotalFee)*0.8,2),0) as totalBudget," + + " coalesce (expenditure.expenditure) as totalExpenditure," + + " coalesce (sum(p.expectedTotalFee)*0.8 - expenditure.expenditure,0) as expenditureReceivable" + + " from project p" + + " left join invoice i on p.code = i.projectCode" + + " left join(" + + " select" + + " sum(r.expenditure) as expenditure" + + " from(" + + " select" + + " (coalesce(sum(t.normalConsumed),0) * s2.hourlyRate) + (coalesce(sum(t.otConsumed),0) * s2.hourlyRate * 1.0) as expenditure" + + " from project p" + + " left join project_task pt on p.id = pt.project_id" + + " left join timesheet t on pt.id = t.projectTaskId" + + " left join staff s on t.staffId = s.id" + + " left join salary s2 on s.salaryId = s2.salaryPoint" + + " where t.id is not null" + + " and p.id in (:projectIds)" + + " group by s2.hourlyRate" + + " ) as r" + + " ) as expenditure on 1=1" + + " where p.id in (:projectIds)" + + " group by expenditure.expenditure" + ) + + return jdbcDao.queryForList(sql.toString(), args) + } + fun CashFlowAnticipateIncome(args: Map): List> { + val sql = StringBuilder("select" + + " months.month as monthanticipateIncome," + + " coalesce (anticipateIncome.anticipateIncomeDate,'-') as anticipateIncomeDate," + + " coalesce (anticipateIncome.anticipateIncome,0) as anticipateIncome" + + " FROM (" + + " SELECT 01 AS month" + + " UNION" + + " SELECT 02" + + " UNION" + + " SELECT 03" + + " UNION" + + " SELECT 04" + + " UNION" + + " SELECT 05" + + " UNION" + + " SELECT 06" + + " UNION" + + " SELECT 07" + + " UNION" + + " SELECT 08" + + " UNION" + + " SELECT 09" + + " UNION" + + " SELECT 10" + + " UNION" + + " SELECT 11" + + " union" + + " select 12" + + " ) AS months" + + " left join(" + + " select" + + " month(mp.date) as anticipateIncomeDate," + + " sum(mp.amount) as anticipateIncome" + + " from project p" + + " left join milestone m on p.id = m.projectId" + + " left join milestone_payment mp on m.id = mp.milestoneId" + + " where p.id in (:projectIds)" + + " and year(mp.date) = :year" + + " group by month(mp.date)" + + " ) as anticipateIncome on months.month = anticipateIncome.anticipateIncomeDate" + ) + + return jdbcDao.queryForList(sql.toString(), args) + } + fun CashFlowAnticipateExpenditure(args: Map): List> { + val sql = StringBuilder("select" + + " p.id, p.name," + + " date_format(p.planStart, '%Y-%m') as planStart," + + " date_format(p.planEnd, '%Y-%m') as planEnd," + + " case" + + " when year(p.planStart) < :year then 1" + + " when year(p.planStart) = :year then month(p.planStart)" + + " end as startMonth," + + " case" + + " when year(p.planStart) < :year and year(p.planEnd) > :year then 12" + + " when year(p.planStart) < :year and year(p.planEnd) = :year then month(p.planEnd)" + + " when year(p.planStart) = :year and year(p.planEnd) > :year then 12 - month(p.planStart)" + + " else PERIOD_DIFF(DATE_FORMAT(p.planEnd, '%Y%m'), DATE_FORMAT(p.planStart, '%Y%m'))+1" + + " end AS 'Duration'," + + " PERIOD_DIFF(DATE_FORMAT(p.planEnd, '%Y%m'), DATE_FORMAT(p.planStart, '%Y%m'))+1 as projectDuration," + + " p.expectedTotalFee*0.8 as totalBudget," + + " (p.expectedTotalFee*0.8) / (PERIOD_DIFF(DATE_FORMAT(p.planEnd, '%Y%m'), DATE_FORMAT(p.planStart, '%Y%m'))+1) as aniticipateExpenditure," + + " ROUND(p.totalManhour / (PERIOD_DIFF(DATE_FORMAT(p.planEnd, '%Y%m'), DATE_FORMAT(p.planStart, '%Y%m'))+1), 2) AS 'AverageManhours'," + + " p.teamLead, p.totalManhour" + + " FROM project p" + + " WHERE p.status = 'On-going'" + + " and p.id in (:projectIds)" + + " and (year(p.planStart) <= :year and year(p.planEnd) >= :year)" + + " order by teamLead, planStart" + ) + + return jdbcDao.queryForList(sql.toString(), args) + } + fun CashFlowLedger(args: Map): List> { + val sql = StringBuilder("select" + + " ROW_NUMBER() OVER (ORDER BY date, income, expenditure) AS id," + + " date," + + " COALESCE(ROUND(income, 2), 0) AS income," + + " COALESCE(ROUND(expenditure, 2), 0) AS expenditure," + + " ROUND(SUM(COALESCE(income, 0) - COALESCE(expenditure, 0)) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), 2) AS balance," + + " CASE" + + " WHEN income > 0 THEN paymentMilestone" + + " ELSE 'Monthly Manpower Expenditure'" + + " END AS remarks" + + " FROM" + + " (" + + " SELECT" + + " date_format(i.receiptDate, '%b %y') AS date," + + " sum(i.paidAmount) AS income," + + " NULL AS expenditure," + + " i.paymentMilestone AS paymentMilestone" + + " FROM" + + " project p" + + " LEFT JOIN invoice i ON p.code = i.projectCode" + + " WHERE" + + " p.id IN (:projectIds)" + + " AND i.paidAmount IS NOT NULL" + + " GROUP BY" + + " date_format(i.receiptDate, '%b %y')," + + " i.paymentMilestone" + + " UNION" + + " SELECT" + + " date_format(r.date, '%b %y') AS date," + + " NULL AS income," + + " sum(r.expenditure) AS expenditure," + + " NULL AS paymentMilestone" + + " FROM" + + " (" + + " SELECT" + + " t.recordDate AS date," + + " (COALESCE(SUM(t.normalConsumed), 0) * s2.hourlyRate) + (COALESCE(SUM(t.otConsumed), 0) * s2.hourlyRate * 1.0) AS expenditure" + + " FROM" + + " project p" + + " LEFT JOIN project_task pt ON p.id = pt.project_id" + + " LEFT JOIN timesheet t ON pt.id = t.projectTaskId" + + " LEFT JOIN staff s ON t.staffId = s.id" + + " LEFT JOIN salary s2 ON s.salaryId = s2.salaryPoint" + + " WHERE" + + " t.id IS NOT NULL" + + " AND p.id IN (:projectIds)" + + " GROUP BY" + + " s2.hourlyRate," + + " t.recordDate" + + " ) AS r" + + " GROUP BY" + + " date_format(r.date, '%b %y')" + + " ) AS combined_data" + + " ORDER BY" + + " date" + ) + + return jdbcDao.queryForList(sql.toString(), args) + } + fun TeamCashFlowIncome(args: Map): List> { + val sql = StringBuilder("select" + + " months.month as monthInvoice," + + " coalesce (invoice.invoiceMonth,'-') as invoiceMonth," + + " coalesce (invoice.income,0) as income," + + " SUM(COALESCE(invoice.income, 0)) OVER (ORDER BY months.month) AS cumulativeIncome" + + " FROM (" + + " SELECT 01 AS month" + + " UNION" + + " SELECT 02" + + " UNION" + + " SELECT 03" + + " UNION" + + " SELECT 04" + + " UNION" + + " SELECT 05" + + " UNION" + + " SELECT 06" + + " UNION" + + " SELECT 07" + + " UNION" + + " SELECT 08" + + " UNION" + + " SELECT 09" + + " UNION" + + " SELECT 10" + + " UNION" + + " SELECT 11" + + " union" + + " select 12" + + " ) AS months" + + " left join(" + + " select" + + " month(i.receiptDate) as invoiceMonth," + + " coalesce(sum(i.paidAmount),0) as income" + + " from project p" + + " left join team t on p.teamLead = t.teamLead" + + " left join invoice i on p.code = i.projectCode" + + " where p.status = 'On-going'" + + ) + + if (args != null) { + if (args.containsKey("teamId")) + sql.append(" AND t.id = :teamId") + } + sql.append(" and year(i.receiptDate) = :year" + + " and i.id is not null" + + " group by month(i.receiptDate)" + + " ) as invoice on months.month = invoice.invoiceMonth") + + return jdbcDao.queryForList(sql.toString(), args) + } + fun TeamCashFlowExpenditure(args: Map): List> { + val sql = StringBuilder("select" + + " months.month as monthExpenditure," + + " coalesce (expenditure.recordMonth,'-') as recordMonth," + + " coalesce (expenditure.expenditure,0) as expenditure," + + " SUM(COALESCE(expenditure.expenditure, 0)) OVER (ORDER BY months.month) AS cumulativeExpenditure" + + " FROM (" + + " SELECT 01 AS month" + + " UNION" + + " SELECT 02" + + " UNION" + + " SELECT 03" + + " UNION" + + " SELECT 04" + + " UNION" + + " SELECT 05" + + " UNION" + + " SELECT 06" + + " UNION" + + " SELECT 07" + + " UNION" + + " SELECT 08" + + " UNION" + + " SELECT 09" + + " UNION" + + " SELECT 10" + + " UNION" + + " SELECT 11" + + " union" + + " select 12" + + " ) AS months" + + " left join(" + + " SELECT" + + " r.recordMonth as recordMonth," + + " sum(r.cumulativeExpenditure) as expenditure" + + " from(" + + " select" + + " month(t.recordDate) as recordMonth," + + " (coalesce(sum(t.normalConsumed),0) * s2.hourlyRate) + (coalesce(sum(t.otConsumed),0) * s2.hourlyRate * 1.0) as cumulativeExpenditure" + + " from project p" + + " left join team t2 on p.teamLead = t2.teamLead" + + " left join project_task pt on p.id = pt.project_id" + + " left join timesheet t on pt.id = t.projectTaskId" + + " left join staff s on t.staffId = s.id" + + " left join salary s2 on s.salaryId = s2.salaryPoint" + + " where t.id is not null" + ) + if (args != null) { + if (args.containsKey("teamId")) + sql.append(" AND t2.id = :teamId") + } + sql.append(" and year(t.recordDate) = :year" + + " group by month(t.recordDate),s2.hourlyRate" + + " ) as r" + + " group by r.recordMonth" + + " ) as expenditure on months.month = expenditure.recordMonth") + + return jdbcDao.queryForList(sql.toString(), args) + } + } diff --git a/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt b/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt index b1893aa..22c9318 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt @@ -41,15 +41,15 @@ open class TeamService( val ids = req.addStaffIds!! // println(ids) val teamLead = staffRepository.findById(ids[0]).orElseThrow() - val teamName = "Team " + teamLead.name - - val initials = teamLead.name.split(" ").map { it.first() } - val teamCode = initials.joinToString("") +// val teamName = "Team " + teamLead.name +// +// val initials = teamLead.name.split(" ").map { it.first() } +// val teamCode = initials.joinToString("") val team = Team().apply { this.staff = teamLead - name = teamName - code = teamCode + name = req.name + code = req.code description = req.description } teamRepository.saveAndFlush(team) @@ -68,26 +68,23 @@ open class TeamService( val addIds = req.addStaffIds ?: listOf() val teamLead: Staff - val teamName: String - val teamCode: String +// val teamName: String +// val teamCode: String if (addIds.isNotEmpty()) { val leader = staffRepository.findById(addIds[0].toLong()).orElseThrow() - teamName = "Team " + leader.name +// teamName = "Team " + leader.name teamLead = leader; - - val initials = leader.name.split(" ").map { it.first() } - teamCode = initials.joinToString("") +// val initials = leader.name.split(" ").map { it.first() } +// teamCode = initials.joinToString("") } else { teamLead = team.staff - teamName = team.name - teamCode = team.code } team.apply { this.staff = teamLead - name = teamName - code = teamCode + name = req.name + code = req.code description = req.description } diff --git a/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt b/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt index 7926dcc..7167dfe 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt @@ -131,4 +131,80 @@ class DashboardController( val args = mutableMapOf() return dashboardService.CashFlowProject(args) } + @GetMapping("/searchCashFlowByMonth") + fun searchCashFlowByMonth(request: HttpServletRequest?): List> { + val args = mutableMapOf() + val projectIdList = request?.getParameter("projectIdList") + val year = request?.getParameter("year") + val projectIds = projectIdList?.split(",")?.map { it.toInt() }?.toList() + if (projectIds != null) { + args["projectIds"] = projectIds + } + if (year != null) { + args["year"] = year + } + val result = mutableMapOf() + val cashFlowMonthlyIncome = dashboardService.CashFlowMonthlyIncomeByMonth(args) + val cashFlowMonthlyExpenditure = dashboardService.CashFlowMonthlyExpenditureByMonth(args) + result["incomeList"] = cashFlowMonthlyIncome + result["expenditureList"] = cashFlowMonthlyExpenditure + return listOf(result) + } + @GetMapping("/searchCashFlowReceivableAndExpenditure") + fun searchCashFlowReceivableAndExpenditure(request: HttpServletRequest?): List> { + val args = mutableMapOf() + val projectIdList = request?.getParameter("projectIdList") + val projectIds = projectIdList?.split(",")?.map { it.toInt() }?.toList() + if (projectIds != null) { + args["projectIds"] = projectIds + } + return dashboardService.CashFlowReceivableAndExpenditure(args) + } + @GetMapping("/searchCashFlowAnticipate") + fun searchCashFlowAnticipate(request: HttpServletRequest?): List> { + val args = mutableMapOf() + val projectIdList = request?.getParameter("projectIdList") + val year = request?.getParameter("year") + val projectIds = projectIdList?.split(",")?.map { it.toInt() }?.toList() + if (projectIds != null) { + args["projectIds"] = projectIds + } + if (year != null) { + args["year"] = year + } + val result = mutableMapOf() + val cashFlowAnticipateIncome = dashboardService.CashFlowAnticipateIncome(args) + val cashFlowAnticipateExpenditure = dashboardService.CashFlowAnticipateExpenditure(args) + result["anticipateIncomeList"] = cashFlowAnticipateIncome + result["anticipateExpenditureList"] = cashFlowAnticipateExpenditure + return listOf(result) + } + @GetMapping("/searchCashFlowLedger") + fun searchCashFlowLedger(request: HttpServletRequest?): List> { + val args = mutableMapOf() + val projectIdList = request?.getParameter("projectIdList") + val projectIds = projectIdList?.split(",")?.map { it.toInt() }?.toList() + if (projectIds != null) { + args["projectIds"] = projectIds + } + return dashboardService.CashFlowLedger(args) + } + @GetMapping("/searchTeamCashFlow") + fun searchTeamCashFlow(request: HttpServletRequest?): List> { + val args = mutableMapOf() + val teamId = request?.getParameter("teamId") + val year = request?.getParameter("year") + if (teamId != null) { + args["teamId"] = teamId + } + if (year != null) { + args["year"] = year + } + val result = mutableMapOf() + val teamCashFlowIncome = dashboardService.TeamCashFlowIncome(args) + val teamCashFlowExpenditure = dashboardService.TeamCashFlowExpenditure(args) + result["teamCashFlowIncome"] = teamCashFlowIncome + result["teamCashFlowExpenditure"] = teamCashFlowExpenditure + return listOf(result) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/data/web/TeamController.kt b/src/main/java/com/ffii/tsms/modules/data/web/TeamController.kt index bb00181..95d9831 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/TeamController.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/TeamController.kt @@ -1,8 +1,11 @@ package com.ffii.tsms.modules.data.web +import com.ffii.core.exception.NotFoundException import com.ffii.core.response.RecordsRes import com.ffii.core.utils.CriteriaArgsBuilder +import com.ffii.core.utils.Params import com.ffii.tsms.modules.data.entity.Team +import com.ffii.tsms.modules.data.service.StaffsService import com.ffii.tsms.modules.data.service.TeamService import com.ffii.tsms.modules.data.web.models.NewTeamRequest import jakarta.servlet.http.HttpServletRequest @@ -15,7 +18,10 @@ import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/team") -class TeamController(private val teamService: TeamService) { +class TeamController( + private val teamService: TeamService, + private val staffsService: StaffsService, + ) { @GetMapping fun allStaff(args: Map): List> { @@ -34,6 +40,21 @@ class TeamController(private val teamService: TeamService) { return teamService.getTeamDetail(args); } + @GetMapping("/{id}") + fun getStaff(@PathVariable id: Long): Map { + val staffList = staffsService.findAllByTeamId(id).orElseThrow { NotFoundException() } + val staffIdList: MutableList = mutableListOf() + for (staff in staffList) { + staffIdList.add(staff.id as Long) + } +// val map: Map = java.util.Map.of("team" to teamService.find(id).orElseThrow { NotFoundException() }) +// map["staffIds"] = staffIdList + return java.util.Map.of( + "team", staffsService.find(id).orElseThrow { NotFoundException() }, + "staffIds", staffIdList + ) + } + // @Transactional(rollbackFor = [Exception::class]) @DeleteMapping("/delete/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) diff --git a/src/main/java/com/ffii/tsms/modules/data/web/models/NewStaffRequest.kt b/src/main/java/com/ffii/tsms/modules/data/web/models/NewStaffRequest.kt index c5316f5..9ef5d8c 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/models/NewStaffRequest.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/models/NewStaffRequest.kt @@ -13,21 +13,21 @@ data class NewStaffRequest( val companyId: Long, @field:NotNull(message = "Staff salaryId cannot be empty") val salaryId: Long, - @field:NotNull(message = "joinDate cannot be empty") +// @field:NotNull(message = "joinDate cannot be empty") val joinDate: LocalDate, @field:NotNull(message = "Staff currentPositionId cannot be empty") val currentPositionId: Long, - @field:NotNull(message = "Staff joinPositionId cannot be empty") +// @field:NotNull(message = "Staff joinPositionId cannot be empty") val joinPositionId: Long, - @field:NotNull(message = "Staff departmentId cannot be empty") +// @field:NotNull(message = "Staff departmentId cannot be empty") val departmentId: Long, @field:NotBlank(message = "Staff phone1 cannot be empty") val phone1: String, @field:NotBlank(message = "Staff email cannot be empty") val email: String, - @field:NotBlank(message = "Staff emergContactName cannot be empty") +// @field:NotBlank(message = "Staff emergContactName cannot be empty") val emergContactName: String, - @field:NotBlank(message = "Staff emergContactPhone cannot be empty") +// @field:NotBlank(message = "Staff emergContactPhone cannot be empty") val emergContactPhone: String, @field:NotBlank(message = "Staff employType cannot be empty") val employType: String, diff --git a/src/main/java/com/ffii/tsms/modules/data/web/models/NewTeamRequest.kt b/src/main/java/com/ffii/tsms/modules/data/web/models/NewTeamRequest.kt index 224a181..c320a7d 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/models/NewTeamRequest.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/models/NewTeamRequest.kt @@ -6,7 +6,8 @@ import java.time.LocalDate data class NewTeamRequest ( val addStaffIds: List?, - + val name: String, + val code: String, val deleteStaffIds: List?, val description: String?, val id: Long? diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt index 57d1720..8f367a8 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt @@ -1,10 +1,13 @@ package com.ffii.tsms.modules.project.entity; import com.ffii.core.support.AbstractRepository +import com.ffii.tsms.modules.data.entity.Customer +import com.ffii.tsms.modules.data.entity.Staff import com.ffii.tsms.modules.project.entity.projections.InvoiceInfoSearchInfo import com.ffii.tsms.modules.project.entity.projections.InvoiceSearchInfo import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo import org.springframework.data.jpa.repository.Query +import org.springframework.lang.Nullable import java.io.Serializable import java.time.LocalDate @@ -25,8 +28,10 @@ interface ProjectRepository : AbstractRepository { "") fun getLatestCodeNumberByMainProject(isClpProject: Boolean, id: Serializable?): Long? - @Query("SELECT max(case when length(p.code) - length(replace(p.code, '-', '')) > 1 then cast(substring_index(p.code, '-', -1) as long) end) FROM Project p WHERE p.code like ?1 and p.id != ?2") + @Query("SELECT max(case when length(p.code) - length(replace(p.code, '-', '')) > 1 then cast(substring_index(p.code, '-', -1) as long) end) FROM Project p WHERE p.code like %?1% and p.id != ?2") fun getLatestCodeNumberBySubProject(code: String, id: Serializable?): Long? fun findAllByStatusIsNotAndMainProjectIsNull(status: String): List + + fun findAllByTeamLeadAndCustomer(teamLead: Staff, customer: Customer): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectTaskRepository.kt b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectTaskRepository.kt index b1b57e0..085ed7e 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectTaskRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectTaskRepository.kt @@ -5,5 +5,6 @@ import com.ffii.core.support.AbstractRepository interface ProjectTaskRepository : AbstractRepository { fun findAllByProject(project: Project): List + fun findAllByProjectIn(projects: List): List fun findByProjectAndTask(project: Project, task: Task): ProjectTask? } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt index ab4d097..f49f190 100644 --- a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt @@ -5,8 +5,11 @@ import com.ffii.tsms.modules.data.entity.Customer import com.ffii.tsms.modules.data.entity.Salary import com.ffii.tsms.modules.data.entity.Staff import com.ffii.tsms.modules.data.entity.Team +import com.ffii.tsms.modules.project.entity.GradeAllocation import com.ffii.tsms.modules.project.entity.Invoice +import com.ffii.tsms.modules.project.entity.Milestone import com.ffii.tsms.modules.project.entity.Project +import com.ffii.tsms.modules.report.web.model.costAndExpenseRequest 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 @@ -38,6 +41,7 @@ import java.time.ZoneId import java.time.format.DateTimeParseException import java.time.temporal.ChronoUnit import java.util.* +import kotlin.jvm.optionals.getOrElse data class DayInfo(val date: String?, val weekday: String?) @@ -51,17 +55,44 @@ open class ReportService( private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") private val FORMATTED_TODAY = LocalDate.now().format(DATE_FORMATTER) + private val COSTANDEXPENSE_REPORT = "templates/report/AR04_Cost and Expense Report v02.xlsx" private val PandL_REPORT = "templates/report/AR07_Project P&L Report v02.xlsx" private val FINANCIAL_STATUS_REPORT = "templates/report/EX01_Financial Status Report.xlsx" private val PROJECT_CASH_FLOW_REPORT = "templates/report/EX02_Project Cash Flow Report.xlsx" + private val PROJECT_POTENTIAL_DELAY_REPORT = "templates/report/AR02_Delay Report v02.xlsx" private val MONTHLY_WORK_HOURS_ANALYSIS_REPORT = "templates/report/AR08_Monthly Work Hours Analysis Report.xlsx" private val SALART_LIST_TEMPLATE = "templates/report/Salary Template.xlsx" private val LATE_START_REPORT = "templates/report/AR01_Late Start Report v01.xlsx" private val RESOURCE_OVERCONSUMPTION_REPORT = "templates/report/AR03_Resource Overconsumption.xlsx" - private val COMPLETE_PROJECT_OUTSTANDING_RECEIVABLE = "templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx" + private val COMPLETE_PROJECT_OUTSTANDING_RECEIVABLE = + "templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx" private val COMPLETION_PROJECT = "templates/report/AR05_Project Completion Report.xlsx" // ==============================|| GENERATE REPORT ||============================== // + fun generalCreateReportIndexed( // just loop through query records one by one, return rowIndex + sheet: Sheet, + result: List>, + startRow: Int, + startColumn: Int + ): Int { + var rowIndex = startRow + var columnIndex = startColumn + result.forEachIndexed { index, obj -> + var tempCell = sheet.getRow(rowIndex).createCell(columnIndex) + tempCell.setCellValue((index + 1).toDouble()) + val keys = obj.keys.toList() + keys.forEachIndexed { keyIndex, key -> + tempCell = sheet.getRow(rowIndex).getCell(columnIndex + keyIndex + 1) ?: sheet.getRow(rowIndex) + .createCell(columnIndex + keyIndex + 1) + when (obj[key]) { + is Double -> tempCell.setCellValue(obj[key] as Double) + else -> tempCell.setCellValue(obj[key] as String) + } + } + rowIndex++ + } + return rowIndex + } fun genFinancialStatusReport(teamLeadId: Long): ByteArray { @@ -71,33 +102,35 @@ open class ReportService( val tempList = mutableListOf>() - for (item in financialStatus){ - val normalConsumed = item.getValue("normalConsumed") as Double - val hourlyRate = item.getValue("hourlyRate") as BigDecimal + 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 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{ + 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")) }!! @@ -128,7 +161,36 @@ open class ReportService( dateType: String ): ByteArray { // Generate the Excel report with query results - val workbook: Workbook = createProjectCashFlowReport(project, invoices, timesheets, dateType, 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() + workbook.write(outputStream) + workbook.close() + + return outputStream.toByteArray() + } + + @Throws(IOException::class) + fun generateProjectPotentialDelayReport( + searchedTeam: String, + searchedClient: String, + projects: List, + timesheets: List, + numberOfDays: Int, + projectCompletion: Int, + ): ByteArray { + // Generate the Excel report with query results + val workbook: Workbook = createProjectPotentialDelayReport( + searchedTeam, + searchedClient, + projects, + timesheets, + numberOfDays, + projectCompletion, + PROJECT_POTENTIAL_DELAY_REPORT + ) // Write the workbook to a ByteArrayOutputStream val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() @@ -184,6 +246,7 @@ open class ReportService( return outputStream.toByteArray() } + @Throws(IOException::class) fun generateProjectCompletionReport( args: MutableMap, @@ -245,7 +308,6 @@ open class ReportService( clientId, projectDetails ) - val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() workbook.write(outputStream) workbook.close() @@ -260,20 +322,20 @@ open class ReportService( templatePath: String, projects: List>, teamLeadId: Long - ) : Workbook { + ): Workbook { val resource = ClassPathResource(templatePath) val templateInputStream = resource.inputStream val workbook: Workbook = XSSFWorkbook(templateInputStream) - val accountingStyle = workbook.createDataFormat() .getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") + val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") 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) + sheet.setColumnWidth(2, 20 * 256) + sheet.setColumnWidth(3, 45 * 256) + sheet.setColumnWidth(4, 15 * 256) val boldFont = sheet.workbook.createFont() boldFont.bold = true @@ -282,7 +344,7 @@ open class ReportService( boldFontCellStyle.setFont(boldFont) var rowNum = 14 - if (projects.isEmpty()){ + if (projects.isEmpty()) { // Fill the cell in Row 2-12 with thr calculated sum rowNum = 1 val row1: Row = sheet.getRow(rowNum) @@ -292,12 +354,13 @@ open class ReportService( 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 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() @@ -315,14 +378,16 @@ open class ReportService( return workbook } - for(item in projects){ + for (item in projects) { val row: Row = sheet.createRow(rowNum++) val codeCell = row.createCell(0) codeCell.setCellValue(if (item["code"] != null) item.getValue("code").toString() else "N/A") val descriptionCell = row.createCell(1) - 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) clientCell.apply { @@ -356,7 +421,7 @@ open class ReportService( val normalConsumed = item["normalConsumed"]?.let { it as BigDecimal } ?: BigDecimal(0) val otConsumed = item["otConsumed"]?.let { it as BigDecimal } ?: BigDecimal(0) val cumExpenditure = normalConsumed.add(otConsumed) - cumExpenditureCell.apply{ + cumExpenditureCell.apply { setCellValue(cumExpenditure.toDouble()) cellStyle.dataFormat = accountingStyle } @@ -377,7 +442,8 @@ open class ReportService( val uninvoiceCell = row.createCell(11) uninvoiceCell.apply { - cellFormula = " IF(H${rowNum}<=I${rowNum}, H${rowNum}-K${rowNum}, IF(AND(H${rowNum}>I${rowNum}, I${rowNum}I${rowNum}, I${rowNum}>=K${rowNum}), I${rowNum}-K${rowNum}, 0))) " + cellFormula = + " IF(H${rowNum}<=I${rowNum}, H${rowNum}-K${rowNum}, IF(AND(H${rowNum}>I${rowNum}, I${rowNum}I${rowNum}, I${rowNum}>=K${rowNum}), I${rowNum}-K${rowNum}, 0))) " cellStyle = boldFontCellStyle cellStyle.dataFormat = accountingStyle } @@ -389,7 +455,7 @@ open class ReportService( val receivedAmountCell = row.createCell(13) val paidAmount = item["paidAmount"]?.let { it as BigDecimal } ?: BigDecimal(0) - receivedAmountCell.apply{ + receivedAmountCell.apply { setCellValue(paidAmount.toDouble()) cellStyle.dataFormat = accountingStyle } @@ -405,18 +471,18 @@ open class ReportService( val lastRowNum = rowNum + 1 val row: Row = sheet.createRow(rowNum) - for(i in 0..4){ + for (i in 0..4) { val cell = row.createCell(i) - CellUtil.setCellStyleProperty(cell,"borderTop", BorderStyle.THIN) - CellUtil.setCellStyleProperty(cell,"borderBottom", BorderStyle.DOUBLE) + CellUtil.setCellStyleProperty(cell, "borderTop", BorderStyle.THIN) + CellUtil.setCellStyleProperty(cell, "borderBottom", BorderStyle.DOUBLE) } val subTotalCell = row.createCell(5) subTotalCell.apply { setCellValue("Sub-total:") } - CellUtil.setCellStyleProperty(subTotalCell,"borderTop", BorderStyle.THIN) - CellUtil.setCellStyleProperty(subTotalCell,"borderBottom", BorderStyle.DOUBLE) + CellUtil.setCellStyleProperty(subTotalCell, "borderTop", BorderStyle.THIN) + CellUtil.setCellStyleProperty(subTotalCell, "borderBottom", BorderStyle.DOUBLE) val sumTotalFeeCell = row.createCell(6) @@ -424,24 +490,24 @@ open class ReportService( cellFormula = "SUM(G15:G${rowNum})" cellStyle.dataFormat = accountingStyle } - CellUtil.setCellStyleProperty(sumTotalFeeCell,"borderTop", BorderStyle.THIN) - CellUtil.setCellStyleProperty(sumTotalFeeCell,"borderBottom", BorderStyle.DOUBLE) + CellUtil.setCellStyleProperty(sumTotalFeeCell, "borderTop", BorderStyle.THIN) + CellUtil.setCellStyleProperty(sumTotalFeeCell, "borderBottom", BorderStyle.DOUBLE) val sumBudgetCell = row.createCell(7) sumBudgetCell.apply { cellFormula = "SUM(H15:H${rowNum})" cellStyle.dataFormat = accountingStyle } - CellUtil.setCellStyleProperty(sumBudgetCell,"borderTop", BorderStyle.THIN) - CellUtil.setCellStyleProperty(sumBudgetCell,"borderBottom", BorderStyle.DOUBLE) + CellUtil.setCellStyleProperty(sumBudgetCell, "borderTop", BorderStyle.THIN) + CellUtil.setCellStyleProperty(sumBudgetCell, "borderBottom", BorderStyle.DOUBLE) val sumCumExpenditureCell = row.createCell(8) sumCumExpenditureCell.apply { cellFormula = "SUM(I15:I${rowNum})" cellStyle.dataFormat = accountingStyle } - CellUtil.setCellStyleProperty(sumCumExpenditureCell,"borderTop", BorderStyle.THIN) - CellUtil.setCellStyleProperty(sumCumExpenditureCell,"borderBottom", BorderStyle.DOUBLE) + CellUtil.setCellStyleProperty(sumCumExpenditureCell, "borderTop", BorderStyle.THIN) + CellUtil.setCellStyleProperty(sumCumExpenditureCell, "borderBottom", BorderStyle.DOUBLE) val sumBudgetVCell = row.createCell(9) sumBudgetVCell.apply { @@ -449,16 +515,16 @@ open class ReportService( cellStyle = boldFontCellStyle cellStyle.dataFormat = accountingStyle } - CellUtil.setCellStyleProperty(sumBudgetVCell,"borderTop", BorderStyle.THIN) - CellUtil.setCellStyleProperty(sumBudgetVCell,"borderBottom", BorderStyle.DOUBLE) + CellUtil.setCellStyleProperty(sumBudgetVCell, "borderTop", BorderStyle.THIN) + CellUtil.setCellStyleProperty(sumBudgetVCell, "borderBottom", BorderStyle.DOUBLE) val sumIInvoiceCell = row.createCell(10) sumIInvoiceCell.apply { cellFormula = "SUM(K15:K${rowNum})" cellStyle.dataFormat = accountingStyle } - CellUtil.setCellStyleProperty(sumIInvoiceCell,"borderTop", BorderStyle.THIN) - CellUtil.setCellStyleProperty(sumIInvoiceCell,"borderBottom", BorderStyle.DOUBLE) + CellUtil.setCellStyleProperty(sumIInvoiceCell, "borderTop", BorderStyle.THIN) + CellUtil.setCellStyleProperty(sumIInvoiceCell, "borderBottom", BorderStyle.DOUBLE) val sumUInvoiceCell = row.createCell(11) sumUInvoiceCell.apply { @@ -466,20 +532,20 @@ open class ReportService( cellStyle = boldFontCellStyle cellStyle.dataFormat = accountingStyle } - CellUtil.setCellStyleProperty(sumUInvoiceCell,"borderTop", BorderStyle.THIN) - CellUtil.setCellStyleProperty(sumUInvoiceCell,"borderBottom", BorderStyle.DOUBLE) + CellUtil.setCellStyleProperty(sumUInvoiceCell, "borderTop", BorderStyle.THIN) + CellUtil.setCellStyleProperty(sumUInvoiceCell, "borderBottom", BorderStyle.DOUBLE) val lastCpiCell = row.createCell(12) - CellUtil.setCellStyleProperty(lastCpiCell,"borderTop", BorderStyle.THIN) - CellUtil.setCellStyleProperty(lastCpiCell,"borderBottom", BorderStyle.DOUBLE) + CellUtil.setCellStyleProperty(lastCpiCell, "borderTop", BorderStyle.THIN) + CellUtil.setCellStyleProperty(lastCpiCell, "borderBottom", BorderStyle.DOUBLE) val sumRAmountCell = row.createCell(13) sumRAmountCell.apply { cellFormula = "SUM(N15:N${rowNum})" cellStyle.dataFormat = accountingStyle } - CellUtil.setCellStyleProperty(sumRAmountCell,"borderTop", BorderStyle.THIN) - CellUtil.setCellStyleProperty(sumRAmountCell,"borderBottom", BorderStyle.DOUBLE) + CellUtil.setCellStyleProperty(sumRAmountCell, "borderTop", BorderStyle.THIN) + CellUtil.setCellStyleProperty(sumRAmountCell, "borderBottom", BorderStyle.DOUBLE) val sumUnSettleCell = row.createCell(14) sumUnSettleCell.apply { @@ -487,8 +553,8 @@ open class ReportService( cellStyle = boldFontCellStyle cellStyle.dataFormat = accountingStyle } - CellUtil.setCellStyleProperty(sumUnSettleCell,"borderTop", BorderStyle.THIN) - CellUtil.setCellStyleProperty(sumUnSettleCell,"borderBottom", BorderStyle.DOUBLE) + 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 @@ -501,7 +567,7 @@ open class ReportService( val row2Cell = row2.createCell(2) if (teamLeadId < 0) { row2Cell.setCellValue("All") - }else{ + } else { row2Cell.apply { cellFormula = "D15" } @@ -662,7 +728,8 @@ open class ReportService( rowIndex = 15 - val dateFormatter = if (dateType == "Date") DateTimeFormatter.ofPattern("yyyy/MM/dd") else 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() @@ -671,9 +738,11 @@ open class ReportService( .mapValues { (_, timesheetEntries) -> timesheetEntries.map { timesheet -> if (timesheet.normalConsumed != null) { - timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0).times(timesheet.staff!!.salary.hourlyRate.toDouble()) + timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) + .times(timesheet.staff!!.salary.hourlyRate.toDouble()) } else if (timesheet.otConsumed != null) { - timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0).times(timesheet.staff!!.salary.hourlyRate.toDouble()) + timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) + .times(timesheet.staff!!.salary.hourlyRate.toDouble()) } else { 0.0 } @@ -710,7 +779,7 @@ open class ReportService( if (groupedInvoices.containsKey(result)) { groupedInvoices[result]!!.forEachIndexed { _, invoice -> - sheet.getRow(rowIndex++).apply { + sheet.createRow(rowIndex++).apply { createCell(0).apply { setCellValue(result) } @@ -748,7 +817,7 @@ open class ReportService( } if (groupedTimesheets.containsKey(result)) { - sheet.getRow(rowIndex++).apply { + sheet.createRow(rowIndex++).apply { createCell(0).apply { setCellValue(result.format(dateFormatter)) @@ -788,6 +857,139 @@ open class ReportService( return workbook } + @Throws(IOException::class) + private fun createProjectPotentialDelayReport( + searchedTeam: String, + searchedClient: String, + projects: List, + timesheets: List, + numberOfDays: Int, + projectCompletion: Int, + templatePath: String, + ): Workbook { + // please create a new function for each report template + val resource = ClassPathResource(templatePath) + val templateInputStream = resource.inputStream + val workbook: Workbook = XSSFWorkbook(templateInputStream) + + val sheet: Sheet = workbook.getSheetAt(0) + + var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field + var columnIndex = 2 + sheet.getRow(rowIndex).createCell(columnIndex).apply { + setCellValue(FORMATTED_TODAY) + } + + rowIndex = 2 + sheet.getRow(rowIndex).createCell(columnIndex).apply { + setCellValue(searchedTeam) + } + + rowIndex = 3 + sheet.getRow(rowIndex).createCell(columnIndex).apply { + setCellValue(searchedClient) + } + + val groupedTimesheets = timesheets + .groupBy { timesheetEntry -> + Pair( + timesheetEntry.projectTask?.project?.id, + timesheetEntry.projectTask?.milestone?.id + ) + } + .mapValues { (_, timesheetEntries) -> + timesheetEntries.map { timesheet -> + if (timesheet.normalConsumed != null) { + timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) + } else if (timesheet.otConsumed != null) { + timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) + } else { + 0.0 + } + } + } + +// groupedTimesheets.entries.forEach { (key, value) -> +// logger.info("key: $key") +// logger.info(key == Pair(1L, 1L)) +// logger.info("value: " + value.sumOf { it }) +// } + + rowIndex = 6 + projects.forEach { project: Project -> + + sheet.createRow(rowIndex).apply { + createCell(0).apply { + setCellValue((rowIndex - 5).toString()) + } + + createCell(1).apply { + setCellValue(project.code) + } + + createCell(2).apply { + setCellValue(project.name) + } + + createCell(3).apply { + val currentTeam = project.teamLead?.team + setCellValue(currentTeam?.code + " - " + currentTeam?.name) + } + + createCell(4).apply { + val currentClient = project.customer + setCellValue(currentClient?.code + " - " + currentClient?.name) + } + + createCell(5).apply { + setCellValue(project.actualStart?.format(DATE_FORMATTER)) + } + + createCell(6).apply { + setCellValue(project.planEnd?.format(DATE_FORMATTER)) + } + } + + project.milestones.forEach { milestone: Milestone -> + + val manHoursSpent = groupedTimesheets[Pair(project.id, milestone.id)]?.sum() ?: 0.0 + val resourceUtilization = manHoursSpent / (milestone.stagePercentAllocation!! / 100 * project.totalManhour!!) +// logger.info(project.name + " : " + milestone.taskGroup?.name + " : " + ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate)) +// logger.info(numberOfDays) + if (ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate) <= numberOfDays.toLong() && resourceUtilization <= projectCompletion.toDouble() / 100.0) { + val tempRow = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex) + rowIndex++ + + tempRow.apply { + createCell(7).apply { + setCellValue(milestone.taskGroup?.name ?: "N/A") + } + + createCell(8).apply { + setCellValue(milestone.endDate?.format(DATE_FORMATTER) ?: "N/A") + } + + createCell(9).apply { + cellStyle.dataFormat = workbook.createDataFormat().getFormat("0.00%") + +// if (groupedTimesheets.containsKey(Pair(project.id, milestone.id))) { +// val manHoursSpent = groupedTimesheets[Pair(project.id, milestone.id)]!!.sum() +// +// val resourceUtilization = +// manHoursSpent / (milestone.stagePercentAllocation!! / 100 * project.totalManhour!!) + setCellValue(resourceUtilization) +// } else { +// setCellValue(0.0) +// } + } + } + } + } + } + + return workbook + } + fun getColumnAlphabet(colIndex: Int): String { val alphabet = StringBuilder() @@ -819,14 +1021,16 @@ open class ReportService( // result = timesheet record mapped var result: Map = mapOf() if (timesheets.isNotEmpty()) { - projectList = timesheets.map{ "${it["code"]}\n ${it["name"]}"}.toList().distinct() + projectList = timesheets.map { "${it["code"]}\n ${it["name"]}" }.toList().distinct() result = timesheets.groupBy( { it["id"].toString() }, - { mapOf( - "date" to it["recordDate"], - "normalConsumed" to it["normalConsumed"], - "otConsumed" to it["otConsumed"], - ) } + { + mapOf( + "date" to it["recordDate"], + "normalConsumed" to it["normalConsumed"], + "otConsumed" to it["otConsumed"], + ) + } ) } println("---result---") @@ -910,7 +1114,7 @@ open class ReportService( rowIndex += 1 tempCell = sheet.getRow(rowIndex).createCell(0) - sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1)) + sheet.addMergedRegion(CellRangeAddress(rowIndex, rowIndex, 0, 1)) tempCell.setCellValue("Sub-total") tempCell.cellStyle = boldStyle CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) @@ -938,7 +1142,7 @@ open class ReportService( tempCell.setCellValue(normalConsumed) tempCell.cellStyle.dataFormat = accountingStyle - sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1)) + sheet.addMergedRegion(CellRangeAddress(rowIndex, rowIndex, 0, 1)) // rowIndex += 1 tempCell = sheet.getRow(rowIndex).createCell(0) @@ -958,7 +1162,7 @@ open class ReportService( tempCell.setCellValue("Total Leave Hours") tempCell.cellStyle = boldStyle 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()) { leaves.forEach { l -> @@ -981,11 +1185,11 @@ open class ReportService( CellUtil.setCellStyleProperties(tempCell, DoubleBorderBottom) tempCell = sheet.getRow(rowIndex).createCell(2) - tempCell.cellFormula = "C${rowIndex-2}+C${rowIndex-1}" + tempCell.cellFormula = "C${rowIndex - 2}+C${rowIndex - 1}" tempCell.cellStyle.dataFormat = accountingStyle CellUtil.setCellStyleProperties(tempCell, DoubleBorderBottom) - sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1)) + sheet.addMergedRegion(CellRangeAddress(rowIndex, rowIndex, 0, 1)) ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// rowIndex = 7 columnIndex = 2 @@ -1009,13 +1213,13 @@ open class ReportService( tempCell.cellStyle.dataFormat = accountingStyle } } - result.forEach{ (id, list) -> + result.forEach { (id, list) -> val temp: List> = list as List> - temp.forEachIndexed { index, _ -> - dayInt = temp[index]["date"].toString().toInt() - tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) - tempCell.setCellValue((temp[index]["normalConsumed"] as Double) + (temp[index]["otConsumed"] as Double)) - } + temp.forEachIndexed { index, _ -> + dayInt = temp[index]["date"].toString().toInt() + tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) + tempCell.setCellValue((temp[index]["normalConsumed"] as Double) + (temp[index]["otConsumed"] as Double)) + } columnIndex++ } } @@ -1032,34 +1236,35 @@ open class ReportService( tempCell.setCellValue(leave["leaveHours"] as Double) } } - ///////////////////////////////////////////////////////// Leave Hours title //////////////////////////////////////////////////////////////////// - tempCell = sheet.getRow(rowIndex).createCell(columnIndex) - tempCell.setCellValue("Leave Hours") - tempCell.cellStyle = boldStyle - CellUtil.setVerticalAlignment(tempCell, VerticalAlignment.CENTER) - CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) - CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom) + ///////////////////////////////////////////////////////// Leave Hours title //////////////////////////////////////////////////////////////////// + tempCell = sheet.getRow(rowIndex).createCell(columnIndex) + tempCell.setCellValue("Leave Hours") + tempCell.cellStyle = boldStyle + CellUtil.setVerticalAlignment(tempCell, VerticalAlignment.CENTER) + CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) + CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom) columnIndex += 1 - tempCell = sheet.getRow(rowIndex).createCell(columnIndex) - tempCell.setCellValue("Daily Manhour Spent\n(Excluding Leave Hours)") - tempCell.cellStyle = boldStyle - CellUtil.setAlignment(tempCell, HorizontalAlignment.LEFT) - CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom) + tempCell = sheet.getRow(rowIndex).createCell(columnIndex) + tempCell.setCellValue("Daily Manhour Spent\n(Excluding Leave Hours)") + tempCell.cellStyle = boldStyle + CellUtil.setAlignment(tempCell, HorizontalAlignment.LEFT) + CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom) - sheet.addMergedRegion(CellRangeAddress(6,6 , 2, columnIndex)) + sheet.addMergedRegion(CellRangeAddress(6, 6, 2, columnIndex)) ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// rowIndex = 8 if (sheet.getRow(rowIndex - 1).getCell(2).stringCellValue != "Leave Hours") { // cal daily spent manhour for (i in 0 until rowSize) { tempCell = sheet.getRow(rowIndex).createCell(columnIndex) - tempCell.cellFormula = "SUM(${getColumnAlphabet(2)}${rowIndex+1}:${getColumnAlphabet(columnIndex - 2)}${rowIndex+1})" // should columnIndex - 2 + tempCell.cellFormula = + "SUM(${getColumnAlphabet(2)}${rowIndex + 1}:${getColumnAlphabet(columnIndex - 2)}${rowIndex + 1})" // should columnIndex - 2 rowIndex++ } // cal subtotal println(rowIndex) - for (i in 0 ..columnSize ) { // minus last col + for (i in 0..columnSize) { // minus last col println(sheet.getRow(rowIndex).getCell(2 + i)) tempCell = sheet.getRow(rowIndex).getCell(2 + i) ?: sheet.getRow(rowIndex).createCell(2 + i) tempCell.cellFormula = "SUM(${getColumnAlphabet(2 + i)}9:${getColumnAlphabet(2 + i)}${rowIndex})" @@ -1109,6 +1314,7 @@ open class ReportService( return workbook } + private fun createProjectCompletionReport( args: MutableMap, result: List>, @@ -1136,19 +1342,8 @@ open class ReportService( rowIndex = 5 columnIndex = 0 - result.forEachIndexed { index, obj -> - tempCell = sheet.getRow(rowIndex).createCell(columnIndex) - tempCell.setCellValue((index + 1).toDouble()) - val keys = obj.keys.toList() - keys.forEachIndexed { keyIndex, key -> - tempCell = sheet.getRow(rowIndex).getCell(columnIndex + keyIndex + 1) ?: sheet.getRow(rowIndex).createCell(columnIndex + keyIndex + 1) - when (obj[key]) { - is Double -> tempCell.setCellValue(obj[key] as Double) - else -> tempCell.setCellValue(obj[key] as String ) - } - } - rowIndex++ - } + + generalCreateReportIndexed(sheet, result, rowIndex, columnIndex) return workbook } @@ -1159,7 +1354,7 @@ open class ReportService( result: List>, lowerLimit: Double, templatePath: String - ):Workbook { + ): Workbook { val resource = ClassPathResource(templatePath) val templateInputStream = resource.inputStream val workbook: Workbook = XSSFWorkbook(templateInputStream) @@ -1187,19 +1382,9 @@ open class ReportService( rowIndex = 6 columnIndex = 0 - result.forEachIndexed { index, obj -> - tempCell = sheet.getRow(rowIndex).createCell(columnIndex) - tempCell.setCellValue((index + 1).toDouble()) - val keys = obj.keys.toList() - keys.forEachIndexed { keyIndex, key -> - tempCell = sheet.getRow(rowIndex).getCell(columnIndex + keyIndex + 1) ?: sheet.getRow(rowIndex).createCell(columnIndex + keyIndex + 1) - when (obj[key]) { - is Double -> tempCell.setCellValue(obj[key] as Double) - else -> tempCell.setCellValue(obj[key] as String ) - } - } - rowIndex++ - } + +// val currRow = generalCreateReportIndexed(sheet, result, rowIndex, columnIndex) + generalCreateReportIndexed(sheet, result, rowIndex, columnIndex) val sheetCF = sheet.sheetConditionalFormatting val rule1 = sheetCF.createConditionalFormattingRule("AND(J7 >= $lowerLimit, J7 <= 1)") @@ -1213,7 +1398,7 @@ open class ReportService( fillRed.setFillPattern(PatternFormatting.SOLID_FOREGROUND) val cfRules = arrayOf(rule1, rule2) - val regions = arrayOf(CellRangeAddress.valueOf("\$J7:\$K${rowIndex+1}")) + val regions = arrayOf(CellRangeAddress.valueOf("\$J7:\$K${rowIndex + 1}")) sheetCF.addConditionalFormatting(regions, cfRules); return workbook @@ -1246,7 +1431,7 @@ private fun createLateStartReport( setDataAndConditionalFormatting(workbook, sheet, lateStartData, evaluator) // Auto-size columns to fit content - autoSizeColumns(sheet) + autoSizeColumns(sheet) return workbook } @@ -1428,29 +1613,29 @@ private fun createLateStartReport( open fun getFinancialStatus(teamLeadId: Long?): List> { 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\'" + + " 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\'" ) if (teamLeadId!! > 0) { sql.append(" and p.teamLead = :teamLeadId ") @@ -1464,32 +1649,33 @@ private fun createLateStartReport( open fun getTimesheet(args: Map): List> { 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" + + " 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" + + " and t.recordDate BETWEEN :startDate and :endDate" + + " group by p.id, t.recordDate" + + " order by p.id, t.recordDate" ) return jdbcDao.queryForList(sql.toString(), args) } + open fun getLeaves(args: Map): List> { 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 " + + " 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) } @@ -1500,7 +1686,7 @@ private fun createLateStartReport( + " result.teamCode, " + " result.custCode, " ) if (args.get("outstanding") as Boolean) { - sql.append(" result.totalBudget - COALESCE(i.issueAmount , 0) + COALESCE(i.issueAmount, 0) - COALESCE(i.paidAmount, 0) as `Receivable Remained`, ") + sql.append(" result.projectFee - (result.totalBudget - COALESCE(i.issueAmount , 0) + COALESCE(i.issueAmount, 0) - COALESCE(i.paidAmount, 0)) as `Receivable Remained`, ") } sql.append( " DATE_FORMAT(result.actualEnd, '%d/%m/%Y') as actualEnd " @@ -1512,6 +1698,7 @@ private fun createLateStartReport( + " min(t.code) as teamCode, " + " min(c.code) as custCode, " + " min(p.actualEnd) as actualEnd, " + + " min(p.expectedTotalFee) as projectFee, " + " sum(COALESCE(tns.totalConsumed*sal.hourlyRate, 0)) as totalBudget " + " FROM ( " + " SELECT " @@ -1524,11 +1711,11 @@ private fun createLateStartReport( + " GROUP BY t.staffId, t.projectTaskId " + " order by t.staffId " + " ) AS tns " - + " inner join project_task pt ON tns.taskId = pt.id " - + " left JOIN staff s ON tns.staffId = s.id " + + " right join project_task pt ON tns.taskId = pt.id " + + " left join project p on p.id = pt.project_id " + + " left JOIN staff s ON p.teamLead = s.id " + " left join salary sal on s.salaryId = sal.salaryPoint " + " left JOIN team t ON s.teamId = t.id " - + " left join project p on p.id = pt.project_id " + " left join customer c on c.id = p.customerId " + " where p.deleted = false " + " and p.status = 'Completed' " @@ -1570,7 +1757,7 @@ private fun createLateStartReport( + " p.name, " + " t.code as team, " + " c.code as client, " - + " p.expectedTotalFee as plannedBudget, " + + " p.expectedTotalFee * 0.8 as plannedBudget, " + " COALESCE(tns.totalBudget, 0) as actualConsumedBudget, " + " COALESCE(p.totalManhour, 0) as plannedManhour, " + " COALESCE(tns.totalConsumed, 0) as actualConsumedManhour, " @@ -1601,60 +1788,64 @@ private fun createLateStartReport( if (args.containsKey("custId")) sql.append("and c.id = :custId") if (args.containsKey("status")) - statusFilter = when (args.get("status")) { - "Potential Overconsumption" -> "and " + - " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 0.9 " + - " and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + - " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 0.9 " + - " and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1)" - "Overconsumption" -> "and " + - " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + - " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1) " - "Within Budget" -> "and " + - " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) < 0.9 " + - " and " + - " (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 0.9 " - else -> "" - } - sql.append(statusFilter) + statusFilter = when (args.get("status")) { + "Potential Overconsumption" -> "and " + + " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 0.9 " + + " and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 0.9 " + + " and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1)" + + "Overconsumption" -> "and " + + " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1) " + + "Within Budget" -> "and " + + " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) < 0.9 " + + " and " + + " (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 0.9 " + + else -> "" + } + sql.append(statusFilter) } return jdbcDao.queryForList(sql.toString(), args) } - open fun getManhoursSpent(projectId: Long, startMonth: String, endMonth: String): List>{ + + open fun getManhoursSpent(projectId: Long, startMonth: String, endMonth: String): List> { 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.salaryPoint, s2.hourlyRate, t.staffId," - + " t.recordDate" - + " 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," - + " IFNULL(cte_ts.normalConsumed, 0) as normalConsumed, IFNULL(cte_ts.otConsumed, 0) as otConsumed, DATE_FORMAT(cte_ts.recordDate, '%Y-%m') as recordDate, " - + " IFNULL(cte_ts.salaryPoint, 0) as salaryPoint, " - + " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate, IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount," - + " s.name, s.staffId, g.code as gradeCode, g.name as gradeName, t2.code as teamCode, t2.name as teamName" - + " 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" - + " left join staff s on s.id = cte_ts.staffId" - + " left join grade g on g.id = s.gradeId" - + " left join team t2 on t2.id = s.teamId" - + " where p.deleted = false" - + " and (DATE_FORMAT(cte_ts.recordDate, \'%Y-%m\') >= :startMonth and DATE_FORMAT(cte_ts.recordDate, \'%Y-%m\') <= :endMonth)" - + " and p.id = :projectId" - + " order by g.code, s.name, cte_ts.recordDate" + " with cte_timesheet as (" + + " Select p.code, s.name as staff, IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.salaryPoint, s2.hourlyRate, t.staffId," + + " t.recordDate" + + " 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," + + " IFNULL(cte_ts.normalConsumed, 0) as normalConsumed, IFNULL(cte_ts.otConsumed, 0) as otConsumed, DATE_FORMAT(cte_ts.recordDate, '%Y-%m') as recordDate, " + + " IFNULL(cte_ts.salaryPoint, 0) as salaryPoint, " + + " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate, IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount," + + " s.name, s.staffId, g.code as gradeCode, g.name as gradeName, t2.code as teamCode, t2.name as teamName" + + " 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" + + " left join staff s on s.id = cte_ts.staffId" + + " left join grade g on g.id = s.gradeId" + + " left join team t2 on t2.id = s.teamId" + + " where p.deleted = false" + + " and (DATE_FORMAT(cte_ts.recordDate, \'%Y-%m\') >= :startMonth and DATE_FORMAT(cte_ts.recordDate, \'%Y-%m\') <= :endMonth)" + + " and p.id = :projectId" + + " order by g.code, s.name, cte_ts.recordDate" ) val args = mapOf( @@ -1689,24 +1880,24 @@ private fun createLateStartReport( val staffInfoList = mutableListOf>() println("manHoursSpent------- ${manHoursSpent}") - for(item in manHoursSpent){ - if(info["teamLead"] != item.getValue("teamLead")){ + for (item in manHoursSpent) { + if (info["teamLead"] != item.getValue("teamLead")) { info["teamLead"] = item.getValue("teamLead") } - if(info["client"] != item.getValue("client")){ + if (info["client"] != item.getValue("client")) { info["client"] = item.getValue("client") } - if(info["code"] != item.getValue("code")){ + if (info["code"] != item.getValue("code")) { info["code"] = item.getValue("code") } - if(info["code"] == item["code"] && "paidAmount" !in info){ + if (info["code"] == item["code"] && "paidAmount" !in info) { info["paidAmount"] = item.getValue("sumPaidAmount") } - if(info["description"] != item.getValue("description")){ + if (info["description"] != item.getValue("description")) { info["description"] = item.getValue("description") } val hourlyRate = (item.getValue("hourlyRate") as BigDecimal).toDouble() - if(!staffInfoList.any { it["staffId"] == item["staffId"] && it["name"] == item["name"]}){ + if (!staffInfoList.any { it["staffId"] == item["staffId"] && it["name"] == item["name"] }) { staffInfoList.add( mapOf( "staffId" to item.getValue("staffId"), @@ -1721,13 +1912,13 @@ private fun createLateStartReport( "normalConsumed" to item.getValue("normalConsumed"), "otConsumed" to item.getValue("otConsumed"), "totalManHours" to item.getValue("normalConsumed") as Double + item.getValue("otConsumed") as Double, - "manhourExpenditure" to (hourlyRate * item.getValue("normalConsumed") as Double ) - + (hourlyRate * item.getValue("otConsumed") as Double * otFactor) + "manhourExpenditure" to (hourlyRate * item.getValue("normalConsumed") as Double) + + (hourlyRate * item.getValue("otConsumed") as Double * otFactor) ) ) ) ) - }else{ + } else { val existingMap = staffInfoList.find { it.containsValue(item.getValue("staffId")) }!! val updatedMap = existingMap.toMutableMap() val hourlySpentList = updatedMap["hourlySpent"] as MutableList> @@ -1735,10 +1926,18 @@ private fun createLateStartReport( if (existingRecord != null) { val existingIndex = hourlySpentList.indexOf(existingRecord) val updatedRecord = existingRecord.toMutableMap() - updatedRecord["normalConsumed"] = (updatedRecord["normalConsumed"] as Double) + item.getValue("normalConsumed") as Double - updatedRecord["otConsumed"] = (updatedRecord["otConsumed"] as Double) + item.getValue("otConsumed") as Double - updatedRecord["totalManHours"] = (updatedRecord["totalManHours"] as Double) + item.getValue("normalConsumed") as Double + item.getValue("otConsumed") as Double - updatedRecord["manhourExpenditure"] = (updatedRecord["manhourExpenditure"] as Double) + (hourlyRate * item.getValue("normalConsumed") as Double) + (hourlyRate * item.getValue("otConsumed") as Double * otFactor) + updatedRecord["normalConsumed"] = + (updatedRecord["normalConsumed"] as Double) + item.getValue("normalConsumed") as Double + updatedRecord["otConsumed"] = + (updatedRecord["otConsumed"] as Double) + item.getValue("otConsumed") as Double + updatedRecord["totalManHours"] = + (updatedRecord["totalManHours"] as Double) + item.getValue("normalConsumed") as Double + item.getValue( + "otConsumed" + ) as Double + updatedRecord["manhourExpenditure"] = + (updatedRecord["manhourExpenditure"] as Double) + (hourlyRate * item.getValue("normalConsumed") as Double) + (hourlyRate * item.getValue( + "otConsumed" + ) as Double * otFactor) hourlySpentList[existingIndex] = updatedRecord } else { hourlySpentList.add( @@ -1748,7 +1947,7 @@ private fun createLateStartReport( "otConsumed" to item.getValue("otConsumed"), "totalManHours" to item.getValue("normalConsumed") as Double + item.getValue("otConsumed") as Double, "manhourExpenditure" to (hourlyRate * item.getValue("normalConsumed") as Double) - + (hourlyRate * item.getValue("otConsumed") as Double * otFactor) + + (hourlyRate * item.getValue("otConsumed") as Double * otFactor) ) ) } @@ -1763,7 +1962,7 @@ private fun createLateStartReport( tempList.add(mapOf("info" to info)) tempList.add(mapOf("staffInfoList" to staffInfoList)) - println("Only Staff Info List --------------------- ${ tempList.first() { it.containsKey("staffInfoList") }["staffInfoList"]}") + println("Only Staff Info List --------------------- ${tempList.first() { it.containsKey("staffInfoList") }["staffInfoList"]}") println("tempList----------------- $tempList") return tempList @@ -1786,7 +1985,7 @@ private fun createLateStartReport( manhoursSpent: List>, startMonth: String, endMonth: String - ): Workbook{ + ): Workbook { val resource = ClassPathResource(templatePath) val templateInputStream = resource.inputStream val workbook: Workbook = XSSFWorkbook(templateInputStream) @@ -1812,10 +2011,11 @@ private fun createLateStartReport( val monthFormat = DateTimeFormatter.ofPattern("MMM yyyy", Locale.ENGLISH) - val info:Map = manhoursSpent.first() { it.containsKey("info") }["info"] as Map - val staffInfoList: List> = manhoursSpent.first() { it.containsKey("staffInfoList") }["staffInfoList"] as List> + val info: Map = manhoursSpent.first() { it.containsKey("info") }["info"] as Map + val staffInfoList: List> = + manhoursSpent.first() { it.containsKey("staffInfoList") }["staffInfoList"] as List> - if (staffInfoList.isEmpty()){ + if (staffInfoList.isEmpty()) { val sheet = workbook.getSheetAt(0) var rowNum = 0 rowNum = 1 @@ -1889,7 +2089,7 @@ private fun createLateStartReport( row9Cell.setCellValue("${convertStartMonth.format(monthFormat)} - ${convertEndMonth.format(monthFormat)}") rowNum = 10 - for(staff in staffInfoList){ + for (staff in staffInfoList) { // val row: Row = sheet.getRow(rowNum++) ?: sheet.createRow(rowNum++) val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) val staffCell = row.getCell(0) ?: row.createCell(0) @@ -1899,7 +2099,7 @@ private fun createLateStartReport( val gradeCell = row.getCell(1) ?: row.createCell(1) gradeCell.apply { - setCellValue("G${staff.getValue("grade")}") + setCellValue("${staff.getValue("grade")}") } CellUtil.setAlignment(gradeCell, HorizontalAlignment.CENTER); @@ -1921,7 +2121,7 @@ private fun createLateStartReport( } rowNum += 2 val titleRow = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) - sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, 0, monthRange.size+3)) + sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, 0, monthRange.size + 3)) val titleCell = titleRow.getCell(0) ?: titleRow.createCell(0) titleCell.apply { setCellValue("Manhours Spent per Month") @@ -1938,7 +2138,7 @@ private fun createLateStartReport( } CellUtil.setAlignment(staffCell, HorizontalAlignment.CENTER); CellUtil.setVerticalAlignment(staffCell, VerticalAlignment.CENTER); - CellUtil.setCellStyleProperty(staffCell,"borderBottom", BorderStyle.THIN) + CellUtil.setCellStyleProperty(staffCell, "borderBottom", BorderStyle.THIN) val teamCell = manhoursSpentRow.getCell(1) ?: manhoursSpentRow.createCell(1) teamCell.apply { @@ -1946,44 +2146,44 @@ private fun createLateStartReport( } CellUtil.setAlignment(teamCell, HorizontalAlignment.CENTER); CellUtil.setVerticalAlignment(teamCell, VerticalAlignment.CENTER); - CellUtil.setCellStyleProperty(teamCell,"borderBottom", BorderStyle.THIN) + CellUtil.setCellStyleProperty(teamCell, "borderBottom", BorderStyle.THIN) - for ((column, month) in monthRange.withIndex()){ + for ((column, month) in monthRange.withIndex()) { val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) - val monthCell = row.getCell(2+column) ?: row.createCell(2+column) + val monthCell = row.getCell(2 + column) ?: row.createCell(2 + column) monthCell.apply { setCellValue(month.getValue("display").toString()) } CellUtil.setAlignment(monthCell, HorizontalAlignment.CENTER); CellUtil.setVerticalAlignment(monthCell, VerticalAlignment.CENTER); - CellUtil.setCellStyleProperty(monthCell,"borderBottom", BorderStyle.THIN) + CellUtil.setCellStyleProperty(monthCell, "borderBottom", BorderStyle.THIN) } val monthColumnEnd = monthRange.size val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) - val manhourCell = row.getCell(2+monthColumnEnd) ?: row.createCell(2+monthColumnEnd) + val manhourCell = row.getCell(2 + monthColumnEnd) ?: row.createCell(2 + monthColumnEnd) manhourCell.apply { setCellValue("Manhour Sub-total") } CellUtil.setAlignment(manhourCell, HorizontalAlignment.CENTER); CellUtil.setVerticalAlignment(manhourCell, VerticalAlignment.CENTER); CellUtil.setCellStyleProperty(manhourCell, CellUtil.WRAP_TEXT, true) - CellUtil.setCellStyleProperty(manhourCell,"borderBottom", BorderStyle.THIN) + CellUtil.setCellStyleProperty(manhourCell, "borderBottom", BorderStyle.THIN) - val manhourECell = row.getCell(2+monthColumnEnd+1) ?: row.createCell(2+monthColumnEnd+1) + val manhourECell = row.getCell(2 + monthColumnEnd + 1) ?: row.createCell(2 + monthColumnEnd + 1) manhourECell.apply { setCellValue("Manhour Expenditure (HKD)") } CellUtil.setAlignment(manhourECell, HorizontalAlignment.CENTER); CellUtil.setVerticalAlignment(manhourECell, VerticalAlignment.CENTER); CellUtil.setCellStyleProperty(manhourECell, CellUtil.WRAP_TEXT, true) - CellUtil.setCellStyleProperty(manhourECell,"borderBottom", BorderStyle.THIN) - sheet.setColumnWidth(2+monthColumnEnd+1, 20*256) + CellUtil.setCellStyleProperty(manhourECell, "borderBottom", BorderStyle.THIN) + sheet.setColumnWidth(2 + monthColumnEnd + 1, 20 * 256) // Manhour Spent LOOP println("monthRange--------------- ${monthRange}\n") rowNum += 1 - for(staff in staffInfoList){ + for (staff in staffInfoList) { val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) val staffCell = row.getCell(0) ?: row.createCell(0) var manhourExpenditure = 0.0 @@ -1997,10 +2197,10 @@ private fun createLateStartReport( } CellUtil.setCellStyleProperty(teamCell, CellUtil.WRAP_TEXT, true) - for(i in 0 until monthRange.size){ - val cell = row.getCell(i+2) ?: row.createCell(i+2) + for (i in 0 until monthRange.size) { + val cell = row.getCell(i + 2) ?: row.createCell(i + 2) cell.setCellValue(0.0) - for(hourlySpent in staff.getValue("hourlySpent") as List>){ + for (hourlySpent in staff.getValue("hourlySpent") as List>) { cell.apply { if (hourlySpent.getValue("recordDate") == monthRange[i].getValue("date").toString()) { setCellValue(hourlySpent.getValue("totalManHours") as Double) @@ -2013,13 +2213,13 @@ private fun createLateStartReport( CellUtil.setVerticalAlignment(cell, VerticalAlignment.CENTER); } - val manhourCell = row.getCell(2+monthRange.size) ?: row.createCell(2+monthRange.size) + val manhourCell = row.getCell(2 + monthRange.size) ?: row.createCell(2 + monthRange.size) manhourCell.apply { - cellFormula="SUM(C${rowNum+1}:${getColumnAlphabet(1+monthRange.size)}${rowNum+1})" + cellFormula = "SUM(C${rowNum + 1}:${getColumnAlphabet(1 + monthRange.size)}${rowNum + 1})" cellStyle.dataFormat = accountingStyle } - val manhourECell = row.getCell(3+monthRange.size) ?: row.createCell(3+monthRange.size) + val manhourECell = row.getCell(3 + monthRange.size) ?: row.createCell(3 + monthRange.size) manhourECell.apply { setCellValue(manhourExpenditure) cellStyle.dataFormat = accountingStyle @@ -2041,19 +2241,19 @@ private fun createLateStartReport( rowNum += 1 val totalManhourERow: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) - val startRow = 15+staffInfoList.size - val lastColumnIndex = getColumnAlphabet(3+monthRange.size) + val startRow = 15 + staffInfoList.size + val lastColumnIndex = getColumnAlphabet(3 + monthRange.size) val totalManhourETitleCell = totalManhourERow.getCell(0) ?: totalManhourERow.createCell(0) totalManhourETitleCell.apply { setCellValue("Total Manhour Expenditure (HKD)") } val totalManhourECell = totalManhourERow.getCell(1) ?: totalManhourERow.createCell(1) totalManhourECell.apply { - cellFormula = "SUM(${lastColumnIndex}${startRow}:${lastColumnIndex}${startRow+staffInfoList.size})" + cellFormula = "SUM(${lastColumnIndex}${startRow}:${lastColumnIndex}${startRow + staffInfoList.size})" cellStyle.dataFormat = accountingStyle } - CellUtil.setCellStyleProperty(totalManhourETitleCell,"borderBottom", BorderStyle.THIN) - CellUtil.setCellStyleProperty(totalManhourECell,"borderBottom", BorderStyle.THIN) + CellUtil.setCellStyleProperty(totalManhourETitleCell, "borderBottom", BorderStyle.THIN) + CellUtil.setCellStyleProperty(totalManhourECell, "borderBottom", BorderStyle.THIN) rowNum += 1 val pandlRow: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) @@ -2063,16 +2263,16 @@ private fun createLateStartReport( } val panlCell = pandlRow.getCell(1) ?: pandlRow.createCell(1) panlCell.apply { - cellFormula = "B${rowNum-1}-B${rowNum}" + cellFormula = "B${rowNum - 1}-B${rowNum}" cellStyle.dataFormat = accountingStyle } - CellUtil.setCellStyleProperty(panlCellTitle,"borderBottom", BorderStyle.DOUBLE) - CellUtil.setCellStyleProperty(panlCell,"borderBottom", BorderStyle.DOUBLE) + CellUtil.setCellStyleProperty(panlCellTitle, "borderBottom", BorderStyle.DOUBLE) + CellUtil.setCellStyleProperty(panlCell, "borderBottom", BorderStyle.DOUBLE) return workbook } - fun getCostAndExpense(status: String): List>{ + fun getCostAndExpense(clientId: Long?, teamId: Long?): List>{ 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.salaryPoint, s2.hourlyRate, t.staffId," @@ -2097,17 +2297,25 @@ private fun createLateStartReport( + " left join team t2 on t2.id = s.teamId" + " where ISNULL(p.code) = False" ) - if(status != "All"){ + if(clientId != null){ sql.append( - " and p.status = :status" + " and c.id = :clientId " ) } + + if(teamId != null){ + sql.append( + " and p.teamLead = :teamId " + ) + } + sql.append( " group by p.code, p.description , c.name, teamLead, p.expectedTotalFee , hourlyRate" + " order by p.code" ) val args = mapOf( - "status" to status + "clientId" to clientId, + "teamId" to teamId ) val otFactor = BigDecimal(1).toDouble() @@ -2138,8 +2346,148 @@ private fun createLateStartReport( } } } + val result = costAndExpenseList.map { + item -> + val budget = (item["budget"] as? Double)?.times(0.8) ?: 0.0 + val budgetRemain = budget - (item["manhourExpenditure"] as? Double ?: 0.0) + val remainingPercent = (budgetRemain / budget) + item.toMutableMap().apply { + put("budgetPercentage", remainingPercent) + } + } + + return result + } + + fun createCostAndExpenseWorkbook( + templatePath: String, + costAndExpenseList: List>, + teamId: Long?, + clientId: Long?, + budgetPercentage: Double? + ): Workbook{ + val resource = ClassPathResource(templatePath) + val templateInputStream = resource.inputStream + val workbook: Workbook = XSSFWorkbook(templateInputStream) + + val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") + val percentStyle = workbook.createDataFormat().getFormat("0.00%") + + val sheet = workbook.getSheetAt(0) + var rowNum = 0 + rowNum = 1 + val row1: Row = sheet.getRow(rowNum) + val row1Cell = row1.getCell(2) + row1Cell.setCellValue(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")).toString()) + + rowNum = 2 + val row2: Row = sheet.getRow(rowNum) + val row2Cell = row2.getCell(2) + if(teamId == null){ + row2Cell.setCellValue("All") + }else{ + val sql = StringBuilder( + " select t.id, t.code, t.name, concat(t.code, \" - \" ,t.name) as teamLead from team t where t.id = :teamId " + ) + val team = jdbcDao.queryForMap(sql.toString(), mapOf("teamId" to teamId)).get() + row2Cell.setCellValue(team["teamLead"] as String) + } + + rowNum = 3 + val row3: Row = sheet.getRow(rowNum) + val row3Cell = row3.getCell(2) + if(clientId == null){ + row3Cell.setCellValue("All") + }else{ + val sql = StringBuilder( + " select c.id, c.name from customer c where c.id = :clientId " + ) + val client = jdbcDao.queryForMap(sql.toString(), mapOf("clientId" to clientId)).get() + row3Cell.setCellValue(client["name"] as String) + } + + + val filterList: List> + if(budgetPercentage != null){ + filterList = costAndExpenseList.filter { ((it["budgetPercentage"] as? Double) ?: 0.0) > budgetPercentage } + }else{ + filterList = costAndExpenseList + } + + + rowNum = 6 + for(item in filterList){ + val index = filterList.indexOf(item) + val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) + val cell = row.getCell(0) ?: row.createCell(0) + cell.apply { + setCellValue(index.toDouble()) + } + + val cell1 = row.getCell(1) ?: row.createCell(1) + cell1.apply { + setCellValue(item["code"].toString()) + } + + val cell2 = row.getCell(2) ?: row.createCell(2) + cell2.apply { + setCellValue(item["description"].toString()) + } + + val cell3 = row.getCell(3) ?: row.createCell(3) + cell3.apply { + setCellValue(item["teamLead"].toString()) + } + + val cell4 = row.getCell(4) ?: row.createCell(4) + cell4.apply { + setCellValue(item["client"].toString()) + } + + val cell5 = row.getCell(5) ?: row.createCell(5) + val budget = item["budget"] as Double * 0.8 + cell5.apply { + setCellValue(budget) +// cellStyle.dataFormat = accountingStyle + } + CellUtil.setCellStyleProperty(cell5, "dataFormat", accountingStyle) + + val cell6 = row.getCell(6) ?: row.createCell(6) + val manHoutsSpentCost = item["manhourExpenditure"] as Double + cell6.apply { + setCellValue(manHoutsSpentCost) + } + CellUtil.setCellStyleProperty(cell6, "dataFormat", accountingStyle) + + val cell7 = row.getCell(7) ?: row.createCell(7) + cell7.apply { + cellFormula = "F${rowNum+1}-G${rowNum+1}" + } + CellUtil.setCellStyleProperty(cell7, "dataFormat", accountingStyle) + + val cell8 = row.getCell(8) ?: row.createCell(8) + cell8.apply { + cellFormula = "H${rowNum+1}/F${rowNum+1}" + } + CellUtil.setCellStyleProperty(cell8, "dataFormat", percentStyle) + + sheet.setRowBreak(rowNum++); + } + + return workbook + } - return costAndExpenseList + fun genCostAndExpenseReport(request: costAndExpenseRequest): ByteArray{ + + val costAndExpenseList = getCostAndExpense(request.clientId, request.teamId) + + val workbook: Workbook = createCostAndExpenseWorkbook(COSTANDEXPENSE_REPORT, costAndExpenseList, request.teamId, request.clientId, request.budgetPercentage) + + val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() + workbook.write(outputStream) + workbook.close() + + return outputStream.toByteArray() } open fun getLateStartDetails(teamId: Long?, clientId: Long?, remainedDate: LocalDate, remainedDateTo: LocalDate): List> { val sql = StringBuilder(""" diff --git a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt index 758228f..296d797 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt @@ -2,11 +2,9 @@ package com.ffii.tsms.modules.report.web import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.KotlinModule -import com.ffii.tsms.modules.data.entity.Customer -import com.ffii.tsms.modules.data.entity.StaffRepository +import com.ffii.tsms.modules.data.entity.* //import com.ffii.tsms.modules.data.entity.projections.FinancialStatusReportInfo import com.ffii.tsms.modules.data.entity.projections.StaffSearchInfo -import com.ffii.tsms.modules.data.entity.Team import com.ffii.tsms.modules.data.service.CustomerService import com.ffii.tsms.modules.data.service.TeamService import com.ffii.tsms.modules.project.entity.* @@ -35,11 +33,13 @@ import java.time.LocalDate import java.net.URLEncoder import java.time.format.DateTimeFormatter import org.springframework.stereotype.Controller -import com.ffii.tsms.modules.data.entity.TeamRepository -import com.ffii.tsms.modules.data.entity.CustomerRepository import org.springframework.data.jpa.repository.JpaRepository import com.ffii.tsms.modules.project.entity.Project import com.ffii.tsms.modules.report.web.model.* +import com.ffii.tsms.modules.timesheet.entity.Timesheet +import org.springframework.data.domain.Example +import org.springframework.data.domain.ExampleMatcher +import java.time.temporal.ChronoUnit @RestController @RequestMapping("/reports") @@ -56,7 +56,8 @@ class ReportController( private val leaveRepository: LeaveRepository, private val teamService: TeamService, private val customerService: CustomerService, - private val invoiceService: InvoiceService) { + private val invoiceService: InvoiceService, private val gradeAllocationRepository: GradeAllocationRepository +) { @PostMapping("/fetchProjectsFinancialStatusReport") @Throws(ServletRequestBindingException::class, IOException::class) @@ -87,6 +88,34 @@ class ReportController( .body(ByteArrayResource(reportResult)) } + @PostMapping("/ProjectPotentialDelayReport") + @Throws(ServletRequestBindingException::class, IOException::class) + fun getProjectPotentialDelayReport(@RequestBody @Valid request: ProjectPotentialDelayReportRequest): ResponseEntity { + + val team = if (request.teamId.lowercase() == "all") null else teamRepository.findById(request.teamId.toLong()).orElse(null) + val searchedTeam = if (team == null) "All" else team.code + " - " +team.name + val client = if (request.clientId.lowercase() == "all") null else customerRepository.findById(request.clientId.toLong()).orElse(null) + val searchedClient = if (client == null) "All" else client.code + " - " +client.name + + val matcher = ExampleMatcher.matching().withIgnoreNullValues() + val exampleQuery = Example.of(Project().apply { + // org.springframework.dao.InvalidDataAccessApiUsageException: Path 'teamLead.team.staff' from root Project must not span a cyclic property reference + // [{ com.ffii.tsms.modules.project.entity.Project@6847e037 }] -teamLead-> [{ com.ffii.tsms.modules.data.entity.Staff@2a4c488b }] -team-> [{ com.ffii.tsms.modules.data.entity.Team@a09acb5 }] -staff-> [{ com.ffii.tsms.modules.data.entity.Staff@2a4c488b }] + // teamLead = team?.staff + customer = client + status = "On-going" + }, matcher) + + val projects = if (team == null) projectRepository.findAll(exampleQuery) else projectRepository.findAll(exampleQuery).filter { it.teamLead == team.staff } + val projectTasks = projectTaskRepository.findAllByProjectIn(projects) + val timesheets = timesheetRepository.findAllByProjectTaskIn(projectTasks) + + val reportResult: ByteArray = excelReportService.generateProjectPotentialDelayReport(searchedTeam, searchedClient, projects, timesheets, request.numberOfDays, request.projectCompletion) + return ResponseEntity.ok() + .header("filename", "Project Potential Delay Report - " + LocalDate.now() + ".xlsx") + .body(ByteArrayResource(reportResult)) + } + @PostMapping("/StaffMonthlyWorkHourAnalysisReport") @Throws(ServletRequestBindingException::class, IOException::class) fun StaffMonthlyWorkHourAnalysisReport(@RequestBody @Valid request: StaffMonthlyWorkHourAnalysisReportRequest): ResponseEntity { @@ -262,8 +291,19 @@ class ReportController( } @GetMapping("/costNExpenses/{status}") - fun getManhoursSpent(@PathVariable status: String): List> { - return excelReportService.getCostAndExpense("All") + fun getManhoursSpent(@RequestBody @Valid request: costAndExpenseRequest): List> { + return excelReportService.getCostAndExpense(request.clientId, request.teamId) + } + + @PostMapping("/costandexpenseReport") + @Throws(ServletRequestBindingException::class, IOException::class) + fun getCostAndExpenseReport(@RequestBody @Valid request: costAndExpenseRequest): ResponseEntity { + println(request) + val reportResult: ByteArray = excelReportService.genCostAndExpenseReport(request) + + return ResponseEntity.ok() + .header("filename", "Cost and Expense Report - " + LocalDate.now() + ".xlsx") + .body(ByteArrayResource(reportResult)) } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt index acbb1a9..9959d0a 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt @@ -13,11 +13,24 @@ data class PandLReportRequest ( val endMonth: String, ) +data class costAndExpenseRequest ( + val teamId: Long?, + val clientId: Long?, + val budgetPercentage: Double?, +) + data class ProjectCashFlowReportRequest ( val projectId: Long, val dateType: String, // "Date", "Month" ) +data class ProjectPotentialDelayReportRequest ( + val teamId: String, + val clientId: String, + val numberOfDays: Int, + val projectCompletion: Int, +) + data class StaffMonthlyWorkHourAnalysisReportRequest ( val id: Long, val yearMonth: YearMonth diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/entity/Timesheet.kt b/src/main/java/com/ffii/tsms/modules/timesheet/entity/Timesheet.kt index 9128493..1566126 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/entity/Timesheet.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/entity/Timesheet.kt @@ -2,6 +2,7 @@ package com.ffii.tsms.modules.timesheet.entity import com.ffii.core.entity.BaseEntity import com.ffii.tsms.modules.data.entity.Staff +import com.ffii.tsms.modules.project.entity.Project import com.ffii.tsms.modules.project.entity.ProjectTask import jakarta.persistence.* import jakarta.validation.constraints.NotNull @@ -29,6 +30,10 @@ open class Timesheet : BaseEntity() { @JoinColumn(name = "projectTaskId") open var projectTask: ProjectTask? = null + @ManyToOne + @JoinColumn(name = "projectId") + open var project: Project? = null + @Column(name = "remark") open var remark: String? = null } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt b/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt index 1324c4b..31350b1 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt @@ -46,6 +46,7 @@ open class TimesheetsService( this.normalConsumed = timeEntry.inputHours this.otConsumed = timeEntry.otHours this.projectTask = projectTask + this.project = project this.remark = timeEntry.remark } } @@ -72,6 +73,7 @@ open class TimesheetsService( this.normalConsumed = entry.inputHours this.otConsumed = entry.otHours this.projectTask = projectTask + this.project = project this.remark = entry.remark this.recordDate = this.recordDate ?: recordDate this.staff = this.staff ?: memberStaff @@ -112,7 +114,7 @@ open class TimesheetsService( .mapValues { (_, timesheets) -> timesheets.map { timesheet -> TimeEntry( id = timesheet.id!!, - projectId = timesheet.projectTask?.project?.id, + projectId = timesheet.projectTask?.project?.id ?: timesheet.project?.id, taskId = timesheet.projectTask?.task?.id, taskGroupId = timesheet.projectTask?.task?.taskGroup?.id, inputHours = timesheet.normalConsumed ?: 0.0, diff --git a/src/main/java/com/ffii/tsms/modules/user/req/UpdateUserReq.java b/src/main/java/com/ffii/tsms/modules/user/req/UpdateUserReq.java index 853f04b..6bd7e68 100644 --- a/src/main/java/com/ffii/tsms/modules/user/req/UpdateUserReq.java +++ b/src/main/java/com/ffii/tsms/modules/user/req/UpdateUserReq.java @@ -18,6 +18,9 @@ public class UpdateUserReq { @NotBlank private String name; + @NotBlank + private String username; + private String firstname; private String lastname; private LocalDate expiryDate; @@ -55,6 +58,9 @@ public class UpdateUserReq { public void setName(String name) { this.name = name; } + public String getUsername() { return username; } + + public void setUsername(String username) { this.username = username; } public LocalDate getExpiryDate() { return expiryDate; diff --git a/src/main/resources/db/changelog/changes/20240318_01_wayne/02_mock_team_leads.sql b/src/main/resources/db/changelog/changes/20240318_01_wayne/02_mock_team_leads.sql deleted file mode 100644 index ce29748..0000000 --- a/src/main/resources/db/changelog/changes/20240318_01_wayne/02_mock_team_leads.sql +++ /dev/null @@ -1,35 +0,0 @@ --- liquibase formatted sql - --- changeset wayne:mock_team_leads - -INSERT INTO `user` (name, username, password) VALUES - ('Wayne Lee','wlee','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'), - ('Ming Chan','mchan','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'); - -INSERT INTO company (companyCode, name) VALUES - ('ABC', 'Company ABC'); - -INSERT INTO team (name, code) VALUES - ('Team Wayne Lee', 'WL'), - ('Team Ming Chan', 'MC'); - -INSERT INTO salary_effective (date, salaryId) VALUES - (current_date, 1); - -INSERT INTO staff (userId, name, staffId, companyId, teamId, salaryEffId) VALUES - ( - (SELECT id from `user` where username = 'wlee'), - 'Wayne Lee', - '001', - (SELECT id from company where companyCode = 'ABC'), - (SELECT id from team where code = 'WL'), - (SELECT id from salary_effective where salaryId = 1) - ), - ( - (SELECT id from `user` where username = 'mchan'), - 'Ming Chan', - '002', - (SELECT id from company where companyCode = 'ABC'), - (SELECT id from team where code = 'MC'), - (SELECT id from salary_effective where salaryId = 1) - ); \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20240414_01_wayne/01_mock_staffs.sql b/src/main/resources/db/changelog/changes/20240414_01_wayne/01_mock_staffs.sql deleted file mode 100644 index aa90e7a..0000000 --- a/src/main/resources/db/changelog/changes/20240414_01_wayne/01_mock_staffs.sql +++ /dev/null @@ -1,114 +0,0 @@ --- liquibase formatted sql --- changeset wayne:mock_staffs - -INSERT INTO `user` (name, username, password) VALUES - ('Albert Lam','alam','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'), - ('Bernard Chou','bchou','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'), - ('Carl Junior','cjunior','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'), - ('Denis Lau','dlau','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'), - ('Edward Wong','ewong','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'), - ('Fred Cheung','fcheung','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'), - ('Gordon Ramsey','gramsey','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'), - ('Heather Stewards','hstewards','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'), - ('Ivan Foo','ifoo','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'), - ('Jack Son','json','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'), - ('Kurt Land','kland','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'), - ('Lawrence Arnold','larnold','$2a$10$65S7/AhKn8MldlYmvFN5JOfr1yaULwFNDIhTskLTuUCKgbbs8sFAi'); - -INSERT INTO staff (userId, name, staffId, companyId, teamId, salaryEffId) VALUES - ( - (SELECT id from `user` where username = 'alam'), - 'Albert Lam', - '003', - 1, - 1, - 1 - ), - ( - (SELECT id from `user` where username = 'bchou'), - 'Bernard Chou', - '004', - 1, - 2, - 1 - ), - ( - (SELECT id from `user` where username = 'cjunior'), - 'Carl Junior', - '005', - 1, - 1, - 1 - ), - ( - (SELECT id from `user` where username = 'dlau'), - 'Denis Lau', - '006', - 1, - 2, - 1 - ), - ( - (SELECT id from `user` where username = 'ewong'), - 'Edward Wong', - '007', - 1, - 1, - 1 - ), - ( - (SELECT id from `user` where username = 'fcheung'), - 'Fred Cheung', - '008', - 1, - 2, - 1 - ), - ( - (SELECT id from `user` where username = 'gramsey'), - 'Gordon Ramsey', - '009', - 1, - 1, - 1 - ), - ( - (SELECT id from `user` where username = 'hstewards'), - 'Heather Stewards', - '010', - 1, - 2, - 1 - ), - ( - (SELECT id from `user` where username = 'ifoo'), - 'Ivan Foo', - '011', - 1, - 1, - 1 - ), - ( - (SELECT id from `user` where username = 'json'), - 'Jack Son', - '012', - 1, - 2, - 1 - ), - ( - (SELECT id from `user` where username = 'kland'), - 'Kurt Land', - '013', - 1, - 1, - 1 - ), - ( - (SELECT id from `user` where username = 'larnold'), - 'Lawrence Arnold', - '014', - 1, - 2, - 1 - ); \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20240525_01_wayne/01_timesheet_project.sql b/src/main/resources/db/changelog/changes/20240525_01_wayne/01_timesheet_project.sql new file mode 100644 index 0000000..d4a42a5 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240525_01_wayne/01_timesheet_project.sql @@ -0,0 +1,6 @@ +-- liquibase formatted sql +-- changeset wayne:timesheet_project + +ALTER TABLE timesheet ADD projectId INT NULL; + +ALTER TABLE timesheet ADD CONSTRAINT FK_TIMESHEET_ON_PROJECTID FOREIGN KEY (projectId) REFERENCES project (id); diff --git a/src/main/resources/db/changelog/changes/20240527_01_cyril/01_update_task.sql b/src/main/resources/db/changelog/changes/20240527_01_cyril/01_update_task.sql new file mode 100644 index 0000000..ddfd700 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240527_01_cyril/01_update_task.sql @@ -0,0 +1,123 @@ +-- liquibase formatted sql +-- changeset cyril:task + +UPDATE task +SET name='1.1 Prepare / revise Cost Estimate / Cost Plan' +WHERE id=1; +UPDATE task +SET name='1.2 Cost estimation and cost studies' +WHERE id=2; +UPDATE task +SET name='1.3 Cash flow forecast' +WHERE id=3; +UPDATE task +SET name='1.4 Attend meetings' +WHERE id=4; +UPDATE task +SET name='2.1 Advise on tendering, contractual and procurement arrangement',taskGroupId=2 +WHERE id=5; +UPDATE task +SET name='2.2 Drafting / vetting front-part' +WHERE id=6; +UPDATE task +SET name='2.3 Carry out pre-qualification exercise / EOI' +WHERE id=7; +UPDATE task +SET name='2.4 Measurement & billing for 1st tender out' +WHERE id=8; +UPDATE task +SET name='2.5 Measurement & billing for tender addendum' +WHERE id=9; +UPDATE task +SET name='2.6 Bulk checking' +WHERE id=10; +UPDATE task +SET name='2.7 Edit tender documents (Incl. Bills of Quantities / SOR)' +WHERE id=11; +UPDATE task +SET name='2.8 Preparation of pre-tender estimates' +WHERE id=12; +UPDATE task +SET name='2.9 Update cash flow forecast' +WHERE id=13; +UPDATE task +SET name='2.10 Attend meetings' +WHERE id=14; +UPDATE task +SET name='3.1 Evaluation of tenders (incl. rate analysis)',taskGroupId=3 +WHERE id=15; +UPDATE task +SET name='3.2 Preparation of tender queries',taskGroupId=3 +WHERE id=16; +UPDATE task +SET name='3.3 Attend tender interviews' +WHERE id=17; +UPDATE task +SET name='3.4 Preparation of Report on Tenderers' +WHERE id=18; +UPDATE task +SET name='3.5 Draft Letter of Acceptance / Award' +WHERE id=19; +UPDATE task +SET name='3.6 Preparation of Contract Documents' +WHERE id=20; +UPDATE task +SET name='4.1 Check insurance policies, surety bond, guarantee, etc.',taskGroupId=4 +WHERE id=21; +UPDATE task +SET name='4.2 Valuation for interim/final payments (incl. site visits)',taskGroupId=4 +WHERE id=22; +UPDATE task +SET name='4.3 Preparation of financial statements (incl. cash flow forecasts)',taskGroupId=4 +WHERE id=23; +UPDATE task +SET name='4.4 Cost check / advice on alterative design solutions' +WHERE id=24; +UPDATE task +SET name='4.5 Cost estimation for draft AIs/EIs/SIs' +WHERE id=25; +UPDATE task +SET name='4.6 Advise on contractual issues & evaluate monetary claims' +WHERE id=26; +UPDATE task +SET name='4.7 Measurement & valuation of variations / prime cost & provisional sums' +WHERE id=27; +UPDATE task +SET name='4.8 Negotiation and settlement of final accounts (incl. meetings)' +WHERE id=28; +UPDATE task +SET name='4.9 Preparation of Statement of Final Account' +WHERE id=29; +UPDATE task +SET name='4.10 Preparation of Cost Analysis for the completed project' +WHERE id=30; +UPDATE task +SET name='4.11 Check / review draft final bills' +WHERE id=31; +UPDATE task +SET name='4.12 Carry out site check for draft final bills' +WHERE id=32; +UPDATE task +SET name='4.13 Attend meetings (other than final account meetings)' +WHERE id=33; +UPDATE task +SET name='5.1 Preparation of Fee Proposal / EOI',taskGroupId=5 +WHERE id=34; +UPDATE task +SET name='5.2 Attend Management Meeting / Management Workshop',taskGroupId=5 +WHERE id=35; +UPDATE task +SET name='5.3 Preparation of project budget i.e. manhours vs fee receivables',taskGroupId=5 +WHERE id=36; +UPDATE task +SET name='5.4 Attend Local / International Conference / Seminar / Webinar' +WHERE id=37; +UPDATE task +SET name='5.5 Preparing supplementary agreements (SA)' +WHERE id=38; +UPDATE task +SET name='5.6 Measurement / ad hoc tasks for Contractor' +WHERE id=39; +UPDATE task +SET name='5.7 Management Timesheet Allocation' +WHERE id=40; \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20240527_01_cyril/02_update_position.sql b/src/main/resources/db/changelog/changes/20240527_01_cyril/02_update_position.sql new file mode 100644 index 0000000..3a22bae --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240527_01_cyril/02_update_position.sql @@ -0,0 +1,74 @@ +-- liquibase formatted sql +-- changeset cyril:position + +INSERT INTO position (description,name,code) +VALUES ('Resident Quantity Surveyor','Resident Quantity Surveyor','Resident QS'); +INSERT INTO position (description,name,code) +VALUES ('Resident AQS','Resident AQS','Resident AQS'); +INSERT INTO position (description,name,code) +VALUES ('Administrative Officer','Administrative Officer','Admin Officer'); +INSERT INTO position (description,name,code) +VALUES ('Secretary','Secretary','Secretary'); +INSERT INTO position (description,name,code) +VALUES ('CPMS Assessor','CPMS Assessor','CPMS Assessor'); +INSERT INTO position (description,name,code) +VALUES ('Co-ordinator','Co-ordinator','Co-ordinator'); +INSERT INTO position (description,name,code) +VALUES ('Admin Supporter','Admin Supporter','Admin Supporter'); +INSERT INTO position (description,name,code) +VALUES ('Receptionist','Receptionist','Receptionist'); +INSERT INTO position (description,name,code) +VALUES ('Junior Secretary','Junior Secretary','Junior Secretary'); +INSERT INTO position (description,name,code) +VALUES ('Clerk','Clerk','Clerk'); +INSERT INTO position (description,name,code) +VALUES ('Office Assistant','Office Assistant','Office Assistant'); +INSERT INTO position (description,name,code) +VALUES ('HR Assistant','HR Assistant','HR Assistant'); +INSERT INTO position (description,name,code) +VALUES ('Project Officer','Project Officer','Project Officer'); +INSERT INTO position (description,name,code) +VALUES ('Ex. Secretary','Ex. Secretary','Ex. Secretary'); +INSERT INTO position (description,name,code) +VALUES ('I.T. Officer','I.T. Officer','I.T. Officer'); +INSERT INTO position (description,name,code) +VALUES ('Resident Senior Quantity Surveyor','Resident Senior Quantity Surveyor','Resident SQS'); +INSERT INTO position (description,name,code) +VALUES ('Obsever (SQS)','Obsever (SQS)','Obsever (SQS)'); +INSERT INTO position (description,name,code) +VALUES ('HR Manager','HR Manager','HR Manager'); +INSERT INTO position (description,name,code) +VALUES ('Senior Consultant','Senior Consultant','Senior Consultant'); +UPDATE position +SET description='Quantity Surveying Trainee' +WHERE id=1; +UPDATE position +SET name='Assistant Quantity Surveyor',description='Assistant Quantity Surveyor',code='AQS' +WHERE id=2; +UPDATE position +SET name='Quantity Surveyor',description='Quantity Surveyor' +WHERE id=3; +UPDATE position +SET name='Senior Quantity Surveyor',description='Senior Quantity Surveyor',code='SQS' +WHERE id=4; +UPDATE position +SET description='Assistant Manager' +WHERE id=5; +UPDATE position +SET description='Deputy Manager' +WHERE id=6; +UPDATE position +SET description='Manager' +WHERE id=7; +UPDATE position +SET description='Senior Manager' +WHERE id=8; +UPDATE position +SET description='Assistant Director' +WHERE id=9; +UPDATE position +SET description='Deputy Director' +WHERE id=10; +UPDATE position +SET description='Director' +WHERE id=11; \ No newline at end of file diff --git a/src/main/resources/templates/report/AR02_Delay Report v02.xlsx b/src/main/resources/templates/report/AR02_Delay Report v02.xlsx index fe0d493..d07a414 100644 Binary files a/src/main/resources/templates/report/AR02_Delay Report v02.xlsx and b/src/main/resources/templates/report/AR02_Delay Report v02.xlsx differ diff --git a/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx b/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx index ff19668..88dd3c8 100644 Binary files a/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx and b/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx differ diff --git a/src/main/resources/templates/report/AR04_Cost and Expense Report v02.xlsx b/src/main/resources/templates/report/AR04_Cost and Expense Report v02.xlsx index cb9ba8e..a8aeceb 100644 Binary files a/src/main/resources/templates/report/AR04_Cost and Expense Report v02.xlsx and b/src/main/resources/templates/report/AR04_Cost and Expense Report v02.xlsx differ diff --git a/src/main/resources/templates/report/AR05_Project Completion Report.xlsx b/src/main/resources/templates/report/AR05_Project Completion Report.xlsx index 126f10c..d9187b6 100644 Binary files a/src/main/resources/templates/report/AR05_Project Completion Report.xlsx and b/src/main/resources/templates/report/AR05_Project Completion Report.xlsx differ diff --git a/src/main/resources/templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx b/src/main/resources/templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx index db5ea39..700dc59 100644 Binary files a/src/main/resources/templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx and b/src/main/resources/templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx differ diff --git a/src/main/resources/templates/report/AR07_Project P&L Report v02.xlsx b/src/main/resources/templates/report/AR07_Project P&L Report v02.xlsx index 7febce9..111482b 100644 Binary files a/src/main/resources/templates/report/AR07_Project P&L Report v02.xlsx and b/src/main/resources/templates/report/AR07_Project P&L Report v02.xlsx differ diff --git a/src/main/resources/templates/report/AR08_Monthly Work Hours Analysis Report.xlsx b/src/main/resources/templates/report/AR08_Monthly Work Hours Analysis Report.xlsx index 08129d7..c471a6a 100644 Binary files a/src/main/resources/templates/report/AR08_Monthly Work Hours Analysis Report.xlsx and b/src/main/resources/templates/report/AR08_Monthly Work Hours Analysis Report.xlsx differ diff --git a/src/main/resources/templates/report/EX01_Financial Status Report.xlsx b/src/main/resources/templates/report/EX01_Financial Status Report.xlsx index 61e078a..f864de3 100644 Binary files a/src/main/resources/templates/report/EX01_Financial Status Report.xlsx and b/src/main/resources/templates/report/EX01_Financial Status Report.xlsx differ diff --git a/src/main/resources/templates/report/EX02_Project Cash Flow Report.xlsx b/src/main/resources/templates/report/EX02_Project Cash Flow Report.xlsx index 0f63db7..108ca57 100644 Binary files a/src/main/resources/templates/report/EX02_Project Cash Flow Report.xlsx and b/src/main/resources/templates/report/EX02_Project Cash Flow Report.xlsx differ