| @@ -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.JdbcDao | |||
| import com.ffii.core.utils.CheckingUtils | |||
| import com.ffii.tsms.modules.data.entity.Customer | |||
| import com.ffii.tsms.modules.data.entity.CustomerType | |||
| import com.ffii.tsms.modules.data.entity.CustomerRepository | |||
| 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.FinancialSummaryByProject | |||
| 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.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.util.CellRangeAddress | |||
| import org.apache.poi.ss.util.CellUtil | |||
| import org.apache.poi.util.ArrayUtil | |||
| import org.apache.poi.xssf.usermodel.XSSFWorkbook | |||
| import org.springframework.beans.BeanUtils | |||
| import org.springframework.core.io.ClassPathResource | |||
| @@ -33,7 +43,17 @@ open class DashboardService( | |||
| private val customerTypeRepository: CustomerTypeRepository, | |||
| private val customerSubsidiaryService: CustomerSubsidiaryService, | |||
| 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 teamLogService: TeamLogService, | |||
| private val salaryEffectiveService: SalaryEffectiveService, | |||
| private val projectExpenseRepository: ProjectExpenseRepository, | |||
| private val teamRepository: TeamRepository, | |||
| private val jdbcDao: JdbcDao | |||
| ) { | |||
| private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") | |||
| @@ -3204,6 +3224,256 @@ open class DashboardService( | |||
| 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) | |||
| private fun createFinancialSummaryByProjectExcel( | |||
| 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 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.web.models.TeamHistory | |||
| import org.springframework.stereotype.Service | |||
| import java.time.LocalDate | |||
| @Service | |||
| open class TeamLogService ( | |||
| @@ -19,6 +20,16 @@ open class TeamLogService ( | |||
| 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>) { | |||
| if (delTeamHistory.isNotEmpty()) { | |||
| delTeamHistory.forEach { | |||
| @@ -15,13 +15,18 @@ import org.springframework.web.bind.annotation.DeleteMapping | |||
| import org.springframework.web.bind.annotation.ResponseStatus | |||
| import com.ffii.core.response.RecordsRes | |||
| 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.web.models.ExportFinancialSummaryByClientExcelRequest | |||
| 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.Resource | |||
| import org.springframework.http.ResponseEntity | |||
| import org.springframework.web.bind.annotation.RequestParam | |||
| import java.time.LocalDate | |||
| import java.time.LocalDateTime | |||
| @RestController | |||
| @RequestMapping("/dashboard") | |||
| @@ -30,6 +35,7 @@ class DashboardController( | |||
| private val customerSubsidiaryService: CustomerSubsidiaryService, | |||
| private val customerContactService: CustomerContactService, | |||
| private val dashboardService: DashboardService, | |||
| private val teamRepository: TeamRepository, | |||
| private val staffsService: StaffsService, | |||
| ) { | |||
| @GetMapping("/searchCustomerSubsidiary") | |||
| @@ -465,6 +471,37 @@ class DashboardController( | |||
| val args = mutableMapOf<String, Any>() | |||
| 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") | |||
| fun exportFinancialSummaryByClientExcel(@Valid @RequestBody request: ExportFinancialSummaryByClientExcelRequest): ResponseEntity<Resource> { | |||
| @@ -15,6 +15,7 @@ interface InvoiceRepository : AbstractRepository<Invoice, Long> { | |||
| fun findInvoiceInfoByPaidAmountIsNotNull(): List<InvoiceInfo> | |||
| fun findByInvoiceNo(invoiceNo: String): Invoice | |||
| fun findAllByProjectCodeIn(projectCode: List<String>): List<Invoice> | |||
| fun findAllByProjectCodeAndPaidAmountIsNotNullAndDeletedFalse(projectCode: String): List<Invoice> | |||
| } | |||
| @@ -7,4 +7,6 @@ interface ProjectExpenseRepository : AbstractRepository<ProjectExpense, Long> { | |||
| fun findExpenseSearchInfoByDeletedFalseOrderByProjectCode(): List<ProjectExpenseSearchInfo> | |||
| 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.utils.ExcelUtils | |||
| 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.projections.InvoiceData | |||
| import com.ffii.tsms.modules.project.entity.projections.InvoiceInfo | |||
| import com.ffii.tsms.modules.project.entity.projections.InvoicePDFReq | |||
| @@ -33,12 +35,32 @@ import kotlin.math.min | |||
| @Service | |||
| open class InvoiceService( | |||
| private val invoiceRepository: InvoiceRepository, | |||
| private val projectRepository: ProjectRepository, | |||
| private val milestoneRepository: MilestoneRepository, | |||
| private val milestonePaymentRepository: MilestonePaymentRepository, | |||
| private val projectService: ProjectsService, | |||
| private val jdbcDao: JdbcDao, | |||
| ) : 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> { | |||
| val milestones = milestoneRepository.findAllByProject(project) | |||
| 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.JdbcDao | |||
| import com.ffii.core.utils.CheckingUtils | |||
| import com.ffii.tsms.modules.project.entity.ProjectExpense | |||
| import com.ffii.tsms.modules.project.entity.ProjectExpenseRepository | |||
| import com.ffii.tsms.modules.project.entity.ProjectRepository | |||
| 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.ProjectExpenseRequest | |||
| import org.springframework.cglib.core.Local | |||
| import org.springframework.stereotype.Service | |||
| import java.time.LocalDate | |||
| import java.time.format.DateTimeFormatter | |||
| @@ -22,6 +24,21 @@ open class ProjectExpenseService( | |||
| 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 { | |||
| try { | |||
| for (projectExpense in projectExpenseList) { | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.tsms.modules.project.service | |||
| import com.ffii.core.utils.CheckingUtils | |||
| import com.ffii.core.utils.ExcelUtils | |||
| import com.ffii.tsms.modules.common.SecurityUtils | |||
| 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.project.entity.* | |||
| 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.timesheet.entity.TimesheetRepository | |||
| import com.ffii.tsms.modules.timesheet.service.TimesheetsService | |||
| import org.apache.commons.logging.LogFactory | |||
| import org.apache.poi.ss.usermodel.CellType | |||
| import org.apache.poi.ss.usermodel.DataFormatter | |||
| @@ -31,6 +31,9 @@ import kotlin.jvm.optionals.getOrNull | |||
| @Service | |||
| open class ProjectsService( | |||
| private val projectRepository: ProjectRepository, | |||
| // private val invoiceService: InvoiceService, | |||
| private val invoiceRepository: InvoiceRepository, | |||
| private val projectExpenseService: ProjectExpenseService, | |||
| private val customerService: CustomerService, | |||
| private val tasksService: TasksService, | |||
| private val customerContactService: CustomerContactService, | |||
| @@ -51,6 +54,7 @@ open class ProjectsService( | |||
| private val milestonePaymentRepository: MilestonePaymentRepository, | |||
| private val taskGroupRepository: TaskGroupRepository, | |||
| private val timesheetRepository: TimesheetRepository, | |||
| private val timesheetsService: TimesheetsService, | |||
| private val taskTemplateRepository: TaskTemplateRepository, | |||
| private val subsidiaryContactService: SubsidiaryContactService, | |||
| 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? { | |||
| 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.support.AbstractBaseEntityService | |||
| 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.StaffRepository | |||
| import com.ffii.tsms.modules.data.service.SalaryEffectiveService | |||
| 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.project.entity.* | |||
| import com.ffii.tsms.modules.timesheet.entity.Leave | |||
| @@ -23,6 +26,7 @@ import java.time.LocalDate | |||
| import java.time.format.DateTimeFormatter | |||
| import kotlin.jvm.optionals.getOrDefault | |||
| import kotlin.jvm.optionals.getOrNull | |||
| import kotlin.time.times | |||
| @Service | |||
| open class TimesheetsService( | |||
| @@ -32,6 +36,8 @@ open class TimesheetsService( | |||
| private val taskRepository: TaskRepository, | |||
| private val staffsService: StaffsService, | |||
| private val teamService: TeamService, | |||
| private val teamLogService: TeamLogService, | |||
| private val salaryEffectiveService: SalaryEffectiveService, | |||
| private val staffRepository: StaffRepository, private val leaveRepository: LeaveRepository, | |||
| private val jdbcDao: JdbcDao, | |||
| ) : 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" | |||
| } | |||
| 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]) | |||
| open fun rearrangeTimesheets(): String { | |||