Преглед изворни кода

Update Mail Service

Update customer max code
Update Last record Report
master
MSI\2Fi пре 3 недеља
родитељ
комит
1ed2cc68b2
10 измењених фајлова са 278 додато и 15 уклоњено
  1. +118
    -4
      src/main/java/com/ffii/tsms/modules/common/mail/service/MailReminderService.kt
  2. +2
    -2
      src/main/java/com/ffii/tsms/modules/common/mail/web/MailController.kt
  3. +7
    -0
      src/main/java/com/ffii/tsms/modules/common/mail/web/models/WorkHourRecords.kt
  4. +18
    -0
      src/main/java/com/ffii/tsms/modules/data/service/CustomerService.kt
  5. +6
    -0
      src/main/java/com/ffii/tsms/modules/data/web/CustomerController.kt
  6. +2
    -0
      src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt
  7. +120
    -6
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  8. +3
    -2
      src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt
  9. +2
    -1
      src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt
  10. BIN
      src/main/resources/templates/report/AR10_Staff Last Record Report.xlsx

+ 118
- 4
src/main/java/com/ffii/tsms/modules/common/mail/service/MailReminderService.kt Прегледај датотеку

@@ -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.mail.pojo.MailRequest
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.WorkHourRecordsWithJoinDate
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 org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.springframework.cglib.core.Local
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service
import java.time.DayOfWeek
@@ -58,7 +60,7 @@ open class MailReminderService(
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 header = StringBuilder(
" <tr> "
@@ -69,11 +71,21 @@ open class MailReminderService(
)
tableStarter.append(header)
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(
" <tr> "
+ " <td>${per.staffId}</td>"
+ " <td>${per.name}</td>"
+ " <td>${per.missingDates.joinToString(", ")}</td>"
+ " <td>${missDatesHtml}</td>"
+ " </tr> "
)
}
@@ -103,7 +115,7 @@ open class MailReminderService(
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() {
logger.info("-----------------------")
logger.info("Scheduled Start: 7th email reminder")
@@ -153,6 +165,7 @@ open class MailReminderService(
key.third // joinDate
)
}
// println("**** timesheetByIdAndRecord: ${timesheetByIdAndRecord.filter { it.staffId == 1L }}")
val goodStaffsList = filteredLastMonthDays.map { date ->
val matchedStaffIds = timesheetByIdAndRecord
.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
open fun test7thStaffList(){
val today = LocalDate.now()
@@ -259,7 +373,7 @@ open class MailReminderService(
Pair(date, matchedStaffIds)
}.sortedBy { it.first }

println(goodStaffsList)
// println(goodStaffsList)
}
}



+ 2
- 2
src/main/java/com/ffii/tsms/modules/common/mail/web/MailController.kt Прегледај датотеку

@@ -37,7 +37,7 @@ class MailController(
}
@GetMapping("/test7th")
fun test7th() {
mailReminderService.sendTimesheetToTeamLead7TH()
mailReminderService.sendMissingTimesheetDateToTeamLead7th()
}
@GetMapping("/test15th")
fun test15th() {
@@ -46,7 +46,7 @@ class MailController(

@GetMapping("/test7th-staff-list")
fun test7thStaffList(){
mailReminderService.test7thStaffList()
mailReminderService.sendMissingTimesheetDateToTeamLead7th()
}

@GetMapping("/test15th-staff-list")


+ 7
- 0
src/main/java/com/ffii/tsms/modules/common/mail/web/models/WorkHourRecords.kt Прегледај датотеку

@@ -21,3 +21,10 @@ data class StaffRecords(
val sumOfHours: Double,
val joinDate: LocalDate? = LocalDate.of(1970,1,1)
)

data class StaffWithMissingDates(
val staffId: Long,
val staffIdinCode: String,
val staffName: String,
val missingDates: List<LocalDate>
)

+ 18
- 0
src/main/java/com/ffii/tsms/modules/data/service/CustomerService.kt Прегледај датотеку

@@ -103,4 +103,22 @@ open class CustomerService(

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
}
}

+ 6
- 0
src/main/java/com/ffii/tsms/modules/data/web/CustomerController.kt Прегледај датотеку

@@ -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.SaveCustomerResponse
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.RequestMapping
import org.springframework.web.bind.annotation.RestController
@@ -56,4 +57,9 @@ class CustomerController(
fun saveCustomer(@Valid @RequestBody saveCustomer: SaveCustomerRequest): SaveCustomerResponse {
return customerService.saveCustomer(saveCustomer)
}

@GetMapping("/getMaxCode")
fun getMaxCustomerCode(): String{
return customerService.findMaxCustomerCode();
}
}

+ 2
- 0
src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt Прегледај датотеку

@@ -609,6 +609,8 @@ open class InvoiceService(
if (workbook == 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 importInvoices: MutableList<Map<String, Any>> = mutableListOf();


+ 120
- 6
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt Прегледај датотеку

@@ -1,7 +1,12 @@
package com.ffii.tsms.modules.report.service

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.service.CompanyHolidayService
import com.ffii.tsms.modules.data.service.SalaryEffectiveService
import com.ffii.tsms.modules.data.service.SalaryEffectiveService.MonthlyStaffSalaryData
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.timesheet.entity.Timesheet
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.LogFactory
import org.apache.poi.ss.usermodel.*
@@ -58,6 +64,10 @@ open class ReportService(
private val teamLogRepository: TeamLogRepository,
private val teamRepository: TeamRepository,
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 DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd")
@@ -3478,6 +3488,7 @@ open class ReportService(
if (teamId != null) {
sql.append(
" and p.teamLead = :teamLeadId "
+ " and p.deleted = false "
)
}

@@ -5729,6 +5740,16 @@ open class ReportService(
val team: 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>{
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val sql = StringBuilder(
@@ -5760,7 +5781,7 @@ open class ReportService(
val currentDate = Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant())
departDate.after(currentDate)
} else {
false // Exclude entries with null departDate
true // Include entries with null departDate, null = not departed
}
}
.map {
@@ -5777,16 +5798,100 @@ open class ReportService(
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)
private fun createLastRecordReportWorkbook(
date: LocalDate,
dayRange: Long,
templatePath: String,
): Workbook{
val resource = ClassPathResource(templatePath)
val templateInputStream = resource.inputStream
val workbook: Workbook = XSSFWorkbook(templateInputStream)

val result = getStaffLastRecordDateByDate(date)
// val result = getStaffLastRecordDateByDate(date)
val result = getStaffLastRecordDateByDateWithDayRange(date, dayRange)
println(result)

var sheet: Sheet = workbook.getSheetAt(0)
@@ -5812,7 +5917,9 @@ open class ReportService(

rowIndex = 2
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
@@ -5830,9 +5937,10 @@ open class ReportService(
val emailCell = row.createCell(2)
val teamCell = row.createCell(3)
val lastRecordDateCell = row.createCell(4)
val numOfMissingDateCell = row.createCell(5)

staffIdCell.apply {
setCellValue(it.staffId)
setCellValue(it.staffIdinCode)
cellStyle =blackFontStyle
}

@@ -5852,7 +5960,12 @@ open class ReportService(
}

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
}

@@ -5864,8 +5977,9 @@ open class ReportService(

fun genLastRecordReport(
date: LocalDate,
dayRange: Long
): ByteArray{
val workbook = createLastRecordReportWorkbook(date, LAST_RECORD_REPORT)
val workbook = createLastRecordReportWorkbook(date, dayRange, LAST_RECORD_REPORT)

val outputStream: ByteArrayOutputStream = ByteArrayOutputStream()
workbook.write(outputStream)


+ 3
- 2
src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt Прегледај датотеку

@@ -387,16 +387,17 @@ class ReportController(
val reportResult: ByteArray = excelReportService.genProjectMonthlyReport(request.yearMonth, request.projectId, request.holidays)

return ResponseEntity.ok()
.header("filename", "Project Monthly Report - " + LocalDate.now() + ".xlsx")
.header("filename", "Project Monthly daily Summary Report - " + LocalDate.now() + ".xlsx")
.body(ByteArrayResource(reportResult))
}

@PostMapping("/gen-staff-last-record-report")
fun genLastRecordReport(@RequestBody @Valid request: LastRecordReportRequest): ResponseEntity<Resource>{
println("================= ${request.dateString}")
println("================= ${request.dayRange}")
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val date = LocalDate.parse(request.dateString, formatter)
val reportResult: ByteArray = excelReportService.genLastRecordReport(date)
val reportResult: ByteArray = excelReportService.genLastRecordReport(date, request.dayRange)

return ResponseEntity.ok()
.header("filename", "Staff Last Record Report - " + LocalDate.now() + ".xlsx")


+ 2
- 1
src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt Прегледај датотеку

@@ -79,7 +79,8 @@ data class ProjectMonthlyRequest (
)

data class LastRecordReportRequest (
val dateString: String
val dateString: String,
val dayRange: Long
)

data class ExportCurrentStaffInfoRequest (


BIN
src/main/resources/templates/report/AR10_Staff Last Record Report.xlsx Прегледај датотеку


Loading…
Откажи
Сачувај