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