MSI\2Fi 11 miesięcy temu
rodzic
commit
2da20b697f
6 zmienionych plików z 185 dodań i 43 usunięć
  1. +113
    -37
      src/main/java/com/ffii/tsms/modules/common/mail/service/MailReminderService.kt
  2. +9
    -0
      src/main/java/com/ffii/tsms/modules/common/mail/web/models/WorkHourRecords.kt
  3. +13
    -4
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  4. +2
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/entity/LeaveRepository.kt
  5. +48
    -2
      src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt
  6. BIN
      src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx

+ 113
- 37
src/main/java/com/ffii/tsms/modules/common/mail/service/MailReminderService.kt Wyświetl plik

@@ -3,6 +3,7 @@ package com.ffii.tsms.modules.common.mail.service
import com.ffii.tsms.modules.common.SettingNames
import com.ffii.tsms.modules.common.holiday.service.HolidayService
import com.ffii.tsms.modules.common.mail.pojo.MailRequest
import com.ffii.tsms.modules.common.mail.web.models.WorkHourRecords
import com.ffii.tsms.modules.data.entity.Staff
import com.ffii.tsms.modules.data.entity.StaffRepository
import com.ffii.tsms.modules.data.entity.TeamRepository
@@ -10,6 +11,7 @@ import com.ffii.tsms.modules.data.service.CompanyHolidayService
import com.ffii.tsms.modules.data.service.StaffsService
import com.ffii.tsms.modules.settings.service.SettingsService
import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository
import com.ffii.tsms.modules.timesheet.service.TimesheetsService
import com.ffii.tsms.modules.user.service.UserService
import jakarta.mail.internet.InternetAddress
import org.apache.commons.logging.Log
@@ -26,12 +28,14 @@ data class TableRow(
val name: String,
val missingDates: MutableList<LocalDate>,
)

