| @@ -0,0 +1,17 @@ | |||||
| package com.ffii.core.utils | |||||
| import java.time.LocalDate | |||||
| open class CheckingUtils { | |||||
| companion object { | |||||
| fun checkTimePeriod(checkDate: LocalDate, startDate: LocalDate?, endDate: LocalDate?): Boolean { | |||||
| return if (startDate == null && endDate == null) { | |||||
| true | |||||
| } else if (startDate == null) { | |||||
| checkDate.isBefore(endDate) | |||||
| } else { | |||||
| checkDate.isAfter(startDate) && checkDate.isBefore(endDate) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -2,20 +2,30 @@ package com.ffii.tsms.modules.data.service | |||||
| import com.ffii.core.support.AbstractBaseEntityService | import com.ffii.core.support.AbstractBaseEntityService | ||||
| import com.ffii.core.support.JdbcDao | import com.ffii.core.support.JdbcDao | ||||
| import com.ffii.core.utils.CheckingUtils | |||||
| import com.ffii.tsms.modules.data.entity.Customer | import com.ffii.tsms.modules.data.entity.Customer | ||||
| import com.ffii.tsms.modules.data.entity.CustomerType | import com.ffii.tsms.modules.data.entity.CustomerType | ||||
| import com.ffii.tsms.modules.data.entity.CustomerRepository | import com.ffii.tsms.modules.data.entity.CustomerRepository | ||||
| import com.ffii.tsms.modules.data.entity.CustomerTypeRepository | import com.ffii.tsms.modules.data.entity.CustomerTypeRepository | ||||
| import com.ffii.tsms.modules.data.entity.SalaryEffectiveRepository | |||||
| import com.ffii.tsms.modules.data.entity.TeamLogRepository | |||||
| import com.ffii.tsms.modules.data.entity.TeamRepository | |||||
| import com.ffii.tsms.modules.data.web.models.FinancialSummaryByClient | import com.ffii.tsms.modules.data.web.models.FinancialSummaryByClient | ||||
| import com.ffii.tsms.modules.data.web.models.FinancialSummaryByProject | import com.ffii.tsms.modules.data.web.models.FinancialSummaryByProject | ||||
| import com.ffii.tsms.modules.data.web.models.SaveCustomerResponse | import com.ffii.tsms.modules.data.web.models.SaveCustomerResponse | ||||
| import com.ffii.tsms.modules.project.entity.Invoice | |||||
| import com.ffii.tsms.modules.project.entity.Project | |||||
| import com.ffii.tsms.modules.project.entity.* | |||||
| import com.ffii.tsms.modules.project.entity.projections.DashboardData | |||||
| import com.ffii.tsms.modules.project.service.InvoiceService | |||||
| import com.ffii.tsms.modules.project.service.ProjectExpenseService | |||||
| import com.ffii.tsms.modules.project.service.ProjectsService | |||||
| import com.ffii.tsms.modules.project.web.models.SaveCustomerRequest | import com.ffii.tsms.modules.project.web.models.SaveCustomerRequest | ||||
| import com.ffii.tsms.modules.timesheet.entity.Timesheet | import com.ffii.tsms.modules.timesheet.entity.Timesheet | ||||
| import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository | |||||
| import com.ffii.tsms.modules.timesheet.service.TimesheetsService | |||||
| import org.apache.poi.ss.usermodel.* | import org.apache.poi.ss.usermodel.* | ||||
| import org.apache.poi.ss.util.CellRangeAddress | import org.apache.poi.ss.util.CellRangeAddress | ||||
| import org.apache.poi.ss.util.CellUtil | import org.apache.poi.ss.util.CellUtil | ||||
| import org.apache.poi.util.ArrayUtil | |||||
| import org.apache.poi.xssf.usermodel.XSSFWorkbook | import org.apache.poi.xssf.usermodel.XSSFWorkbook | ||||
| import org.springframework.beans.BeanUtils | import org.springframework.beans.BeanUtils | ||||
| import org.springframework.core.io.ClassPathResource | import org.springframework.core.io.ClassPathResource | ||||
| @@ -33,7 +43,17 @@ open class DashboardService( | |||||
| private val customerTypeRepository: CustomerTypeRepository, | private val customerTypeRepository: CustomerTypeRepository, | ||||
| private val customerSubsidiaryService: CustomerSubsidiaryService, | private val customerSubsidiaryService: CustomerSubsidiaryService, | ||||
| private val customerContactService: CustomerContactService, | private val customerContactService: CustomerContactService, | ||||
| private val projectRepository: ProjectRepository, | |||||
| private val timesheetsService: TimesheetsService, | |||||
| private val invoiceService: InvoiceService, | |||||
| private val projectExpenseService: ProjectExpenseService, | |||||
| private val timesheetRepository: TimesheetRepository, | |||||
| private val invoiceRepository: InvoiceRepository, | |||||
| private val staffsService: StaffsService, | private val staffsService: StaffsService, | ||||
| private val teamLogService: TeamLogService, | |||||
| private val salaryEffectiveService: SalaryEffectiveService, | |||||
| private val projectExpenseRepository: ProjectExpenseRepository, | |||||
| private val teamRepository: TeamRepository, | |||||
| private val jdbcDao: JdbcDao | private val jdbcDao: JdbcDao | ||||
| ) { | ) { | ||||
| private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") | private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") | ||||
| @@ -3204,6 +3224,256 @@ open class DashboardService( | |||||
| return outputStream.toByteArray() | return outputStream.toByteArray() | ||||
| } | } | ||||
| open fun testing ( | |||||
| projectId: Long, | |||||
| startDate: LocalDate?, | |||||
| endDate: LocalDate?, | |||||
| ): DashboardData { | |||||
| var i = 0 | |||||
| // data to add | |||||
| var manhourExpense = 0.0 | |||||
| var projectExpense = 0.0 | |||||
| var invoicedAmount = 0.0 | |||||
| var receivedAmount = 0.0 | |||||
| val project = projectRepository.findById(projectId).orElseThrow() | |||||
| val timesheet = timesheetRepository.findAll() | |||||
| val pe = projectExpenseRepository.findAllByProjectIdAndDeletedFalse(projectId) | |||||
| val invoice = invoiceRepository.findAll() | |||||
| val maxSize = maxOf(timesheet.size, pe.size, invoice.size) | |||||
| for (i in 0 until maxSize) { | |||||
| val currTs = if (i < timesheet.size) timesheet[i] else null | |||||
| val currPe = if (i < pe.size) pe[i] else null | |||||
| val currInvoice = if (i < invoice.size) invoice[i] else null | |||||
| if (currTs != null | |||||
| && currTs.deleted == false | |||||
| && currTs.project != null | |||||
| && currTs.project!!.id == projectId | |||||
| && CheckingUtils.checkTimePeriod(currTs.recordDate!!, startDate, endDate)) { | |||||
| val otMultiplier = 1.0 | |||||
| val crossTeamMultipier = 1.0 | |||||
| val staffId = currTs.staff!!.id!! | |||||
| val staffTeam = teamLogService.getStaffTeamLog(staffId, currTs.recordDate!!) | |||||
| val thisSE = salaryEffectiveService.getStaffSalaryEffective(staffId, currTs.recordDate!!) | |||||
| val hourlyRate = thisSE!!.salary.hourlyRate | |||||
| val normalHour = currTs.normalConsumed ?: 0.0 | |||||
| val otHour = currTs.otConsumed ?: 0.0 | |||||
| val normalCost = normalHour.times(hourlyRate.toDouble()) | |||||
| val otCost = otHour.times(hourlyRate.toDouble()).times(otMultiplier) | |||||
| if (currTs.project!!.teamLead?.id != staffTeam!!.team.id) { // cross team | |||||
| manhourExpense += (normalCost + otCost).times(crossTeamMultipier) | |||||
| } else { | |||||
| manhourExpense += (normalCost + otCost) | |||||
| } | |||||
| } | |||||
| if (currPe != null | |||||
| && currPe.deleted == false | |||||
| && currPe.project!!.id == projectId | |||||
| && CheckingUtils.checkTimePeriod(currPe.issueDate!!, startDate, endDate)) { | |||||
| projectExpense += currPe.amount!! | |||||
| } | |||||
| if (currInvoice != null | |||||
| && currInvoice.deleted == false | |||||
| && currInvoice.projectCode == project.code | |||||
| && CheckingUtils.checkTimePeriod(currInvoice.invoiceDate!!, startDate, endDate)) { | |||||
| invoicedAmount += currInvoice.issueAmount!!.toDouble() | |||||
| receivedAmount += currInvoice.paidAmount?.toDouble() ?: 0.0 | |||||
| } | |||||
| // i++ | |||||
| } | |||||
| val cumulativeExpenditure = manhourExpense + projectExpense | |||||
| val nonInvoicedAmount = (project.expectedTotalFee?: 0.0) - invoicedAmount | |||||
| val output = DashboardData( | |||||
| cumulativeExpenditure, | |||||
| manhourExpense, | |||||
| projectExpense, | |||||
| invoicedAmount, | |||||
| nonInvoicedAmount, | |||||
| receivedAmount, | |||||
| // if (invoicedAmount >= manhourExpense+projectExpense) "Positive" else "Negative", | |||||
| // if (project.expectedTotalFee!! >= cumulativeExpenditure) "Positive" else "Negative", | |||||
| if (cumulativeExpenditure > 0.0) invoicedAmount/cumulativeExpenditure else 0.0, | |||||
| if (cumulativeExpenditure > 0.0) project.expectedTotalFee!!/cumulativeExpenditure else 0.0 | |||||
| ) | |||||
| return output | |||||
| } | |||||
| open fun getProjectDashboardDataByProjectId( | |||||
| projectId: Long, | |||||
| startDate: LocalDate?, | |||||
| endDate: LocalDate?, | |||||
| ): DashboardData { | |||||
| val project = projectRepository.findById(projectId).orElseThrow() | |||||
| val manhourExpense = timesheetsService.getManpowerExpenseByProjectId(projectId,startDate,endDate) | |||||
| val projectExpense = projectExpenseService.getProjectExpenseByProjectId(projectId,startDate,endDate) | |||||
| val invoiceData = invoiceService.getInvoiceDataByProjectId(projectId,startDate,endDate) | |||||
| // var index = 0 | |||||
| // while () | |||||
| val cumulativeExpenditure = manhourExpense + projectExpense | |||||
| val output = DashboardData( | |||||
| cumulativeExpenditure, | |||||
| manhourExpense, | |||||
| projectExpense, | |||||
| invoiceData.invoicedAmount, | |||||
| invoiceData.nonInvoicedAmount, | |||||
| invoiceData.receivedAmount, | |||||
| // if (invoiceData.invoicedAmount >= manhourExpense+projectExpense) "Positive" else "Negative", | |||||
| // if (project.expectedTotalFee!! >= cumulativeExpenditure) "Positive" else "Negative", | |||||
| if (cumulativeExpenditure > 0.0) invoiceData.invoicedAmount/cumulativeExpenditure else 0.0, | |||||
| if (cumulativeExpenditure > 0.0) project.expectedTotalFee!!/cumulativeExpenditure else 0.0 | |||||
| ) | |||||
| return output | |||||
| } | |||||
| open fun fetchFinancialSummaryByProject( | |||||
| teamId: Long, | |||||
| startDate: LocalDate?, | |||||
| endDate: LocalDate, | |||||
| ): List<Map<String, Any>> { | |||||
| val output = mutableListOf<Map<String, Any>>() | |||||
| val projects = projectRepository.findAll() | |||||
| for (curr in projects) { | |||||
| if (curr.deleted == false && curr.teamLead?.team?.id == teamId && curr.status == "On-going") { | |||||
| val data = testing(curr.id!!, startDate, endDate) | |||||
| val item = mapOf<String, Any>( | |||||
| "id" to curr.id!!, | |||||
| "custId" to if (curr.customer != null ) curr.customer!!.id else -1, | |||||
| "projectCode" to curr.code!!, | |||||
| "projectName" to curr.name!!, | |||||
| "customerName" to if (curr.customer != null) curr.customer!!.name else "", | |||||
| "customerName" to if (curr.customer != null) curr.customer!!.code else "", | |||||
| "subsidiaryName" to if (curr.customerSubsidiary != null) curr.customerSubsidiary!!.name else "", | |||||
| "totalFee" to curr.expectedTotalFee!!, | |||||
| "totalBudget" to curr.expectedTotalFee!! * 0.8, | |||||
| "cumulativeExpenditure" to data.cumulativeExpenditure, | |||||
| "manhourExpense" to data.manhourExpense, | |||||
| "projectExpense" to data.projectExpense, | |||||
| "invoicedAmount" to data.invoicedAmount, | |||||
| "nonInvoicedAmount" to data.nonInvoicedAmount, | |||||
| "receivedAmount" to data.receivedAmount, | |||||
| // "cashFlowStatus" to data.cashFlowStatus, | |||||
| // "projectedCashFlowStatus" to data.projectedCashFlowStatus, | |||||
| "cpi" to data.cpi, | |||||
| "projectedCpi" to data.projectedCpi, | |||||
| ) | |||||
| output.add(item) | |||||
| } | |||||
| } | |||||
| println(output) | |||||
| return output | |||||
| } | |||||
| open fun fetchFinancialSummary( | |||||
| startDate: LocalDate?, | |||||
| endDate: LocalDate, | |||||
| teamId: Long | |||||
| ): Map<String, Any> { | |||||
| fun checkTimePeriod(checkDate: LocalDate): Boolean { | |||||
| return if (startDate == null) { | |||||
| checkDate.isBefore(endDate) | |||||
| } else { | |||||
| checkDate.isAfter(startDate) && checkDate.isBefore(endDate) | |||||
| } | |||||
| } | |||||
| val projects = projectRepository.findAll() | |||||
| // active project | |||||
| val activeProject = projects | |||||
| .filter { | |||||
| it.deleted == false | |||||
| && it.teamLead!!.id == teamId | |||||
| && it.status == "On-going" | |||||
| }.size | |||||
| var totalFee = 0.0 | |||||
| val projectCodes = mutableListOf<String>() | |||||
| val projectIds = mutableListOf<Long>() | |||||
| for (curr in projects) { | |||||
| val isTargetTeam = curr.teamLead!!.id == teamId | |||||
| val isOnGoing = curr.status == "On-going" | |||||
| if (curr.deleted == false && isTargetTeam && isOnGoing) { | |||||
| totalFee += curr.expectedTotalFee ?: 0.0 | |||||
| projectCodes.add(curr.code!!) | |||||
| projectIds.add(curr.id!!) | |||||
| } | |||||
| } | |||||
| // manpower Expense | |||||
| var manpowerExpense = 0.0 | |||||
| val timesheet = timesheetRepository.findAll() | |||||
| for (curr in timesheet) { | |||||
| val projectTeam = curr.project?.teamLead?.team | |||||
| val recordDate = curr.recordDate!! | |||||
| val staffId = curr.staff!!.id!! | |||||
| if (checkTimePeriod(recordDate) && curr.deleted == false && projectTeam?.id == teamId) { | |||||
| val crossTeamMultiplier = 1.0 | |||||
| val otMultiplier = 1.0 | |||||
| val staffTeam = teamLogService.getStaffTeamLog(staffId, recordDate)?.team?.code | |||||
| val salaryEffective = salaryEffectiveService.getStaffSalaryEffective(staffId, recordDate)!! | |||||
| val normalHour = curr.normalConsumed ?: 0.0 | |||||
| val otHour = curr.otConsumed ?: 0.0 | |||||
| val normalCost = normalHour.times(salaryEffective.salary.hourlyRate.toDouble()) | |||||
| val otCost = otHour.times(salaryEffective.salary.hourlyRate.toDouble()).times(otMultiplier) | |||||
| val totalCost = normalCost + otCost; | |||||
| // crossed team | |||||
| if (staffTeam != projectTeam.code) { | |||||
| manpowerExpense += totalCost.times(crossTeamMultiplier); | |||||
| } else { | |||||
| manpowerExpense += totalCost; | |||||
| } | |||||
| } | |||||
| } | |||||
| // project expense | |||||
| var projectExpense = 0.0 | |||||
| val projectExpenseList = projectExpenseRepository.findAll() | |||||
| for (curr in projectExpenseList) { | |||||
| val issueDate = curr.issueDate!! | |||||
| if (checkTimePeriod(issueDate) && curr.project?.teamLead?.team?.id == teamId && curr.deleted == false) { | |||||
| projectExpense += curr.amount ?: 0.0 | |||||
| } | |||||
| } | |||||
| var invoicedAmount = 0.0 | |||||
| var paidAmount = 0.0 | |||||
| val invoice = invoiceRepository.findAllByProjectCodeIn(projectCodes) | |||||
| for (curr in invoice) { | |||||
| val invoiceDate = curr.invoiceDate!! | |||||
| if (curr.deleted == false && checkTimePeriod(invoiceDate)) { | |||||
| invoicedAmount += curr.issueAmount?.toDouble() ?: 0.0 | |||||
| paidAmount += curr.paidAmount?.toDouble() ?: 0.0 | |||||
| } | |||||
| } | |||||
| val cumulativeExpenditure = manpowerExpense + projectExpense | |||||
| // cashFlowStatus | |||||
| val cashFlowStatus = if (invoicedAmount >= cumulativeExpenditure) { | |||||
| "Positive" | |||||
| } else { | |||||
| "Negative" | |||||
| } | |||||
| // projectedCashFlowStatus | |||||
| val projectedCashFlowStatus = if (totalFee >= cumulativeExpenditure) { | |||||
| "Positive" | |||||
| } else { | |||||
| "Negative" | |||||
| } | |||||
| val dataMap = mapOf( | |||||
| // team project basic info | |||||
| "team" to teamRepository.findById(teamId), | |||||
| "activeProject" to activeProject, | |||||
| "totalFees" to totalFee, | |||||
| "totalBudget" to totalFee * 0.8, | |||||
| // timesheet data | |||||
| "cumulativeExpenditure" to cumulativeExpenditure, | |||||
| "manpowerExpense" to manpowerExpense, | |||||
| "projectExpense" to projectExpense, | |||||
| // invoice data | |||||
| "invoicedAmount" to invoicedAmount, | |||||
| "nonInvoicedAmount" to totalFee - invoicedAmount, | |||||
| "receivedAmount" to paidAmount, | |||||
| // calculation | |||||
| "cashFlowStatus" to cashFlowStatus, | |||||
| "costPerformanceIndex" to if (cumulativeExpenditure > 0.0) invoicedAmount / cumulativeExpenditure else 0.0, | |||||
| "projectedCashFlowStatus" to projectedCashFlowStatus, | |||||
| "projectedCostPerformanceIndex" to if (cumulativeExpenditure > 0.0) totalFee / cumulativeExpenditure else 0.0 | |||||
| ) | |||||
| return dataMap | |||||
| } | |||||
| @Throws(IOException::class) | @Throws(IOException::class) | ||||
| private fun createFinancialSummaryByProjectExcel( | private fun createFinancialSummaryByProjectExcel( | ||||
| financialSummaryByProjects: List<FinancialSummaryByProject>, | financialSummaryByProjects: List<FinancialSummaryByProject>, | ||||
| @@ -133,6 +133,15 @@ open class SalaryEffectiveService( | |||||
| } | } | ||||
| } | } | ||||
| open fun getStaffSalaryEffective(staffId: Long, recordDate: LocalDate): SalaryEffective? { | |||||
| val salaryEffective = salaryEffectiveRepository.findAll() | |||||
| val thisSE = salaryEffective.find { | |||||
| it.staff.id == staffId | |||||
| && it.startDate.isBefore(recordDate) && (it.endDate.isAfter(recordDate) || it.endDate == null) | |||||
| } | |||||
| return thisSE | |||||
| } | |||||
| data class SalaryData(val idInStaff: Long, val staffId: String, val financialYear: LocalDate, val hourlyRate: BigDecimal, val salaryPoint: Int) | data class SalaryData(val idInStaff: Long, val staffId: String, val financialYear: LocalDate, val hourlyRate: BigDecimal, val salaryPoint: Int) | ||||
| data class StaffSalaryData(val staffId: String, val salaryData: List<SalaryData>) | data class StaffSalaryData(val staffId: String, val salaryData: List<SalaryData>) | ||||
| @@ -8,6 +8,7 @@ import com.ffii.tsms.modules.data.entity.TeamRepository | |||||
| import com.ffii.tsms.modules.data.entity.projections.TeamLogInfo | import com.ffii.tsms.modules.data.entity.projections.TeamLogInfo | ||||
| import com.ffii.tsms.modules.data.web.models.TeamHistory | import com.ffii.tsms.modules.data.web.models.TeamHistory | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import java.time.LocalDate | |||||
| @Service | @Service | ||||
| open class TeamLogService ( | open class TeamLogService ( | ||||
| @@ -19,6 +20,16 @@ open class TeamLogService ( | |||||
| return teamLogRepository.findTeamLogInfoByStaffIdAndDeletedFalseOrderByCreatedDesc(staffId).drop(1) | return teamLogRepository.findTeamLogInfoByStaffIdAndDeletedFalseOrderByCreatedDesc(staffId).drop(1) | ||||
| } | } | ||||
| open fun getStaffTeamLog(staffId:Long, recordDate: LocalDate): TeamLog? { | |||||
| val teamLog = teamLogRepository.findAll() | |||||
| val team = teamLog.find{ | |||||
| it.deleted == false | |||||
| && it.staff.id == staffId | |||||
| && it.from.isBefore(recordDate) && (it.to == null || it.to.isAfter(recordDate)) | |||||
| } | |||||
| return team | |||||
| } | |||||
| open fun editTeamLog(teamHistory: List<TeamHistory>, delTeamHistory: List<Long>) { | open fun editTeamLog(teamHistory: List<TeamHistory>, delTeamHistory: List<Long>) { | ||||
| if (delTeamHistory.isNotEmpty()) { | if (delTeamHistory.isNotEmpty()) { | ||||
| delTeamHistory.forEach { | delTeamHistory.forEach { | ||||
| @@ -15,13 +15,18 @@ import org.springframework.web.bind.annotation.DeleteMapping | |||||
| import org.springframework.web.bind.annotation.ResponseStatus | import org.springframework.web.bind.annotation.ResponseStatus | ||||
| import com.ffii.core.response.RecordsRes | import com.ffii.core.response.RecordsRes | ||||
| import com.ffii.core.utils.CriteriaArgsBuilder | import com.ffii.core.utils.CriteriaArgsBuilder | ||||
| import com.ffii.tsms.modules.data.entity.TeamRepository | |||||
| import com.ffii.tsms.modules.data.service.* | import com.ffii.tsms.modules.data.service.* | ||||
| import com.ffii.tsms.modules.data.web.models.ExportFinancialSummaryByClientExcelRequest | import com.ffii.tsms.modules.data.web.models.ExportFinancialSummaryByClientExcelRequest | ||||
| import com.ffii.tsms.modules.data.web.models.ExportFinancialSummaryByProjectExcelRequest | import com.ffii.tsms.modules.data.web.models.ExportFinancialSummaryByProjectExcelRequest | ||||
| import com.ffii.tsms.modules.data.web.models.FinancialSummaryRequest | |||||
| import com.ffii.tsms.modules.project.entity.projections.DashboardData | |||||
| import org.springframework.core.io.ByteArrayResource | import org.springframework.core.io.ByteArrayResource | ||||
| import org.springframework.core.io.Resource | import org.springframework.core.io.Resource | ||||
| import org.springframework.http.ResponseEntity | import org.springframework.http.ResponseEntity | ||||
| import org.springframework.web.bind.annotation.RequestParam | |||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import java.time.LocalDateTime | |||||
| @RestController | @RestController | ||||
| @RequestMapping("/dashboard") | @RequestMapping("/dashboard") | ||||
| @@ -30,6 +35,7 @@ class DashboardController( | |||||
| private val customerSubsidiaryService: CustomerSubsidiaryService, | private val customerSubsidiaryService: CustomerSubsidiaryService, | ||||
| private val customerContactService: CustomerContactService, | private val customerContactService: CustomerContactService, | ||||
| private val dashboardService: DashboardService, | private val dashboardService: DashboardService, | ||||
| private val teamRepository: TeamRepository, | |||||
| private val staffsService: StaffsService, | private val staffsService: StaffsService, | ||||
| ) { | ) { | ||||
| @GetMapping("/searchCustomerSubsidiary") | @GetMapping("/searchCustomerSubsidiary") | ||||
| @@ -465,6 +471,37 @@ class DashboardController( | |||||
| val args = mutableMapOf<String, Any>() | val args = mutableMapOf<String, Any>() | ||||
| return dashboardService.staffCombo(args) | return dashboardService.staffCombo(args) | ||||
| } | } | ||||
| @GetMapping("/getFinancialSummaryByProject") | |||||
| fun getFinancialSummaryByProject(@Valid request: HttpServletRequest): List<Map<String, Any>> { | |||||
| println("") | |||||
| println("start...: ${LocalDateTime.now()}") | |||||
| val startDate = if (request.getParameter("startDate") != null) LocalDate.parse(request.getParameter("startDate")) else null | |||||
| val endDate = LocalDate.parse(request.getParameter("endDate")) | |||||
| val teamId = request.getParameter("teamId").toLong() | |||||
| val output = dashboardService.fetchFinancialSummaryByProject(teamId, startDate, endDate) | |||||
| println("end...: ${LocalDateTime.now()}") | |||||
| return output | |||||
| } | |||||
| @GetMapping("/getFinancialSummary") | |||||
| fun getFinancialSummary(@Valid request: HttpServletRequest): List<Map<String, Any>> { | |||||
| val output = mutableListOf<Map<String, Any>>() | |||||
| val startDate = if (request.getParameter("startDate") != null) LocalDate.parse(request.getParameter("startDate")) else null | |||||
| val endDate = LocalDate.parse(request.getParameter("endDate")) | |||||
| val targetTeam = request.getParameter("teamId") ?: null | |||||
| val teams = teamRepository.findAll() | |||||
| val teamIds: List<Long> = if (targetTeam != null) { | |||||
| listOf(targetTeam.toLong()) | |||||
| } else { | |||||
| teams.mapNotNull { | |||||
| if (it.deleted == false) it.id else null | |||||
| } | |||||
| } | |||||
| for (id in teamIds) { | |||||
| output.add(dashboardService.fetchFinancialSummary(startDate, endDate, id)) | |||||
| } | |||||
| return output | |||||
| } | |||||
| @PostMapping("/exportFinancialSummaryByClientExcel") | @PostMapping("/exportFinancialSummaryByClientExcel") | ||||
| fun exportFinancialSummaryByClientExcel(@Valid @RequestBody request: ExportFinancialSummaryByClientExcelRequest): ResponseEntity<Resource> { | fun exportFinancialSummaryByClientExcel(@Valid @RequestBody request: ExportFinancialSummaryByClientExcelRequest): ResponseEntity<Resource> { | ||||
| @@ -15,6 +15,7 @@ interface InvoiceRepository : AbstractRepository<Invoice, Long> { | |||||
| fun findInvoiceInfoByPaidAmountIsNotNull(): List<InvoiceInfo> | fun findInvoiceInfoByPaidAmountIsNotNull(): List<InvoiceInfo> | ||||
| fun findByInvoiceNo(invoiceNo: String): Invoice | fun findByInvoiceNo(invoiceNo: String): Invoice | ||||
| fun findAllByProjectCodeIn(projectCode: List<String>): List<Invoice> | |||||
| fun findAllByProjectCodeAndPaidAmountIsNotNullAndDeletedFalse(projectCode: String): List<Invoice> | fun findAllByProjectCodeAndPaidAmountIsNotNullAndDeletedFalse(projectCode: String): List<Invoice> | ||||
| } | } | ||||
| @@ -7,4 +7,6 @@ interface ProjectExpenseRepository : AbstractRepository<ProjectExpense, Long> { | |||||
| fun findExpenseSearchInfoByDeletedFalseOrderByProjectCode(): List<ProjectExpenseSearchInfo> | fun findExpenseSearchInfoByDeletedFalseOrderByProjectCode(): List<ProjectExpenseSearchInfo> | ||||
| fun findAllByProjectIdAndDeletedFalse(projectId: Long): List<ProjectExpense> | fun findAllByProjectIdAndDeletedFalse(projectId: Long): List<ProjectExpense> | ||||
| fun findAllByProjectIdInAndDeletedFalse(projectIds: List<Long>): List<ProjectExpense> | |||||
| } | } | ||||
| @@ -0,0 +1,17 @@ | |||||
| package com.ffii.tsms.modules.project.entity.projections | |||||
| data class DashboardData( | |||||
| // timesheet data | |||||
| val cumulativeExpenditure: Double, | |||||
| val manhourExpense: Double, | |||||
| val projectExpense: Double, | |||||
| // invoice data | |||||
| val invoicedAmount: Double, | |||||
| val nonInvoicedAmount: Double, | |||||
| val receivedAmount: Double, | |||||
| // calculation | |||||
| // val cashFlowStatus: String, | |||||
| // val projectedCashFlowStatus: String, | |||||
| val cpi: Double, | |||||
| val projectedCpi: Double, | |||||
| ) | |||||
| @@ -0,0 +1,8 @@ | |||||
| package com.ffii.tsms.modules.project.entity.projections | |||||
| data class InvoiceData( | |||||
| // invoice data | |||||
| val invoicedAmount: Double, | |||||
| val nonInvoicedAmount: Double, | |||||
| val receivedAmount: Double, | |||||
| ) | |||||
| @@ -4,7 +4,9 @@ import com.ffii.core.support.AbstractIdEntityService | |||||
| import com.ffii.core.support.JdbcDao | import com.ffii.core.support.JdbcDao | ||||
| import com.ffii.core.utils.ExcelUtils | import com.ffii.core.utils.ExcelUtils | ||||
| import com.ffii.core.utils.PdfUtils | import com.ffii.core.utils.PdfUtils | ||||
| import com.ffii.core.utils.CheckingUtils | |||||
| import com.ffii.tsms.modules.project.entity.* | import com.ffii.tsms.modules.project.entity.* | ||||
| import com.ffii.tsms.modules.project.entity.projections.InvoiceData | |||||
| import com.ffii.tsms.modules.project.entity.projections.InvoiceInfo | import com.ffii.tsms.modules.project.entity.projections.InvoiceInfo | ||||
| import com.ffii.tsms.modules.project.entity.projections.InvoicePDFReq | import com.ffii.tsms.modules.project.entity.projections.InvoicePDFReq | ||||
| @@ -33,12 +35,32 @@ import kotlin.math.min | |||||
| @Service | @Service | ||||
| open class InvoiceService( | open class InvoiceService( | ||||
| private val invoiceRepository: InvoiceRepository, | private val invoiceRepository: InvoiceRepository, | ||||
| private val projectRepository: ProjectRepository, | |||||
| private val milestoneRepository: MilestoneRepository, | private val milestoneRepository: MilestoneRepository, | ||||
| private val milestonePaymentRepository: MilestonePaymentRepository, | private val milestonePaymentRepository: MilestonePaymentRepository, | ||||
| private val projectService: ProjectsService, | private val projectService: ProjectsService, | ||||
| private val jdbcDao: JdbcDao, | private val jdbcDao: JdbcDao, | ||||
| ) : AbstractIdEntityService<Invoice, Long, InvoiceRepository>(jdbcDao, invoiceRepository){ | ) : AbstractIdEntityService<Invoice, Long, InvoiceRepository>(jdbcDao, invoiceRepository){ | ||||
| open fun getInvoiceDataByProjectId(projectId: Long, startDate: LocalDate?, endDate: LocalDate?): InvoiceData { | |||||
| val project = projectRepository.findById(projectId).orElseThrow() | |||||
| val code = project.code | |||||
| val invoice = invoiceRepository.findAll() | |||||
| var invoicedAmount = 0.0 | |||||
| var receivedAmount = 0.0 | |||||
| for (curr in invoice) { | |||||
| if (curr.deleted == false | |||||
| && curr.projectCode == code | |||||
| && CheckingUtils.checkTimePeriod(curr.invoiceDate!!, startDate, endDate) | |||||
| ) { | |||||
| invoicedAmount += curr.issueAmount!!.toDouble() | |||||
| receivedAmount += curr.paidAmount?.toDouble() ?: 0.0 | |||||
| } | |||||
| } | |||||
| val nonInvoicedAmount = (project.expectedTotalFee?: 0.0) - invoicedAmount | |||||
| return InvoiceData(invoicedAmount, nonInvoicedAmount, receivedAmount) | |||||
| } | |||||
| open fun findAllByProjectAndPaidAmountIsNotNull(project: Project): List<Invoice> { | open fun findAllByProjectAndPaidAmountIsNotNull(project: Project): List<Invoice> { | ||||
| val milestones = milestoneRepository.findAllByProject(project) | val milestones = milestoneRepository.findAllByProject(project) | ||||
| val milestonePayments = milestonePaymentRepository.findAllByMilestoneIn(milestones) | val milestonePayments = milestonePaymentRepository.findAllByMilestoneIn(milestones) | ||||
| @@ -2,12 +2,14 @@ package com.ffii.tsms.modules.project.service | |||||
| import com.ffii.core.support.AbstractIdEntityService | import com.ffii.core.support.AbstractIdEntityService | ||||
| import com.ffii.core.support.JdbcDao | import com.ffii.core.support.JdbcDao | ||||
| import com.ffii.core.utils.CheckingUtils | |||||
| import com.ffii.tsms.modules.project.entity.ProjectExpense | import com.ffii.tsms.modules.project.entity.ProjectExpense | ||||
| import com.ffii.tsms.modules.project.entity.ProjectExpenseRepository | import com.ffii.tsms.modules.project.entity.ProjectExpenseRepository | ||||
| import com.ffii.tsms.modules.project.entity.ProjectRepository | import com.ffii.tsms.modules.project.entity.ProjectRepository | ||||
| import com.ffii.tsms.modules.project.entity.projections.ProjectExpenseSearchInfo | import com.ffii.tsms.modules.project.entity.projections.ProjectExpenseSearchInfo | ||||
| import com.ffii.tsms.modules.project.web.models.EditProjectExpenseRequest | import com.ffii.tsms.modules.project.web.models.EditProjectExpenseRequest | ||||
| import com.ffii.tsms.modules.project.web.models.ProjectExpenseRequest | import com.ffii.tsms.modules.project.web.models.ProjectExpenseRequest | ||||
| import org.springframework.cglib.core.Local | |||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| @@ -22,6 +24,21 @@ open class ProjectExpenseService( | |||||
| return projectExpenseRepository.findExpenseSearchInfoByDeletedFalseOrderByProjectCode() | return projectExpenseRepository.findExpenseSearchInfoByDeletedFalseOrderByProjectCode() | ||||
| } | } | ||||
| open fun getProjectExpenseByProjectId(projectId: Long, startDate: LocalDate?, endDate: LocalDate?): Double { | |||||
| val projectExpense = projectExpenseRepository.findAllByProjectIdAndDeletedFalse(projectId) | |||||
| var sum = 0.0 | |||||
| for (curr in projectExpense) { | |||||
| if (curr!!.deleted == false | |||||
| && curr.project!!.id == projectId | |||||
| // issueDate might be an issue | |||||
| && CheckingUtils.checkTimePeriod(curr.issueDate!!, startDate, endDate) | |||||
| ) { | |||||
| sum += curr.amount ?: 0.0 | |||||
| } | |||||
| } | |||||
| return sum | |||||
| } | |||||
| open fun createProjectExpense(projectExpenseList: List<ProjectExpenseRequest>): Boolean { | open fun createProjectExpense(projectExpenseList: List<ProjectExpenseRequest>): Boolean { | ||||
| try { | try { | ||||
| for (projectExpense in projectExpenseList) { | for (projectExpense in projectExpenseList) { | ||||
| @@ -1,5 +1,6 @@ | |||||
| package com.ffii.tsms.modules.project.service | package com.ffii.tsms.modules.project.service | ||||
| import com.ffii.core.utils.CheckingUtils | |||||
| import com.ffii.core.utils.ExcelUtils | import com.ffii.core.utils.ExcelUtils | ||||
| import com.ffii.tsms.modules.common.SecurityUtils | import com.ffii.tsms.modules.common.SecurityUtils | ||||
| import com.ffii.tsms.modules.data.entity.* | import com.ffii.tsms.modules.data.entity.* | ||||
| @@ -7,11 +8,10 @@ import com.ffii.tsms.modules.data.service.* | |||||
| import com.ffii.tsms.modules.data.web.models.SaveCustomerResponse | import com.ffii.tsms.modules.data.web.models.SaveCustomerResponse | ||||
| import com.ffii.tsms.modules.project.entity.* | import com.ffii.tsms.modules.project.entity.* | ||||
| import com.ffii.tsms.modules.project.entity.Milestone | import com.ffii.tsms.modules.project.entity.Milestone | ||||
| 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 com.ffii.tsms.modules.project.entity.projections.* | |||||
| import com.ffii.tsms.modules.project.web.models.* | import com.ffii.tsms.modules.project.web.models.* | ||||
| import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository | import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository | ||||
| import com.ffii.tsms.modules.timesheet.service.TimesheetsService | |||||
| import org.apache.commons.logging.LogFactory | import org.apache.commons.logging.LogFactory | ||||
| import org.apache.poi.ss.usermodel.CellType | import org.apache.poi.ss.usermodel.CellType | ||||
| import org.apache.poi.ss.usermodel.DataFormatter | import org.apache.poi.ss.usermodel.DataFormatter | ||||
| @@ -31,6 +31,9 @@ import kotlin.jvm.optionals.getOrNull | |||||
| @Service | @Service | ||||
| open class ProjectsService( | open class ProjectsService( | ||||
| private val projectRepository: ProjectRepository, | private val projectRepository: ProjectRepository, | ||||
| // private val invoiceService: InvoiceService, | |||||
| private val invoiceRepository: InvoiceRepository, | |||||
| private val projectExpenseService: ProjectExpenseService, | |||||
| private val customerService: CustomerService, | private val customerService: CustomerService, | ||||
| private val tasksService: TasksService, | private val tasksService: TasksService, | ||||
| private val customerContactService: CustomerContactService, | private val customerContactService: CustomerContactService, | ||||
| @@ -51,6 +54,7 @@ open class ProjectsService( | |||||
| private val milestonePaymentRepository: MilestonePaymentRepository, | private val milestonePaymentRepository: MilestonePaymentRepository, | ||||
| private val taskGroupRepository: TaskGroupRepository, | private val taskGroupRepository: TaskGroupRepository, | ||||
| private val timesheetRepository: TimesheetRepository, | private val timesheetRepository: TimesheetRepository, | ||||
| private val timesheetsService: TimesheetsService, | |||||
| private val taskTemplateRepository: TaskTemplateRepository, | private val taskTemplateRepository: TaskTemplateRepository, | ||||
| private val subsidiaryContactService: SubsidiaryContactService, | private val subsidiaryContactService: SubsidiaryContactService, | ||||
| private val subsidiaryContactRepository: SubsidiaryContactRepository, | private val subsidiaryContactRepository: SubsidiaryContactRepository, | ||||
| @@ -376,6 +380,48 @@ open class ProjectsService( | |||||
| ) | ) | ||||
| } | } | ||||
| } | } | ||||
| open fun getInvoiceDataByProjectId(projectId: Long, startDate: LocalDate?, endDate: LocalDate?): InvoiceData { | |||||
| val project = projectRepository.findById(projectId).orElseThrow() | |||||
| val code = project.code | |||||
| val invoice = invoiceRepository.findAll() | |||||
| var invoicedAmount = 0.0 | |||||
| var receivedAmount = 0.0 | |||||
| for (curr in invoice) { | |||||
| if (curr.deleted == false | |||||
| && curr.projectCode == code | |||||
| && CheckingUtils.checkTimePeriod(curr.invoiceDate!!, startDate, endDate) | |||||
| ) { | |||||
| invoicedAmount += curr.issueAmount!!.toDouble() | |||||
| receivedAmount += curr.paidAmount?.toDouble() ?: 0.0 | |||||
| } | |||||
| } | |||||
| val nonInvoicedAmount = (project.expectedTotalFee?: 0.0) - invoicedAmount | |||||
| return InvoiceData(invoicedAmount, nonInvoicedAmount, receivedAmount) | |||||
| } | |||||
| // open fun getProjectDashboardDataByProjectId( | |||||
| // projectId: Long, | |||||
| // startDate: LocalDate?, | |||||
| // endDate: LocalDate?, | |||||
| // ): DashboardData { | |||||
| // val project = projectRepository.findById(projectId).orElseThrow() | |||||
| // val manhourExpense = timesheetsService.getManpowerExpenseByProjectId(projectId,startDate,endDate) | |||||
| // val projectExpense = projectExpenseService.getProjectExpenseByProjectId(projectId,startDate,endDate) | |||||
| // val invoiceData = getInvoiceDataByProjectId(projectId,startDate,endDate) | |||||
| // val cumulativeExpenditure = manhourExpense+projectExpense | |||||
| // val output = DashboardData( | |||||
| // cumulativeExpenditure, | |||||
| // manhourExpense, | |||||
| // projectExpense, | |||||
| // invoiceData.invoicedAmount, | |||||
| // invoiceData.nonInvoicedAmount, | |||||
| // invoiceData.receivedAmount, | |||||
| // if (invoiceData.invoicedAmount >= manhourExpense+projectExpense) "Positive" else "Negative", | |||||
| // if (project.expectedTotalFee!! >= cumulativeExpenditure) "Positive" else "Negative", | |||||
| // if (cumulativeExpenditure > 0.0) invoiceData.invoicedAmount/cumulativeExpenditure else 0.0, | |||||
| // if (cumulativeExpenditure > 0.0) project.expectedTotalFee!!/cumulativeExpenditure else 0.0 | |||||
| // ) | |||||
| // return output | |||||
| // } | |||||
| open fun getProjectDetails(projectId: Long): EditProjectDetails? { | open fun getProjectDetails(projectId: Long): EditProjectDetails? { | ||||
| val project = projectRepository.findById(projectId) | val project = projectRepository.findById(projectId) | ||||
| @@ -3,9 +3,12 @@ package com.ffii.tsms.modules.timesheet.service | |||||
| import com.ffii.core.exception.BadRequestException | import com.ffii.core.exception.BadRequestException | ||||
| import com.ffii.core.support.AbstractBaseEntityService | import com.ffii.core.support.AbstractBaseEntityService | ||||
| import com.ffii.core.support.JdbcDao | import com.ffii.core.support.JdbcDao | ||||
| import com.ffii.core.utils.CheckingUtils | |||||
| import com.ffii.tsms.modules.data.entity.Staff | import com.ffii.tsms.modules.data.entity.Staff | ||||
| import com.ffii.tsms.modules.data.entity.StaffRepository | import com.ffii.tsms.modules.data.entity.StaffRepository | ||||
| import com.ffii.tsms.modules.data.service.SalaryEffectiveService | |||||
| import com.ffii.tsms.modules.data.service.StaffsService | import com.ffii.tsms.modules.data.service.StaffsService | ||||
| import com.ffii.tsms.modules.data.service.TeamLogService | |||||
| import com.ffii.tsms.modules.data.service.TeamService | import com.ffii.tsms.modules.data.service.TeamService | ||||
| import com.ffii.tsms.modules.project.entity.* | import com.ffii.tsms.modules.project.entity.* | ||||
| import com.ffii.tsms.modules.timesheet.entity.Leave | import com.ffii.tsms.modules.timesheet.entity.Leave | ||||
| @@ -23,6 +26,7 @@ import java.time.LocalDate | |||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| import kotlin.jvm.optionals.getOrDefault | import kotlin.jvm.optionals.getOrDefault | ||||
| import kotlin.jvm.optionals.getOrNull | import kotlin.jvm.optionals.getOrNull | ||||
| import kotlin.time.times | |||||
| @Service | @Service | ||||
| open class TimesheetsService( | open class TimesheetsService( | ||||
| @@ -32,6 +36,8 @@ open class TimesheetsService( | |||||
| private val taskRepository: TaskRepository, | private val taskRepository: TaskRepository, | ||||
| private val staffsService: StaffsService, | private val staffsService: StaffsService, | ||||
| private val teamService: TeamService, | private val teamService: TeamService, | ||||
| private val teamLogService: TeamLogService, | |||||
| private val salaryEffectiveService: SalaryEffectiveService, | |||||
| private val staffRepository: StaffRepository, private val leaveRepository: LeaveRepository, | private val staffRepository: StaffRepository, private val leaveRepository: LeaveRepository, | ||||
| private val jdbcDao: JdbcDao, | private val jdbcDao: JdbcDao, | ||||
| ) : AbstractBaseEntityService<Timesheet, Long, TimesheetRepository>(jdbcDao, timesheetRepository) { | ) : AbstractBaseEntityService<Timesheet, Long, TimesheetRepository>(jdbcDao, timesheetRepository) { | ||||
| @@ -322,6 +328,43 @@ open class TimesheetsService( | |||||
| return if (sheet.lastRowNum > 0) "Import Excel success btw staffId:" + notExistStaffIdList.distinct().joinToString(", ") else "Import Excel failure" | return if (sheet.lastRowNum > 0) "Import Excel success btw staffId:" + notExistStaffIdList.distinct().joinToString(", ") else "Import Excel failure" | ||||
| } | } | ||||
| open fun getManpowerExpenseByProjectId( | |||||
| projectId: Long, | |||||
| startDate: LocalDate?, | |||||
| endDate: LocalDate?, | |||||
| ): Double { | |||||
| val timesheet = timesheetRepository.findAll() | |||||
| var manpowerExpense = 0.0 | |||||
| for (curr in timesheet) { | |||||
| val project = curr.project | |||||
| val otMultiplier = 1.0 | |||||
| val crossTeamMultipier = 1.0 | |||||
| val recordDate = curr.recordDate!! | |||||
| if (curr.deleted == false | |||||
| && project != null | |||||
| && project.id == projectId | |||||
| && CheckingUtils.checkTimePeriod(recordDate, startDate, endDate)) { | |||||
| val staffId = curr.staff!!.id!! | |||||
| val staffTeam = teamLogService.getStaffTeamLog(staffId, recordDate) | |||||
| if (staffTeam == null) { | |||||
| println(staffId) | |||||
| } | |||||
| val thisSE = salaryEffectiveService.getStaffSalaryEffective(staffId, recordDate) | |||||
| val hourlyRate = thisSE!!.salary.hourlyRate | |||||
| // will the staff be no team during that period of time????? | |||||
| val normalHour = curr.normalConsumed ?: 0.0 | |||||
| val otHour = curr.otConsumed ?: 0.0 | |||||
| val normalCost = normalHour.times(hourlyRate.toDouble()) | |||||
| val otCost = otHour.times(hourlyRate.toDouble()).times(otMultiplier) | |||||
| if (project.teamLead?.id != staffTeam!!.team.id) { // cross team | |||||
| manpowerExpense += (normalCost + otCost).times(crossTeamMultipier) | |||||
| } else { | |||||
| manpowerExpense += (normalCost + otCost) | |||||
| } | |||||
| } | |||||
| } | |||||
| return manpowerExpense | |||||
| } | |||||
| @Transactional(rollbackFor = [Exception::class]) | @Transactional(rollbackFor = [Exception::class]) | ||||
| open fun rearrangeTimesheets(): String { | open fun rearrangeTimesheets(): String { | ||||