@@ -33,17 +33,23 @@ import java.util.*
import java.awt.Color
import java.awt.Color
import java.math.RoundingMode
import java.math.RoundingMode
import java.time.Year
import java.time.Year
import javax.swing.plaf.synth.Region
import kotlin.collections.ArrayList
import kotlin.collections.ArrayList
data class DayInfo(val date: String?, val weekday: String?)
data class DayInfo(val date: String?, val weekday: String?)
data class TsData(var manhour: Double, var cost: Double)
data class InOut(var In: TsData, var Out: TsData)
@Service
@Service
open class ReportService(
open class ReportService(
private val jdbcDao: JdbcDao,
private val jdbcDao: JdbcDao,
private val projectRepository: ProjectRepository,
private val projectRepository: ProjectRepository,
private val salaryEffectiveService: SalaryEffectiveService, private val salaryRepository: SalaryRepository, private val timesheetRepository: TimesheetRepository
private val salaryEffectiveService: SalaryEffectiveService,
private val salaryEffectiveRepository: SalaryEffectiveRepository,
private val salaryRepository: SalaryRepository,
private val timesheetRepository: TimesheetRepository,
private val teamLogRepository: TeamLogRepository
) {
) {
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")
@@ -63,6 +69,45 @@ open class ReportService(
private val COMPLETION_PROJECT = "templates/report/AR05_Project Completion Report.xlsx"
private val COMPLETION_PROJECT = "templates/report/AR05_Project Completion Report.xlsx"
private val CROSS_TEAM_CHARGE_REPORT = "templates/report/Cross Team Charge Report.xlsx"
private val CROSS_TEAM_CHARGE_REPORT = "templates/report/Cross Team Charge Report.xlsx"
private fun cellBorderArgs(top: Int, bottom: Int, left: Int, right: Int): MutableMap<String, Any> {
var cellBorderArgs = mutableMapOf<String, Any>()
when (top) {
1 -> cellBorderArgs.put(CellUtil.BORDER_TOP, BorderStyle.THIN)
2 -> cellBorderArgs.put(CellUtil.BORDER_TOP, BorderStyle.THICK)
}
when (bottom) {
1 -> cellBorderArgs.put(CellUtil.BORDER_BOTTOM, BorderStyle.THIN)
2 -> cellBorderArgs.put(CellUtil.BORDER_BOTTOM, BorderStyle.THICK)
}
when (left) {
1 -> cellBorderArgs.put(CellUtil.BORDER_LEFT, BorderStyle.THIN)
2 -> cellBorderArgs.put(CellUtil.BORDER_LEFT, BorderStyle.THICK)
}
when (right) {
1 -> cellBorderArgs.put(CellUtil.BORDER_RIGHT, BorderStyle.THIN)
2 -> cellBorderArgs.put(CellUtil.BORDER_RIGHT, BorderStyle.THICK)
}
return cellBorderArgs
}
private fun setRegionBorders(top: Int, bottom: Int, left: Int, right: Int, region: CellRangeAddress, sheet: Sheet) {
when (top) {
1 -> RegionUtil.setBorderTop(BorderStyle.THIN, region, sheet)
2 -> RegionUtil.setBorderTop(BorderStyle.THICK, region, sheet)
}
when (bottom) {
1 -> RegionUtil.setBorderBottom(BorderStyle.THIN, region, sheet)
2 -> RegionUtil.setBorderBottom(BorderStyle.THICK, region, sheet)
}
when (left) {
1 -> RegionUtil.setBorderLeft(BorderStyle.THIN, region, sheet)
2 -> RegionUtil.setBorderLeft(BorderStyle.THICK, region, sheet)
}
when (right) {
1 -> RegionUtil.setBorderRight(BorderStyle.THIN, region, sheet)
2 -> RegionUtil.setBorderRight(BorderStyle.THICK, region, sheet)
}
}
private val chargeFee = 1.15
private val chargeFee = 1.15
private fun conditionalFormattingNegative(sheet: Sheet) {
private fun conditionalFormattingNegative(sheet: Sheet) {
// Create a conditional formatting rule
// Create a conditional formatting rule
@@ -330,6 +375,7 @@ open class ReportService(
grades: List<Grade>,
grades: List<Grade>,
monthlyStaffSalaryEffective: List<MonthlyStaffSalaryData>,
monthlyStaffSalaryEffective: List<MonthlyStaffSalaryData>,
teamId: String,
teamId: String,
gradeLog: List<GradeLog>,
): ByteArray {
): ByteArray {
// Generate the Excel report with query results
// Generate the Excel report with query results
val workbook: Workbook =
val workbook: Workbook =
@@ -340,6 +386,7 @@ open class ReportService(
grades,
grades,
monthlyStaffSalaryEffective,
monthlyStaffSalaryEffective,
teamId,
teamId,
gradeLog,
CROSS_TEAM_CHARGE_REPORT
CROSS_TEAM_CHARGE_REPORT
)
)
@@ -3547,6 +3594,342 @@ open class ReportService(
return jdbcDao.queryForList(sql.toString(), args)
return jdbcDao.queryForList(sql.toString(), args)
}
}
private fun generateTeamBlock(
teams: List<Team>,
sortedTeams: MutableList<Team>,
grades: List<Grade>,
timesheets: List<Timesheet>,
gradeLog: List<GradeLog>,
salaryEffective: List<SalaryEffective>,
): MutableMap<String, MutableMap<String, InOut>> {
// val teamlog = teamLogRepository.findAll().filter { it.deleted == false }
val gradeMap: MutableMap<String, InOut> = mutableMapOf()
var teamsMap: MutableMap<String, MutableMap<String, InOut>> = mutableMapOf()
grades.forEach {
val key = it.code
if (key !in gradeMap) {
gradeMap[key] = InOut(
TsData(0.0, 0.0),
TsData(0.0, 0.0)
)
}
}
teams.forEach { team ->
val key = team.code
if (key !in teamsMap) {
// Create a new map for each team with copies of the entries from gradeMap
teamsMap[key] = gradeMap.mapValues { (_, value) ->
InOut(
TsData(value.In.manhour, value.In.cost),
TsData(value.Out.manhour, value.Out.cost)
)
}.toMutableMap()
}
}
println("teamsMap")
println(gradeMap)
// teamsMap["TW"]!!["1"]!!.In.manhour += 1000
println(teamsMap)
sortedTeams.forEach { team ->
val currentTeam = team.code
val _timesheets = timesheets.filter { ts ->
// for team log
// val thisTeam = teamlog.filter {
// it.from.isBefore(ts.recordDate)
// && it.to != null
// && it.staff.id == ts.staff!!.id
// }.maxByOrNull { it.from }
ts.project!!.teamLead!!.team.code == currentTeam // check isChargingTeam
&&
ts.project!!.teamLead!!.team.code != ts.staff!!.team.code // check isCrossTeam
// ts.project!!.teamLead!!.team.id != thisTeam!!.team.id // for team log
}
_timesheets.forEach {ts ->
// this team charging others
// get the grade and salary data of the record
val _grade = gradeLog.find { it.staff.id == ts.staff!!.id }
val gradeCode = _grade!!.grade.code
val otMultiplier = 1
val thisSE = salaryEffective.filter {
it.staff.id == ts.staff!!.id
&&
it.date.isBefore(ts.recordDate)
}.maxByOrNull { it.date }
val normalHour = ts.normalConsumed ?: 0.0
val otHour = ts.otConsumed ?: 0.0
val normalCost = normalHour.times(thisSE!!.salary.hourlyRate.toDouble())
val otCost = otHour.times(thisSE.salary.hourlyRate.toDouble()).times(otMultiplier)
//assigning data
val projectTeam = ts.project!!.teamLead!!.team.code
val staffTeam = ts.staff!!.team.code
// write in
println("putting in")
var tsInData = teamsMap[projectTeam]!![gradeCode]!!.In
println(tsInData)
tsInData.manhour += normalHour + otHour
tsInData.cost += normalCost + otCost
// write out
println("putting out")
val tsOutData = teamsMap[staffTeam]!!.get(gradeCode)!!.Out
tsOutData.manhour += normalHour + otHour
tsOutData.cost += normalCost + otCost
}
}
println("all team - gradeMap")
println(teamsMap)
return teamsMap
}
private fun alignTopCenter(tempCell: Cell) {
CellUtil.setVerticalAlignment(tempCell, VerticalAlignment.TOP)
CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER)
}
private fun createCrossTeamForm(
workbook: Workbook,
sheet: Sheet,
_rowIndex: Int,
_columnIndex: Int,
teamsMap: MutableMap<String, MutableMap<String, InOut>>
) {
var rowIndex = _rowIndex
var columnIndex = _columnIndex
var tempRow: Row
var tempCell: Cell
val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)")
fun dataFormatArgs(accountingStyle: Short): MutableMap<String, Any> {
val dataFormatArgs = mutableMapOf<String, Any>(
CellUtil.DATA_FORMAT to accountingStyle
)
return dataFormatArgs
}
fun fontArgs(isBold: Boolean): MutableMap<String, Any>{
val font = sheet.workbook.createFont()
font.bold = isBold
font.fontName = "Times New Roman"
val fontArgs = mutableMapOf<String, Any>(
CellUtil.FONT to font.index,
CellUtil.WRAP_TEXT to true,
)
return fontArgs
}
val InHourLetter = CellReference.convertNumToColString(3) // col D
val InCostLetter = CellReference.convertNumToColString(4) // col E
val OutHourLetter = CellReference.convertNumToColString(5) // col F
val OutCostLetter = CellReference.convertNumToColString(6) // col G
val NetHourLetter = CellReference.convertNumToColString(7) // col G
val NetCostLetter = CellReference.convertNumToColString(8) // col G
val subTotalRowIndexList = mutableListOf<Int>()
teamsMap.forEach{ (teamCode, gradeMap) ->
columnIndex = 0
tempRow = getOrCreateRow(sheet, rowIndex)
tempCell = getOrCreateCell(tempRow, columnIndex)
tempCell.setCellValue(teamCode)
alignTopCenter(tempCell)
val teamCodeRegion = CellRangeAddress(rowIndex, rowIndex + 5,0,0)
sheet.addMergedRegion(teamCodeRegion)
setRegionBorders(2,2,1,1, teamCodeRegion, sheet)
CellUtil.setCellStyleProperties(tempCell, fontArgs(false))
columnIndex++
val gradeRegion = CellRangeAddress(rowIndex, rowIndex + 4,1,1)
tempCell = getOrCreateCell(tempRow, columnIndex)
tempCell.setCellValue("Grade")
setRegionBorders(1,1,1,1, gradeRegion, sheet)
CellUtil.setCellStyleProperties(tempCell, fontArgs(true))
alignTopCenter(tempCell)
sheet.addMergedRegion(gradeRegion)
columnIndex++
gradeMap.forEach{ (gradeCode, inOut) ->
tempRow = getOrCreateRow(sheet, rowIndex)
tempCell = getOrCreateCell(tempRow, columnIndex).apply {
setCellValue(gradeCode)
alignTopCenter(this)
}
CellUtil.setCellStyleProperties(tempCell, cellBorderArgs(1,1,1,1)+fontArgs(false))
for (i in 1..6) { // horizontal loop: payment In & payment Out & Net Amount
tempCell = getOrCreateCell(tempRow, columnIndex + i).apply {
when (i) {
1 -> setCellValue(inOut.In.manhour)
2 -> setCellValue(inOut.In.cost)
3 -> setCellValue(inOut.Out.manhour)
4 -> setCellValue(inOut.Out.cost)
5 -> cellFormula = "${InHourLetter}${rowIndex+1}-${OutHourLetter}${rowIndex+1}"
6 -> cellFormula = "${InCostLetter}${rowIndex+1}-${OutCostLetter}${rowIndex+1}"
}
CellUtil.setCellStyleProperties(this,
cellBorderArgs(1,1,1,1)
+ fontArgs(false)
+ dataFormatArgs(accountingStyle)
)
}
} // end loop: payment In & payment Out & Net Amount
rowIndex++
}
// write subtotal
columnIndex = 1
tempRow = getOrCreateRow(sheet, rowIndex)
tempCell = getOrCreateCell(tempRow, columnIndex).apply {
setCellValue("Sub-total:")
CellUtil.setCellStyleProperties(this,
cellBorderArgs(1,1,1,1)
+ fontArgs(false))
alignTopCenter(this)
}
subTotalRowIndexList.add(rowIndex)
// InHour subtotal
columnIndex = 3
tempCell = getOrCreateCell(tempRow, columnIndex).apply {
cellFormula = "SUM(${InHourLetter}${rowIndex-4}:${InHourLetter}${rowIndex})"
CellUtil.setCellStyleProperties(this,
cellBorderArgs(1,1,1,1)
+ fontArgs(false)
+ dataFormatArgs(accountingStyle)
)
}
// InCost subtotal
columnIndex = 4
tempCell = getOrCreateCell(tempRow, columnIndex).apply {
cellFormula = "SUM(${InCostLetter}${rowIndex-4}:${InCostLetter}${rowIndex})"
CellUtil.setCellStyleProperties(this,
cellBorderArgs(1,1,1,1)
+ fontArgs(false)
+ dataFormatArgs(accountingStyle)
)
}
// OutHour subtotal
columnIndex = 5
tempCell = getOrCreateCell(tempRow, columnIndex).apply {
cellFormula = "SUM(${OutHourLetter}${rowIndex-4}:${OutHourLetter}${rowIndex})"
CellUtil.setCellStyleProperties(this,
cellBorderArgs(1,1,1,1)
+ fontArgs(false)
+ dataFormatArgs(accountingStyle)
)
}
// OutCost subtotal
columnIndex = 6
tempCell = getOrCreateCell(tempRow, columnIndex).apply {
cellFormula = "SUM(${OutCostLetter}${rowIndex-4}:${OutCostLetter}${rowIndex})"
CellUtil.setCellStyleProperties(this,
cellBorderArgs(1,1,1,1)
+ fontArgs(false)
+ dataFormatArgs(accountingStyle)
)
}
// NetHour subtotal
columnIndex = 7
tempCell = getOrCreateCell(tempRow, columnIndex).apply {
cellFormula = "SUM(${NetHourLetter}${rowIndex-4}:${NetHourLetter}${rowIndex})"
CellUtil.setCellStyleProperties(this, cellBorderArgs(1,1,1,1)
+ fontArgs(false)
+ dataFormatArgs(accountingStyle)
)
}
// NetCost subtotal
columnIndex = 8
tempCell = getOrCreateCell(tempRow, columnIndex).apply {
cellFormula = "SUM(${NetCostLetter}${rowIndex-4}:${NetCostLetter}${rowIndex})"
CellUtil.setCellStyleProperties(this, cellBorderArgs(1,1,1,1)
+ fontArgs(false)
+ dataFormatArgs(accountingStyle)
)
}
// merge at last
val subtotalRegion = CellRangeAddress(rowIndex, rowIndex,1,2)
sheet.addMergedRegion(subtotalRegion)
val subtotalRowRegion = CellRangeAddress(rowIndex, rowIndex,0,8)
setRegionBorders(1,2,1,1, subtotalRowRegion, sheet)
rowIndex++
}
// rowIndex == 47
columnIndex = 1
tempRow = getOrCreateRow(sheet, rowIndex)
tempCell = getOrCreateCell(tempRow, columnIndex).apply {
setCellValue("Total:")
CellUtil.setCellStyleProperties(this,
cellBorderArgs(1,1,1,1)
+ fontArgs(false)
)
alignTopCenter(this)
}
val totalRegion = CellRangeAddress(rowIndex, rowIndex,1,2)
sheet.addMergedRegion(totalRegion)
for (i in 3..8) {
columnIndex = i
val subtotalList = mutableListOf<String>()
val currColumnLetter = CellReference.convertNumToColString(i)
subTotalRowIndexList.forEach{
subtotalList.add("${currColumnLetter}${it+1}")
}
val subtotalFormula = subtotalList.joinToString("+")
tempCell = getOrCreateCell(tempRow, columnIndex).apply {
cellFormula = subtotalFormula
CellUtil.setCellStyleProperties(this, cellBorderArgs(1,1,1,1)
+ fontArgs(false)
+ dataFormatArgs(accountingStyle)
)
}
}
val subtotalRowRegion = CellRangeAddress(rowIndex, rowIndex,0,8)
setRegionBorders(1,2,1,1, subtotalRowRegion, sheet)
}
private fun createThirdSheetTeamChargeReport(
month: String,
workbook: Workbook,
timesheets: List<Timesheet>,
teams: List<Team>,
grades: List<Grade>,
teamId: String,
gradeLog: List<GradeLog>,
isTeamLead: Boolean,
) {
val salaryEffective = salaryEffectiveRepository.findAll()
var sheet: Sheet = workbook.getSheetAt(2)
val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)")
val rowIndex = 1 // Assuming the location is in (1,2), which is the report date field
val columnIndex = 2
val monthFormat = DateTimeFormatter.ofPattern("MMMM yyyy", Locale.ENGLISH)
val reportMonth = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyy-MM"))
val convertReportMonth = YearMonth.of(reportMonth.year, reportMonth.month).format(monthFormat)
sheet.getRow(rowIndex).getCell(columnIndex).apply {
setCellValue(convertReportMonth)
}
var sortedTeams = teams.sortedBy { it.id }.toMutableList()
if (teamId.lowercase() != "all") {
// sortedTeams = teams.sortedWith(compareBy { if (it.id == teamId.toLong()) 0 else 1 }).toMutableList()
sortedTeams = mutableListOf(sortedTeams.find { teamId.toLong() == it.id }!!)
}
//// generate info map /////
val teamsInOutMap: MutableMap<String, MutableMap<String, InOut>> = generateTeamBlock(
teams,
sortedTeams,
grades,
timesheets,
gradeLog,
salaryEffective,
)
///// end of function ////
///// create the form ////
createCrossTeamForm(
workbook,
sheet,
5,
0,
teamsInOutMap
)
// autosize
for (i in 3..8) {
sheet.setColumnWidth(i, 20 * 256)
}
}
@Throws(IOException::class)
@Throws(IOException::class)
private fun createCrossTeamChargeReport(
private fun createCrossTeamChargeReport(
month: String,
month: String,
@@ -3555,6 +3938,7 @@ open class ReportService(
grades: List<Grade>,
grades: List<Grade>,
monthlyStaffSalaryEffective: List<MonthlyStaffSalaryData>,
monthlyStaffSalaryEffective: List<MonthlyStaffSalaryData>,
teamId: String,
teamId: String,
gradeLog: List<GradeLog>,
templatePath: String,
templatePath: String,
): Workbook {
): Workbook {
// please create a new function for each report template
// please create a new function for each report template
@@ -4119,10 +4503,19 @@ open class ReportService(
}
}
}
}
}
}
val isTeamLead = false
conditionalFormattingNegative(sheet)
conditionalFormattingNegative(sheet)
conditionalFormattingPositive(sheet)
conditionalFormattingPositive(sheet)
createThirdSheetTeamChargeReport(
month,
workbook,
timesheets,
teams,
grades,
teamId,
gradeLog,
isTeamLead,
)
return workbook
return workbook
}
}
// Use to Calculate cummunlative expenditure
// Use to Calculate cummunlative expenditure