diff --git a/src/main/java/com/ffii/core/utils/CheckingUtils.kt b/src/main/java/com/ffii/core/utils/CheckingUtils.kt new file mode 100644 index 0000000..6cbdd68 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/CheckingUtils.kt @@ -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) + } + } + } +} \ No newline at end of file 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 904c7fa..6621d6a 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 @@ -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> { + val output = mutableListOf>() + 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( + "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 { + 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() + val projectIds = mutableListOf() + 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, diff --git a/src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt b/src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt index b484692..32500c8 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt @@ -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) diff --git a/src/main/java/com/ffii/tsms/modules/data/service/TeamLogService.kt b/src/main/java/com/ffii/tsms/modules/data/service/TeamLogService.kt index 3f551ca..ee7bf4b 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/TeamLogService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/TeamLogService.kt @@ -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, delTeamHistory: List) { if (delTeamHistory.isNotEmpty()) { delTeamHistory.forEach { 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 83cffac..9a47963 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 @@ -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() return dashboardService.staffCombo(args) } + @GetMapping("/getFinancialSummaryByProject") + fun getFinancialSummaryByProject(@Valid request: HttpServletRequest): List> { + 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> { + val output = mutableListOf>() + 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 = 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 { diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/InvoiceRepository.kt b/src/main/java/com/ffii/tsms/modules/project/entity/InvoiceRepository.kt index d42227f..35c1ef1 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/InvoiceRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/InvoiceRepository.kt @@ -15,6 +15,7 @@ interface InvoiceRepository : AbstractRepository { fun findInvoiceInfoByPaidAmountIsNotNull(): List fun findByInvoiceNo(invoiceNo: String): Invoice + fun findAllByProjectCodeIn(projectCode: List): List fun findAllByProjectCodeAndPaidAmountIsNotNullAndDeletedFalse(projectCode: String): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectExpenseRepository.kt b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectExpenseRepository.kt index 6e03002..f00ba33 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectExpenseRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectExpenseRepository.kt @@ -7,4 +7,6 @@ interface ProjectExpenseRepository : AbstractRepository { fun findExpenseSearchInfoByDeletedFalseOrderByProjectCode(): List fun findAllByProjectIdAndDeletedFalse(projectId: Long): List + + fun findAllByProjectIdInAndDeletedFalse(projectIds: List): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/projections/DashboardData.kt b/src/main/java/com/ffii/tsms/modules/project/entity/projections/DashboardData.kt new file mode 100644 index 0000000..218dfac --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/entity/projections/DashboardData.kt @@ -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, +) diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoiceData.kt b/src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoiceData.kt new file mode 100644 index 0000000..29055fa --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoiceData.kt @@ -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, +) diff --git a/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt b/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt index 6aa7163..3b1f974 100644 --- a/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt +++ b/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt @@ -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(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 { val milestones = milestoneRepository.findAllByProject(project) val milestonePayments = milestonePaymentRepository.findAllByMilestoneIn(milestones) diff --git a/src/main/java/com/ffii/tsms/modules/project/service/ProjectExpenseService.kt b/src/main/java/com/ffii/tsms/modules/project/service/ProjectExpenseService.kt index 149f054..5c5a029 100644 --- a/src/main/java/com/ffii/tsms/modules/project/service/ProjectExpenseService.kt +++ b/src/main/java/com/ffii/tsms/modules/project/service/ProjectExpenseService.kt @@ -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): Boolean { try { for (projectExpense in projectExpenseList) { diff --git a/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt b/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt index 530da7a..f5f6ac7 100644 --- a/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt +++ b/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt @@ -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) 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 012acfc..bc17333 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 @@ -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(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 {