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