| @@ -4,6 +4,7 @@ import com.ffii.tsms.modules.common.SettingNames | |||||
| import com.ffii.tsms.modules.common.holiday.service.HolidayService | 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.pojo.MailRequest | ||||
| import com.ffii.tsms.modules.common.mail.web.models.StaffRecords | import com.ffii.tsms.modules.common.mail.web.models.StaffRecords | ||||
| import com.ffii.tsms.modules.common.mail.web.models.StaffWithMissingDates | |||||
| import com.ffii.tsms.modules.common.mail.web.models.WorkHourRecords | import com.ffii.tsms.modules.common.mail.web.models.WorkHourRecords | ||||
| import com.ffii.tsms.modules.common.mail.web.models.WorkHourRecordsWithJoinDate | import com.ffii.tsms.modules.common.mail.web.models.WorkHourRecordsWithJoinDate | ||||
| import com.ffii.tsms.modules.data.entity.Staff | import com.ffii.tsms.modules.data.entity.Staff | ||||
| @@ -18,6 +19,7 @@ import com.ffii.tsms.modules.user.service.UserService | |||||
| import jakarta.mail.internet.InternetAddress | import jakarta.mail.internet.InternetAddress | ||||
| import org.apache.commons.logging.Log | import org.apache.commons.logging.Log | ||||
| import org.apache.commons.logging.LogFactory | import org.apache.commons.logging.LogFactory | ||||
| import org.springframework.cglib.core.Local | |||||
| import org.springframework.scheduling.annotation.Scheduled | import org.springframework.scheduling.annotation.Scheduled | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import java.time.DayOfWeek | import java.time.DayOfWeek | ||||
| @@ -58,7 +60,7 @@ open class MailReminderService( | |||||
| return false | return false | ||||
| } | } | ||||
| } | } | ||||
| private fun createHTMLTable(naughtyList: MutableList<TableRow>): String { | |||||
| private fun createHTMLTable(naughtyList: MutableList<TableRow>, dateForRed: LocalDate? = LocalDate.of(2024,4,1)): String { | |||||
| val tableStarter = StringBuilder(" <table width='100%' border='1' align='center'> ") | val tableStarter = StringBuilder(" <table width='100%' border='1' align='center'> ") | ||||
| val header = StringBuilder( | val header = StringBuilder( | ||||
| " <tr> " | " <tr> " | ||||
| @@ -69,11 +71,21 @@ open class MailReminderService( | |||||
| ) | ) | ||||
| tableStarter.append(header) | tableStarter.append(header) | ||||
| for (per in naughtyList) { | for (per in naughtyList) { | ||||
| val missDatesHtml = per.missingDates.joinToString(", "){ | |||||
| localDate -> | |||||
| val color = if(localDate.isBefore(LocalDate.now().minusMonths(1))){ | |||||
| "red" | |||||
| }else{ | |||||
| "black" | |||||
| } | |||||
| "<span style='color: $color;'>$localDate</span>" | |||||
| } | |||||
| tableStarter.append( | tableStarter.append( | ||||
| " <tr> " | " <tr> " | ||||
| + " <td>${per.staffId}</td>" | + " <td>${per.staffId}</td>" | ||||
| + " <td>${per.name}</td>" | + " <td>${per.name}</td>" | ||||
| + " <td>${per.missingDates.joinToString(", ")}</td>" | |||||
| + " <td>${missDatesHtml}</td>" | |||||
| + " </tr> " | + " </tr> " | ||||
| ) | ) | ||||
| } | } | ||||
| @@ -103,7 +115,7 @@ open class MailReminderService( | |||||
| mailService.send(mailRequestList) | mailService.send(mailRequestList) | ||||
| } | } | ||||
| @Scheduled(cron = "0 0 6 7 * ?") // (SS/MM/HH/DD/MM/YY) | |||||
| // @Scheduled(cron = "0 0 6 7 * ?") // (SS/MM/HH/DD/MM/YY) | |||||
| open fun sendTimesheetToTeamLead7TH() { | open fun sendTimesheetToTeamLead7TH() { | ||||
| logger.info("-----------------------") | logger.info("-----------------------") | ||||
| logger.info("Scheduled Start: 7th email reminder") | logger.info("Scheduled Start: 7th email reminder") | ||||
| @@ -153,6 +165,7 @@ open class MailReminderService( | |||||
| key.third // joinDate | key.third // joinDate | ||||
| ) | ) | ||||
| } | } | ||||
| // println("**** timesheetByIdAndRecord: ${timesheetByIdAndRecord.filter { it.staffId == 1L }}") | |||||
| val goodStaffsList = filteredLastMonthDays.map { date -> | val goodStaffsList = filteredLastMonthDays.map { date -> | ||||
| val matchedStaffIds = timesheetByIdAndRecord | val matchedStaffIds = timesheetByIdAndRecord | ||||
| .filter { | .filter { | ||||
| @@ -196,6 +209,107 @@ open class MailReminderService( | |||||
| } | } | ||||
| } | } | ||||
| // Function to generate a sequence of workdays, excluding weekends and public holidays | |||||
| private fun generateWorkdays(startDate: LocalDate, endDate: LocalDate, publicHolidays: Set<LocalDate>): Sequence<LocalDate> { | |||||
| return generateSequence(startDate) { date -> | |||||
| date.plusDays(1).takeIf { it <= endDate } | |||||
| }.filter { date -> | |||||
| date.dayOfWeek != DayOfWeek.SATURDAY && | |||||
| date.dayOfWeek != DayOfWeek.SUNDAY && | |||||
| !publicHolidays.contains(date) | |||||
| } | |||||
| } | |||||
| // Function to find missing workdays for staff | |||||
| private fun findStaffWithMissingDates(staffRecords: List<StaffRecords>, workdays: Sequence<LocalDate>, staffList: List<Staff>): List<StaffWithMissingDates> { | |||||
| val staffMap = staffList.associateBy { it.id } | |||||
| return staffRecords | |||||
| .groupBy { it.staffId } | |||||
| .map { (staffId, records) -> | |||||
| val joinDate = records.firstOrNull()?.joinDate ?: LocalDate.of(1970, 1, 1) | |||||
| val recordedDates = records.map { it.recordDate }.toSet() | |||||
| val staffWorkdays = workdays.filter { it >= joinDate }.toList() | |||||
| val missingDates = staffWorkdays.filterNot { recordedDates.contains(it) } | |||||
| val staffInfo = staffMap[staffId] | |||||
| StaffWithMissingDates( | |||||
| staffId = staffId, | |||||
| staffIdinCode = staffInfo?.staffId ?: "", | |||||
| staffName = staffInfo?.name ?: "", | |||||
| missingDates = missingDates | |||||
| ) | |||||
| } | |||||
| .filter { it.missingDates.isNotEmpty() } | |||||
| } | |||||
| @Scheduled(cron = "0 0 6 7 * ?") // (SS/MM/HH/DD/MM/YY) | |||||
| open fun sendMissingTimesheetDateToTeamLead7th(){ | |||||
| logger.info("-----------------------") | |||||
| logger.info("Scheduled Start: 7th email reminder V2") | |||||
| val firstDate = LocalDate.of(2024,10,1) | |||||
| val today = LocalDate.now() | |||||
| val publicHolidays = holidayService.commonHolidayList().map { it.date } | |||||
| val companyHolidays = companyHolidayService.allCompanyHolidays().map { it.date } | |||||
| val allHolidays: Set<LocalDate> = (publicHolidays + companyHolidays).toSet() | |||||
| val workdays = generateWorkdays(firstDate, today, allHolidays) | |||||
| println("workdays: ${workdays.joinToString { ", " }}") | |||||
| //Get all missing timesheet date and group by staff | |||||
| val args = mutableMapOf( | |||||
| "from" to firstDate, | |||||
| "to" to today, | |||||
| ) | |||||
| val ts = timesheetsService.workHourRecordsWithinRange(args) | |||||
| val timesheet: List<StaffRecords> = ts.map { | |||||
| WorkHourRecordsWithJoinDate( | |||||
| staffId = it["staffId"].toString().toLong(), | |||||
| recordDate = LocalDate.parse(it["recordDate"].toString()), | |||||
| hours = it["hours"].toString().toDouble(), | |||||
| joinDate = it["joinDate"]?.toString()?.let { date -> LocalDate.parse(date) } ?: LocalDate.of(1970, 1, 1) | |||||
| ) | |||||
| }.groupBy { Triple(it.staffId, it.recordDate, it.joinDate) } | |||||
| .mapNotNull { (key, records) -> | |||||
| StaffRecords( | |||||
| key.second, // recordDate | |||||
| key.first, // staffId | |||||
| records.sumOf { it.hours }, | |||||
| key.third // joinDate | |||||
| ) | |||||
| } | |||||
| val staffs = staffRepository.findAllByEmployTypeAndDeletedFalseAndDepartDateIsNull(FULLTIME).filter { it.staffId != "A003" && it.staffId != "A004" && it.staffId != "B011" }.filter{ it.team?.code != "HO"} | |||||
| val teams = teamRepository.findAll().filter { team -> team.deleted == false } | |||||
| for (team in teams) { | |||||
| val teamLead = team.staff | |||||
| 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.staffId) } | |||||
| val staffWithMissingDates = findStaffWithMissingDates(filteredTimesheet, workdays, teamMembers) | |||||
| // println("staffWithMissingDates----: $staffWithMissingDates") | |||||
| val intro = StringBuilder("${teamLead.name}, Staffs Missing Timesheet in the Table Below: \n") | |||||
| val tableData = mutableListOf<TableRow>() | |||||
| staffWithMissingDates.forEach{ | |||||
| val rowData = TableRow(it.staffIdinCode, it.staffName, (it.missingDates).toMutableList()) | |||||
| tableData.add(rowData) | |||||
| } | |||||
| val table = createHTMLTable(tableData) | |||||
| val emailContent = intro | |||||
| .append(table) | |||||
| val receiver = listOf(teamLead.email) | |||||
| createEmailRequest(emailContent.toString(), receiver) | |||||
| } | |||||
| } | |||||
| // Testing function to print the good staff list | // Testing function to print the good staff list | ||||
| open fun test7thStaffList(){ | open fun test7thStaffList(){ | ||||
| val today = LocalDate.now() | val today = LocalDate.now() | ||||
| @@ -259,7 +373,7 @@ open class MailReminderService( | |||||
| Pair(date, matchedStaffIds) | Pair(date, matchedStaffIds) | ||||
| }.sortedBy { it.first } | }.sortedBy { it.first } | ||||
| println(goodStaffsList) | |||||
| // println(goodStaffsList) | |||||
| } | } | ||||
| } | } | ||||
| @@ -37,7 +37,7 @@ class MailController( | |||||
| } | } | ||||
| @GetMapping("/test7th") | @GetMapping("/test7th") | ||||
| fun test7th() { | fun test7th() { | ||||
| mailReminderService.sendTimesheetToTeamLead7TH() | |||||
| mailReminderService.sendMissingTimesheetDateToTeamLead7th() | |||||
| } | } | ||||
| @GetMapping("/test15th") | @GetMapping("/test15th") | ||||
| fun test15th() { | fun test15th() { | ||||
| @@ -46,7 +46,7 @@ class MailController( | |||||
| @GetMapping("/test7th-staff-list") | @GetMapping("/test7th-staff-list") | ||||
| fun test7thStaffList(){ | fun test7thStaffList(){ | ||||
| mailReminderService.test7thStaffList() | |||||
| mailReminderService.sendMissingTimesheetDateToTeamLead7th() | |||||
| } | } | ||||
| @GetMapping("/test15th-staff-list") | @GetMapping("/test15th-staff-list") | ||||
| @@ -21,3 +21,10 @@ data class StaffRecords( | |||||
| val sumOfHours: Double, | val sumOfHours: Double, | ||||
| val joinDate: LocalDate? = LocalDate.of(1970,1,1) | val joinDate: LocalDate? = LocalDate.of(1970,1,1) | ||||
| ) | ) | ||||
| data class StaffWithMissingDates( | |||||
| val staffId: Long, | |||||
| val staffIdinCode: String, | |||||
| val staffName: String, | |||||
| val missingDates: List<LocalDate> | |||||
| ) | |||||
| @@ -103,4 +103,22 @@ open class CustomerService( | |||||
| return SaveCustomerResponse(customer = customer, message = "Success"); | return SaveCustomerResponse(customer = customer, message = "Success"); | ||||
| } | } | ||||
| //Auto generate the latest client code | |||||
| open fun findMaxCustomerCode(): String{ | |||||
| val sql = StringBuilder("select MAX(code) as maxCode from customer where code regexp 'CT-'") | |||||
| val maxCode = jdbcDao.queryForString(sql.toString()) | |||||
| val regex = "(\\D+)-(\\d+)".toRegex() | |||||
| val match = regex.matchEntire(maxCode) | |||||
| val next = if (match != null) { | |||||
| val (prefix, numberStr) = match.destructured | |||||
| val nextNumber = numberStr.toInt() + 1 | |||||
| val padded = nextNumber.toString().padStart(numberStr.length, '0') | |||||
| "$prefix-$padded" | |||||
| } else { | |||||
| maxCode | |||||
| } | |||||
| return next | |||||
| } | |||||
| } | } | ||||
| @@ -8,6 +8,7 @@ import com.ffii.tsms.modules.data.service.CustomerSubsidiaryService | |||||
| import com.ffii.tsms.modules.data.web.models.CustomerResponse | import com.ffii.tsms.modules.data.web.models.CustomerResponse | ||||
| import com.ffii.tsms.modules.data.web.models.SaveCustomerResponse | import com.ffii.tsms.modules.data.web.models.SaveCustomerResponse | ||||
| import com.ffii.tsms.modules.project.web.models.SaveCustomerRequest | import com.ffii.tsms.modules.project.web.models.SaveCustomerRequest | ||||
| import jakarta.servlet.http.HttpServletResponse | |||||
| import org.springframework.web.bind.annotation.GetMapping | import org.springframework.web.bind.annotation.GetMapping | ||||
| import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||
| import org.springframework.web.bind.annotation.RestController | import org.springframework.web.bind.annotation.RestController | ||||
| @@ -56,4 +57,9 @@ class CustomerController( | |||||
| fun saveCustomer(@Valid @RequestBody saveCustomer: SaveCustomerRequest): SaveCustomerResponse { | fun saveCustomer(@Valid @RequestBody saveCustomer: SaveCustomerRequest): SaveCustomerResponse { | ||||
| return customerService.saveCustomer(saveCustomer) | return customerService.saveCustomer(saveCustomer) | ||||
| } | } | ||||
| @GetMapping("/getMaxCode") | |||||
| fun getMaxCustomerCode(): String{ | |||||
| return customerService.findMaxCustomerCode(); | |||||
| } | |||||
| } | } | ||||
| @@ -609,6 +609,8 @@ open class InvoiceService( | |||||
| if (workbook == null) { | if (workbook == null) { | ||||
| return InvoiceResponse(false, "No Excel import", ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList()) // if workbook is null | return InvoiceResponse(false, "No Excel import", ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList()) // if workbook is null | ||||
| } | } | ||||
| // find the imported Invoice Records | |||||
| val invoiceRecords = repository.findImportInvoiceInfoByAndDeletedFalse() | val invoiceRecords = repository.findImportInvoiceInfoByAndDeletedFalse() | ||||
| val importInvoices: MutableList<Map<String, Any>> = mutableListOf(); | val importInvoices: MutableList<Map<String, Any>> = mutableListOf(); | ||||
| @@ -1,7 +1,12 @@ | |||||
| package com.ffii.tsms.modules.report.service | package com.ffii.tsms.modules.report.service | ||||
| import com.ffii.core.support.JdbcDao | import com.ffii.core.support.JdbcDao | ||||
| import com.ffii.tsms.modules.common.holiday.service.HolidayService | |||||
| import com.ffii.tsms.modules.common.mail.web.models.StaffRecords | |||||
| import com.ffii.tsms.modules.common.mail.web.models.StaffWithMissingDates | |||||
| import com.ffii.tsms.modules.common.mail.web.models.WorkHourRecordsWithJoinDate | |||||
| import com.ffii.tsms.modules.data.entity.* | import com.ffii.tsms.modules.data.entity.* | ||||
| import com.ffii.tsms.modules.data.service.CompanyHolidayService | |||||
| import com.ffii.tsms.modules.data.service.SalaryEffectiveService | import com.ffii.tsms.modules.data.service.SalaryEffectiveService | ||||
| import com.ffii.tsms.modules.data.service.SalaryEffectiveService.MonthlyStaffSalaryData | import com.ffii.tsms.modules.data.service.SalaryEffectiveService.MonthlyStaffSalaryData | ||||
| import com.ffii.tsms.modules.data.service.StaffsService | import com.ffii.tsms.modules.data.service.StaffsService | ||||
| @@ -10,6 +15,7 @@ import com.ffii.tsms.modules.report.web.model.ExportCurrentStaffInfoRequest | |||||
| import com.ffii.tsms.modules.report.web.model.costAndExpenseRequest | import com.ffii.tsms.modules.report.web.model.costAndExpenseRequest | ||||
| 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.entity.TimesheetRepository | ||||
| import com.ffii.tsms.modules.timesheet.service.TimesheetsService | |||||
| import org.apache.commons.logging.Log | import org.apache.commons.logging.Log | ||||
| import org.apache.commons.logging.LogFactory | import org.apache.commons.logging.LogFactory | ||||
| import org.apache.poi.ss.usermodel.* | import org.apache.poi.ss.usermodel.* | ||||
| @@ -58,6 +64,10 @@ open class ReportService( | |||||
| private val teamLogRepository: TeamLogRepository, | private val teamLogRepository: TeamLogRepository, | ||||
| private val teamRepository: TeamRepository, | private val teamRepository: TeamRepository, | ||||
| private val staffService: StaffsService, | private val staffService: StaffsService, | ||||
| private val holidayService: HolidayService, | |||||
| private val companyHolidayService: CompanyHolidayService, | |||||
| private val timesheetsService: TimesheetsService, | |||||
| private val staffRepository: StaffRepository | |||||
| ) { | ) { | ||||
| private val logger: Log = LogFactory.getLog(javaClass) | private val logger: Log = LogFactory.getLog(javaClass) | ||||
| private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") | private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") | ||||
| @@ -3478,6 +3488,7 @@ open class ReportService( | |||||
| if (teamId != null) { | if (teamId != null) { | ||||
| sql.append( | sql.append( | ||||
| " and p.teamLead = :teamLeadId " | " and p.teamLead = :teamLeadId " | ||||
| + " and p.deleted = false " | |||||
| ) | ) | ||||
| } | } | ||||
| @@ -5729,6 +5740,16 @@ open class ReportService( | |||||
| val team: String, | val team: String, | ||||
| val lastRecordDate: String, | val lastRecordDate: String, | ||||
| ) | ) | ||||
| data class StaffLastRecordDataV2( | |||||
| val staffId: Long, | |||||
| val staffIdinCode: String, | |||||
| val staffName: String, | |||||
| val email: String, | |||||
| val team: String, | |||||
| val missingDates: List<LocalDate>, | |||||
| val lastRecordDate: LocalDate | |||||
| ) | |||||
| private fun getStaffLastRecordDateByDate(date: LocalDate): List<StaffLastRecordData>{ | private fun getStaffLastRecordDateByDate(date: LocalDate): List<StaffLastRecordData>{ | ||||
| val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") | val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") | ||||
| val sql = StringBuilder( | val sql = StringBuilder( | ||||
| @@ -5760,7 +5781,7 @@ open class ReportService( | |||||
| val currentDate = Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant()) | val currentDate = Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant()) | ||||
| departDate.after(currentDate) | departDate.after(currentDate) | ||||
| } else { | } else { | ||||
| false // Exclude entries with null departDate | |||||
| true // Include entries with null departDate, null = not departed | |||||
| } | } | ||||
| } | } | ||||
| .map { | .map { | ||||
| @@ -5777,16 +5798,100 @@ open class ReportService( | |||||
| return results | return results | ||||
| } | } | ||||
| // Function to generate a sequence of workdays, excluding weekends and public holidays | |||||
| private fun generateWorkdays(startDate: LocalDate, endDate: LocalDate, publicHolidays: Set<LocalDate>): Sequence<LocalDate> { | |||||
| return generateSequence(startDate) { date -> | |||||
| date.plusDays(1).takeIf { it <= endDate } | |||||
| }.filter { date -> | |||||
| date.dayOfWeek != DayOfWeek.SATURDAY && | |||||
| date.dayOfWeek != DayOfWeek.SUNDAY && | |||||
| !publicHolidays.contains(date) | |||||
| } | |||||
| } | |||||
| // Function to find missing workdays for staff | |||||
| private fun findStaffWithMissingDates(staffRecords: List<StaffRecords>, workdays: Sequence<LocalDate>, staffList: List<Staff>): List<StaffLastRecordDataV2> { | |||||
| val staffMap = staffList.associateBy { it.id } | |||||
| return staffRecords | |||||
| .groupBy { it.staffId } | |||||
| .map { (staffId, records) -> | |||||
| val joinDate = records.firstOrNull()?.joinDate ?: LocalDate.of(1970, 1, 1) | |||||
| val recordedDates = records.map { it.recordDate }.toSet() | |||||
| val staffWorkdays = workdays.filter { it >= joinDate }.toList() | |||||
| val missingDates = staffWorkdays.filterNot { recordedDates.contains(it) } | |||||
| val workDates = staffWorkdays.filter { recordedDates.contains(it) } | |||||
| val staffInfo = staffMap[staffId] | |||||
| StaffLastRecordDataV2( | |||||
| staffId = staffId, | |||||
| staffIdinCode = staffInfo?.staffId ?: "", | |||||
| staffName = staffInfo?.name ?: "", | |||||
| email = staffInfo?.email ?: "", | |||||
| team = staffInfo?.team?.code ?: "", | |||||
| missingDates = missingDates, | |||||
| lastRecordDate = workDates.last() | |||||
| ) | |||||
| } | |||||
| .filter { it.missingDates.isNotEmpty() } | |||||
| } | |||||
| private fun getStaffLastRecordDateByDateWithDayRange(date: LocalDate, dayRange: Long): List<StaffLastRecordDataV2>{ | |||||
| logger.info("-----------------------") | |||||
| logger.info("Report Start: Staff Last Record Date By Date With DayRange") | |||||
| val today = date | |||||
| val firstDate = today.minusDays(dayRange) | |||||
| val publicHolidays = holidayService.commonHolidayList().map { it.date } | |||||
| val companyHolidays = companyHolidayService.allCompanyHolidays().map { it.date } | |||||
| val allHolidays: Set<LocalDate> = (publicHolidays + companyHolidays).toSet() | |||||
| val workdays = generateWorkdays(firstDate, today, allHolidays) | |||||
| //Get all missing timesheet date and group by staff | |||||
| val args = mutableMapOf( | |||||
| "from" to firstDate, | |||||
| "to" to today, | |||||
| ) | |||||
| logger.info(args) | |||||
| val ts = timesheetsService.workHourRecordsWithinRange(args) | |||||
| val timesheet: List<StaffRecords> = ts.map { | |||||
| WorkHourRecordsWithJoinDate( | |||||
| staffId = it["staffId"].toString().toLong(), | |||||
| recordDate = LocalDate.parse(it["recordDate"].toString()), | |||||
| hours = it["hours"].toString().toDouble(), | |||||
| joinDate = it["joinDate"]?.toString()?.let { date -> LocalDate.parse(date) } ?: LocalDate.of(1970, 1, 1) | |||||
| ) | |||||
| }.groupBy { Triple(it.staffId, it.recordDate, it.joinDate) } | |||||
| .mapNotNull { (key, records) -> | |||||
| StaffRecords( | |||||
| key.second, // recordDate | |||||
| key.first, // staffId | |||||
| records.sumOf { it.hours }, | |||||
| key.third // joinDate | |||||
| ) | |||||
| } | |||||
| val staffs = staffRepository.findAllByEmployTypeAndDeletedFalseAndDepartDateIsNull("FT").filter { it.staffId != "A003" && it.staffId != "A004" && it.staffId != "B011" }.filter{ it.team?.code != "HO"} | |||||
| // val teams = teamRepository.findAll().filter { team -> team.deleted == false } | |||||
| val filteredTimesheet = timesheet.filter { staffs.map { it.id!! }.sorted().contains(it.staffId) } | |||||
| val staffWithMissingDates = findStaffWithMissingDates(filteredTimesheet, workdays, staffs) | |||||
| // logger.info(staffWithMissingDates.sortedBy { it.team }) | |||||
| return staffWithMissingDates.sortedWith(compareBy({ it.team }, { it.staffId })) | |||||
| } | |||||
| @Throws(IOException::class) | @Throws(IOException::class) | ||||
| private fun createLastRecordReportWorkbook( | private fun createLastRecordReportWorkbook( | ||||
| date: LocalDate, | date: LocalDate, | ||||
| dayRange: Long, | |||||
| templatePath: String, | templatePath: String, | ||||
| ): Workbook{ | ): Workbook{ | ||||
| val resource = ClassPathResource(templatePath) | val resource = ClassPathResource(templatePath) | ||||
| val templateInputStream = resource.inputStream | val templateInputStream = resource.inputStream | ||||
| val workbook: Workbook = XSSFWorkbook(templateInputStream) | val workbook: Workbook = XSSFWorkbook(templateInputStream) | ||||
| val result = getStaffLastRecordDateByDate(date) | |||||
| // val result = getStaffLastRecordDateByDate(date) | |||||
| val result = getStaffLastRecordDateByDateWithDayRange(date, dayRange) | |||||
| println(result) | println(result) | ||||
| var sheet: Sheet = workbook.getSheetAt(0) | var sheet: Sheet = workbook.getSheetAt(0) | ||||
| @@ -5812,7 +5917,9 @@ open class ReportService( | |||||
| rowIndex = 2 | rowIndex = 2 | ||||
| sheet.getRow(rowIndex).getCell(columnIndex+1).apply { | sheet.getRow(rowIndex).getCell(columnIndex+1).apply { | ||||
| setCellValue(date.format(DATE_FORMATTER)) | |||||
| val startDate = date.minusDays(dayRange).format(DATE_FORMATTER) | |||||
| val endDate = date.format(DATE_FORMATTER) | |||||
| setCellValue("$startDate - $endDate ($dayRange)") | |||||
| } | } | ||||
| rowIndex = 3 | rowIndex = 3 | ||||
| @@ -5830,9 +5937,10 @@ open class ReportService( | |||||
| val emailCell = row.createCell(2) | val emailCell = row.createCell(2) | ||||
| val teamCell = row.createCell(3) | val teamCell = row.createCell(3) | ||||
| val lastRecordDateCell = row.createCell(4) | val lastRecordDateCell = row.createCell(4) | ||||
| val numOfMissingDateCell = row.createCell(5) | |||||
| staffIdCell.apply { | staffIdCell.apply { | ||||
| setCellValue(it.staffId) | |||||
| setCellValue(it.staffIdinCode) | |||||
| cellStyle =blackFontStyle | cellStyle =blackFontStyle | ||||
| } | } | ||||
| @@ -5852,7 +5960,12 @@ open class ReportService( | |||||
| } | } | ||||
| lastRecordDateCell.apply { | lastRecordDateCell.apply { | ||||
| setCellValue(it.lastRecordDate) | |||||
| setCellValue(it.lastRecordDate.toString()) | |||||
| cellStyle =blackFontStyle | |||||
| } | |||||
| numOfMissingDateCell.apply { | |||||
| setCellValue(it.missingDates.size.toDouble()) // count total number of missing date in list | |||||
| cellStyle =blackFontStyle | cellStyle =blackFontStyle | ||||
| } | } | ||||
| @@ -5864,8 +5977,9 @@ open class ReportService( | |||||
| fun genLastRecordReport( | fun genLastRecordReport( | ||||
| date: LocalDate, | date: LocalDate, | ||||
| dayRange: Long | |||||
| ): ByteArray{ | ): ByteArray{ | ||||
| val workbook = createLastRecordReportWorkbook(date, LAST_RECORD_REPORT) | |||||
| val workbook = createLastRecordReportWorkbook(date, dayRange, LAST_RECORD_REPORT) | |||||
| val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() | val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() | ||||
| workbook.write(outputStream) | workbook.write(outputStream) | ||||
| @@ -387,16 +387,17 @@ class ReportController( | |||||
| val reportResult: ByteArray = excelReportService.genProjectMonthlyReport(request.yearMonth, request.projectId, request.holidays) | val reportResult: ByteArray = excelReportService.genProjectMonthlyReport(request.yearMonth, request.projectId, request.holidays) | ||||
| return ResponseEntity.ok() | return ResponseEntity.ok() | ||||
| .header("filename", "Project Monthly Report - " + LocalDate.now() + ".xlsx") | |||||
| .header("filename", "Project Monthly daily Summary Report - " + LocalDate.now() + ".xlsx") | |||||
| .body(ByteArrayResource(reportResult)) | .body(ByteArrayResource(reportResult)) | ||||
| } | } | ||||
| @PostMapping("/gen-staff-last-record-report") | @PostMapping("/gen-staff-last-record-report") | ||||
| fun genLastRecordReport(@RequestBody @Valid request: LastRecordReportRequest): ResponseEntity<Resource>{ | fun genLastRecordReport(@RequestBody @Valid request: LastRecordReportRequest): ResponseEntity<Resource>{ | ||||
| println("================= ${request.dateString}") | println("================= ${request.dateString}") | ||||
| println("================= ${request.dayRange}") | |||||
| val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") | val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") | ||||
| val date = LocalDate.parse(request.dateString, formatter) | val date = LocalDate.parse(request.dateString, formatter) | ||||
| val reportResult: ByteArray = excelReportService.genLastRecordReport(date) | |||||
| val reportResult: ByteArray = excelReportService.genLastRecordReport(date, request.dayRange) | |||||
| return ResponseEntity.ok() | return ResponseEntity.ok() | ||||
| .header("filename", "Staff Last Record Report - " + LocalDate.now() + ".xlsx") | .header("filename", "Staff Last Record Report - " + LocalDate.now() + ".xlsx") | ||||
| @@ -79,7 +79,8 @@ data class ProjectMonthlyRequest ( | |||||
| ) | ) | ||||
| data class LastRecordReportRequest ( | data class LastRecordReportRequest ( | ||||
| val dateString: String | |||||
| val dateString: String, | |||||
| val dayRange: Long | |||||
| ) | ) | ||||
| data class ExportCurrentStaffInfoRequest ( | data class ExportCurrentStaffInfoRequest ( | ||||