@Service
open class MailReminderService(
val mailService: MailService,
val userService: UserService,
val settingsService: SettingsService,
val holidayService: HolidayService,
val timesheetsService: TimesheetsService,
val timesheetRepository: TimesheetRepository,
val staffsService: StaffsService,
val staffRepository: StaffRepository,
@@ -117,8 +121,19 @@ open class MailReminderService(
val filteredLastMonthDays = lastMonthDays.filter {
it !in allHolidaysList && it.dayOfWeek != DayOfWeek.SATURDAY && it.dayOfWeek != DayOfWeek.SUNDAY
}

val timesheet = timesheetRepository.findByDeletedFalseAndRecordDateBetweenOrderByRecordDate(filteredLastMonthDays.first(),filteredLastMonthDays.last())
val args = mutableMapOf(
"from" to filteredLastMonthDays.first(),
"to" to filteredLastMonthDays.last(),
)
val ts = timesheetsService.workHourRecordsWithinRange(args)
val timesheet: List<WorkHourRecords> = ts.map {
WorkHourRecords(
staffId = it["staffId"].toString().toLong(),
recordDate = LocalDate.parse(it["recordDate"].toString()),
hours = it["hours"].toString().toDouble()
)
}
// val timesheet = timesheetRepository.findByDeletedFalseAndRecordDateBetweenOrderByRecordDate(filteredLastMonthDays.first(),filteredLastMonthDays.last())
val staffs = staffRepository.findAllByEmployTypeAndDeletedFalseAndDepartDateIsNull(FULLTIME)
val teams = teamRepository.findAll().filter { team -> team.deleted == false }

@@ -128,17 +143,36 @@ open class MailReminderService(
val teamMembers: List<Staff> = staffs.filter { it.team != null && it.team.id == team.id }
if (teamMembers.isEmpty()) continue
val teamMembersIds: List<Long?> = teamMembers.map { it.id }.sorted()
val filteredTimesheet = timesheet.filter { teamMembersIds.contains(it.staff?.id) }
val timesheetByIdAndRecord = filteredTimesheet.groupBy { it.staff?.id to it.recordDate }
.map { (key, _) ->
val (staffId, recordDate) = key
recordDate to mutableListOf<Long>(staffId ?: 0)
// val filteredTimesheet = timesheet.filter { teamMembersIds.contains(it.staff?.id) }
val filteredTimesheet = timesheet.filter { teamMembersIds.contains(it.staffId) }
// val timesheetByIdAndRecord = filteredTimesheet.groupBy { it.staff?.id to it.recordDate }
// .map { (key, _) ->
// val (staffId, recordDate) = key
// recordDate to mutableListOf<Long>(staffId ?: 0)
// }
println(filteredTimesheet.filter { it.staffId == 4.toLong() })
val timesheetByIdAndRecord = filteredTimesheet.groupBy { it.staffId to it.recordDate }
.mapNotNull { (key, records) ->
Triple(
key.second,
key.first,
records.sumOf { it.hours }
)
}
val goodStaffsList = filteredLastMonthDays.map { day ->
timesheetByIdAndRecord.find {
it.first == day
} ?: Pair(day, mutableListOf())
}.sortedBy { it.first }
// val goodStaffsList = filteredLastMonthDays.map { day ->
// timesheetByIdAndRecord.find {
// it.first == day
// } ?: Pair(day, mutableListOf())
// }.sortedBy { it.first }

val goodStaffsList = filteredLastMonthDays.map { date ->
val matchedStaffIds = timesheetByIdAndRecord
.filter { it.first == date && it.third >= 8 }
.map { it.second } // Extracting the second element (staffId)
// Returning a Pair of the date and the list of matched staff IDs
Pair(date, matchedStaffIds)
}.sortedBy { it.first } // Sort by date
// return
// creating the email
val intro = StringBuilder("${teamLead.name}, Staffs Missing Timesheet in the Table Below: \n")
val tableData = mutableListOf<TableRow>()
@@ -148,7 +182,7 @@ open class MailReminderService(
goodStaffsList.forEach { (key, value) ->
if (!value.contains(it.id!!)) {
isNaughty = true
missingDates.add(key!!)
missingDates.add(key)
}
}
if (!isNaughty) return@forEach
@@ -176,7 +210,19 @@ open class MailReminderService(
val allHolidaysList: List<LocalDate> = (holidayList + companyHolidayList).toSet().toList()

//get data
val timesheet = timesheetRepository.findByDeletedFalseAndRecordDateBetweenOrderByRecordDate(firstDay, today)
val args = mutableMapOf(
"from" to firstDay,
"to" to today,
)
val ts = timesheetsService.workHourRecordsWithinRange(args)
val timesheet: List<WorkHourRecords> = ts.map {
WorkHourRecords(
staffId = it["staffId"].toString().toLong(),
recordDate = LocalDate.parse(it["recordDate"].toString()),
hours = it["hours"].toString().toDouble()
)
}
// val timesheet = timesheetRepository.findByDeletedFalseAndRecordDateBetweenOrderByRecordDate(firstDay, today)
val staffs = staffRepository.findAllByEmployTypeAndDeletedFalseAndDepartDateIsNull(FULLTIME)
val teams = teamRepository.findAll().filter { team -> team.deleted == false }

@@ -195,18 +241,24 @@ open class MailReminderService(
if (teamMembers.isEmpty()) continue
val teamMembersIds: List<Long?> = teamMembers.map { it.id }.sorted()
// getting the naughty list
val filteredTimesheet = timesheet.filter { teamMembersIds.contains(it.staff?.id) } // filter team members' timesheet
val timesheetByIdAndRecord = filteredTimesheet.groupBy { it.staff?.id to it.recordDate }
.map { (key, _) ->
val (staffId, recordDate) = key
recordDate to mutableListOf<Long>(staffId ?: 0)
val filteredTimesheet = timesheet.filter { teamMembersIds.contains(it.staffId) } // filter team members' timesheet
val timesheetByIdAndRecord = filteredTimesheet.groupBy { it.staffId to it.recordDate }
.mapNotNull { (key, records) ->
Triple(
key.second,
key.first,
records.sumOf { it.hours }
)
}
// change the date list with desired time range
val goodStaffsList = filteredDatesList.map { day ->
timesheetByIdAndRecord.find {
it.first == day
} ?: Pair(day, mutableListOf())
val goodStaffsList = filteredDatesList.map { date ->
val matchedStaffIds = timesheetByIdAndRecord
.filter { it.first == date && it.third >= 8 }
.map { it.second } // Extracting the second element (staffId)
// Returning a Pair of the date and the list of matched staff IDs
Pair(date, matchedStaffIds)
}.sortedBy { it.first }

// creating the email content
val intro = StringBuilder("${teamLead.name}, Staffs Missing Timesheet in the Table Below: ($firstDay-$today) \n")
val tableData = mutableListOf<TableRow>()
@@ -216,7 +268,7 @@ open class MailReminderService(
goodStaffsList.forEach { (key, value) ->
if (!value.contains(it.id!!)) {
isNaughty = true
missingDates.add(key!!)
missingDates.add(key)
}
}
if (!isNaughty) return@forEach
@@ -267,22 +319,46 @@ open class MailReminderService(
daysBefore = daysBefore.minusDays(1)
}

val timesheet = timesheetRepository.findByDeletedFalseAndRecordDateBetweenOrderByRecordDate(sevenDaysBefore, fourDaysBefore)
val args = mutableMapOf(
"from" to sevenDaysBefore,
"to" to fourDaysBefore,
)

val ts = timesheetsService.workHourRecordsWithinRange(args)
val timesheet: List<WorkHourRecords> = ts.map {
WorkHourRecords(
staffId = it["staffId"].toString().toLong(),
recordDate = LocalDate.parse(it["recordDate"].toString()),
hours = it["hours"].toString().toDouble()
)
}
// val timesheet = timesheetRepository.findByDeletedFalseAndRecordDateBetweenOrderByRecordDate(sevenDaysBefore, fourDaysBefore)
val staffs = staffRepository.findAllByEmployTypeAndDeletedFalseAndDepartDateIsNull(FULLTIME) // FT? FT? etc
val staffIds: List<Long> = staffs.map { it.id as Long }

val timesheetByIdAndRecord = timesheet.groupBy { it.staff?.id to it.recordDate }
.map { (key, _) ->
val (staffId, recordDate) = key
"$recordDate" to mutableListOf<Long>(staffId ?: 0)
}
val goodStaffsList = workingDaysList.map { it ->
val key = it.toString()
timesheetByIdAndRecord.find {
it.first == key
}?: Pair(key, mutableListOf())
}.sortedBy { it.first }

// val timesheetByIdAndRecord = timesheet.groupBy { it.staff?.id to it.recordDate }
// .map { (key, _) ->
// val (staffId, recordDate) = key
// "$recordDate" to mutableListOf<Long>(staffId ?: 0)
// }
val timesheetByIdAndRecord = timesheet.groupBy {
it.staffId to it.recordDate
}.mapNotNull { (key, records) ->
Triple(
key.second,
key.first,
records.sumOf { it.hours }
)
}
val goodStaffsList = workingDaysList.map { date ->
val matchedStaffIds = timesheetByIdAndRecord
.filter { it.first == date && it.third >= 8 }
.map { it.second } // Extracting the second element (staffId)
// Returning a Pair of the date and the list of matched staff IDs
Pair(date, matchedStaffIds)
}.sortedBy { it.first } // Sort by date
println("goodStaffsList")
println(goodStaffsList)
// change this list with the staffs that need checking
staffIds.forEach { id ->
var isNaughty: Boolean = false


+ 9
- 0
src/main/java/com/ffii/tsms/modules/common/mail/web/models/WorkHourRecords.kt Wyświetl plik

@@ -0,0 +1,9 @@
package com.ffii.tsms.modules.common.mail.web.models

import java.time.LocalDate

data class WorkHourRecords(
val staffId: Long,
val recordDate: LocalDate,
val hours: Double,
)

+ 13
- 4
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt Wyświetl plik

@@ -2167,10 +2167,18 @@ open class ReportService(
// }

open fun getProjectResourceOverconsumptionReport(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder(
" SELECT "
val sql = StringBuilder("with pe_cte as ("
+ " select"
+ " pe.projectId,"
+ " sum(coalesce(amount)) as expense"
+ " from project_expense pe"
+ " where deleted = false"
+ " group by pe.projectId"
+ " )"
+ " SELECT "
+ " p.code, p.name, tm.code as team, concat(c.code, ' -',c.name) as client, COALESCE(concat(ss.code, ' -', ss.name), 'N/A') as subsidiary "
+ " , (p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8 as plannedBudget, sum(t.consumedBudget) as actualConsumedBudget "
+ " , (p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8 as plannedBudget, sum(t.consumedBudget) + coalesce(pc.expense, 0) as actualConsumedBudget "
+ " , coalesce(pc.expense, 0) as projectExpense "
+ " , COALESCE(p.totalManhour, 0) as plannedManhour, sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as actualConsumedManhour "
+ " , sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) as budgetConsumptionRate "
+ " , sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0) as manhourConsumptionRate "
@@ -2189,6 +2197,7 @@ open class ReportService(
+ " LEFT JOIN salary sal on se.salaryId = sal.salaryPoint "
+ " ) t "
+ " left join project p on p.id = t.projectId "
+ " left join pe_cte pc on pc.projectId = t.projectId "
+ " left join team tm on p.teamLead = tm.teamLead "
+ " left join customer c on c.id = p.customerId "
+ " left join subsidiary ss on p.customerSubsidiaryId = ss.id "
@@ -2217,7 +2226,7 @@ open class ReportService(
else -> ""
}
}
sql.append(" group by p.code, p.name, tm.code, c.code, c.name, ss.code, ss.name, p.expectedTotalFee, p.subContractFee, p.totalManhour ")
sql.append(" group by p.code, p.name, tm.code, c.code, c.name, ss.code, ss.name, p.expectedTotalFee, p.subContractFee, p.totalManhour, pc.expense ")
sql.append(statusFilter)
return jdbcDao.queryForList(sql.toString(), args)
}


+ 2
- 0
src/main/java/com/ffii/tsms/modules/timesheet/entity/LeaveRepository.kt Wyświetl plik

@@ -10,4 +10,6 @@ interface LeaveRepository : AbstractRepository<Leave, Long> {
fun deleteAllByStaffAndRecordDate(staff: Staff, recordDate: LocalDate)

fun findByStaffAndRecordDateBetweenOrderByRecordDate(staff: Staff, start: LocalDate, end: LocalDate): List<Leave>
fun findByDeletedFalseAndRecordDateBetweenOrderByRecordDate(start: LocalDate, end: LocalDate): List<Leave>

}

+ 48
- 2
src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt Wyświetl plik

@@ -1,6 +1,9 @@
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.tsms.modules.data.entity.Staff
import com.ffii.tsms.modules.data.entity.StaffRepository
import com.ffii.tsms.modules.data.service.StaffsService
import com.ffii.tsms.modules.data.service.TeamService
@@ -28,8 +31,10 @@ open class TimesheetsService(
private val projectRepository: ProjectRepository,
private val taskRepository: TaskRepository,
private val staffsService: StaffsService,
private val teamService: TeamService, private val staffRepository: StaffRepository, private val leaveRepository: LeaveRepository
) {
private val teamService: TeamService,
private val staffRepository: StaffRepository, private val leaveRepository: LeaveRepository,
private val jdbcDao: JdbcDao,
) : AbstractBaseEntityService<Timesheet, Long, TimesheetRepository>(jdbcDao, timesheetRepository) {
@Transactional
open fun saveTimesheet(recordTimeEntry: Map<LocalDate, List<TimeEntry>>): Map<String, List<TimeEntry>> {
// Need to be associated with a staff
@@ -378,4 +383,45 @@ open class TimesheetsService(

return "Rearrange success"
}
open fun workHourRecordsWithinRange(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder("WITH ts_cte AS ("
+ " SELECT "
+ " t.recordDate, "
+ " t.staffId, "
+ " SUM(coalesce(t.normalConsumed, 0)) + SUM(COALESCE(t.otConsumed, 0)) AS hours "
+ " , 'normal' as tp"
+ " FROM timesheet t "
+ " where t.deleted = false "
+ " GROUP BY t.staffId, t.recordDate "
+ " ), "
+ " l_cte AS ( "
+ " SELECT "
+ " l.recordDate, "
+ " l.staffId, "
+ " SUM(COALESCE(l.leaveHours, 0)) AS hours "
+ " ,'ot' as tp "
+ " FROM `leave` l "
+ " where l.deleted = false "
+ " GROUP BY l.staffId, l.recordDate "
+ " ) "
+ " select "
+ " recordDate, "
+ " staffId, "
+ " hours "
+ " ,tp "
+ " from ( "
+ " SELECT "
+ " * "
+ " FROM ts_cte tc "
+ " union "
+ " SELECT "
+ " * "
+ " FROM l_cte lc "
+ " ) ut "
)
if (args.containsKey("from") && args.containsKey("to"))
sql.append(" where recordDate BETWEEN :from AND :to ");
return jdbcDao.queryForList(sql.toString(), args)
}

}

BIN
src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx Wyświetl plik


Ładowanie…
Anuluj
Zapisz