ソースを参照

Merge branch 'master' of https://git.2fi-solutions.com/davidhui/TSMS-backend

# Conflicts:
#	src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
tags/Baseline_30082024_BACKEND_UAT
MSI\2Fi 1年前
コミット
6eeeccf010
7個のファイルの変更317行の追加226行の削除
  1. +4
    -0
      src/main/java/com/ffii/tsms/modules/data/entity/SalaryRepository.java
  2. +2
    -1
      src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java
  3. +2
    -2
      src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt
  4. +3
    -0
      src/main/java/com/ffii/tsms/modules/project/entity/StaffAllocationRepository.kt
  5. +281
    -221
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  6. +2
    -1
      src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt
  7. +23
    -1
      src/main/java/com/ffii/tsms/modules/user/web/UserController.java

+ 4
- 0
src/main/java/com/ffii/tsms/modules/data/entity/SalaryRepository.java ファイルの表示

@@ -2,9 +2,13 @@ package com.ffii.tsms.modules.data.entity;

import com.ffii.core.support.AbstractRepository;
import com.ffii.tsms.modules.data.entity.projections.SalarySearchInfo;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;

public interface SalaryRepository extends AbstractRepository<Salary, Long> {
List<SalarySearchInfo> findSalarySearchInfoByOrderBySalaryPoint();

Optional<Salary> findBySalaryPoint(@Param("salaryPoint") Long salaryPoint);
}

+ 2
- 1
src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java ファイルの表示

@@ -5,6 +5,7 @@ import com.ffii.core.support.AbstractRepository;
import com.ffii.tsms.modules.data.entity.projections.StaffSearchInfo;
import org.springframework.data.repository.query.Param;

import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -13,7 +14,7 @@ public interface StaffRepository extends AbstractRepository<Staff, Long> {
List<StaffSearchInfo> findStaffSearchInfoByAndDeletedFalse();
List<StaffSearchInfo> findStaffSearchInfoByAndDeletedFalseAndTeamIdIsNull();

List<StaffSearchInfo> findAllStaffSearchInfoByIdIn(List<Long> ids);
List<StaffSearchInfo> findAllStaffSearchInfoByIdIn(List<Serializable> ids);
Optional<Staff> findByStaffId(@Param("staffId") String staffId);

Optional<StaffSearchInfo> findStaffSearchInfoById(@Param("id") Long id);


+ 2
- 2
src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt ファイルの表示

@@ -35,7 +35,7 @@ open class StaffsService(
) : AbstractBaseEntityService<Staff, Long, StaffRepository>(jdbcDao, staffRepository) {
open fun getTeamLeads(): List<StaffSearchInfo> {
// TODO: Replace by actual logic
val teamIds = teamRepository.findAll().map { team -> team.staff.id }
val teamIds = teamRepository.findAll().filter { team -> team.deleted == false }.map { team -> team.staff.id }
return staffRepository.findAllStaffSearchInfoByIdIn(teamIds)
}

@@ -107,7 +107,7 @@ open class StaffsService(
val company = companyRepository.findById(req.companyId).orElseThrow()
val grade = if (req.gradeId != null && req.gradeId > 0L) gradeRepository.findById(req.gradeId).orElseThrow() else null
val team = if (req.teamId != null && req.teamId > 0L) teamRepository.findById(req.teamId).orElseThrow() else null
val salary = salaryRepository.findById(req.salaryId).orElseThrow()
val salary = salaryRepository.findBySalaryPoint(req.salaryId).orElseThrow()
// val salaryEffective = salaryEffectiveRepository.findById(req.salaryEffId).orElseThrow()
val department = departmentRepository.findById(req.departmentId).orElseThrow()



+ 3
- 0
src/main/java/com/ffii/tsms/modules/project/entity/StaffAllocationRepository.kt ファイルの表示

@@ -2,8 +2,11 @@ package com.ffii.tsms.modules.project.entity;

import com.ffii.core.support.AbstractRepository
import com.ffii.tsms.modules.data.entity.Staff
import com.ffii.tsms.modules.data.entity.projections.StaffSearchInfo
import java.time.LocalDate

interface StaffAllocationRepository : AbstractRepository<StaffAllocation, Long> {
fun findAssignedProjectsByStaff(staff: Staff): List<StaffAllocation>

fun findByProject(project: Project): List<StaffAllocation>
}

+ 281
- 221
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt ファイルの表示

@@ -7,6 +7,7 @@ import com.ffii.tsms.modules.project.entity.Invoice
import com.ffii.tsms.modules.project.entity.Project
import com.ffii.tsms.modules.timesheet.entity.Leave
import com.ffii.tsms.modules.timesheet.entity.Timesheet
import com.ffii.tsms.modules.timesheet.web.models.LeaveEntry
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.apache.poi.ss.usermodel.*
@@ -23,12 +24,12 @@ import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.*


data class DayInfo(val date: String?, val weekday: String?)
@Service
open class ReportService (
open class ReportService(
private val jdbcDao: JdbcDao,
)
{
) {
private val logger: Log = LogFactory.getLog(javaClass)
private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd")
private val FORMATTED_TODAY = LocalDate.now().format(DATE_FORMATTER)
@@ -97,8 +98,13 @@ open class ReportService (

return outputStream.toByteArray()
}

@Throws(IOException::class)
fun generateProjectCashFlowReport(project: Project, invoices: List<Invoice>, timesheets: List<Timesheet>): ByteArray {
fun generateProjectCashFlowReport(
project: Project,
invoices: List<Invoice>,
timesheets: List<Timesheet>
): ByteArray {
// Generate the Excel report with query results
val workbook: Workbook = createProjectCashFlowReport(project, invoices, timesheets, PROJECT_CASH_FLOW_REPORT)

@@ -111,9 +117,22 @@ open class ReportService (
}

@Throws(IOException::class)
fun generateStaffMonthlyWorkHourAnalysisReport(month: LocalDate, staff: Staff, timesheets: List<Timesheet>, leaves: List<Leave>, projectList: List<String>): ByteArray {
fun generateStaffMonthlyWorkHourAnalysisReport(
month: LocalDate,
staff: Staff,
timesheets: List<Timesheet>,
leaves: List<Leave>,
projectList: List<String>
): ByteArray {
// Generate the Excel report with query results
val workbook: Workbook = createStaffMonthlyWorkHourAnalysisReport(month, staff, timesheets, leaves, projectList, MONTHLY_WORK_HOURS_ANALYSIS_REPORT)
val workbook: Workbook = createStaffMonthlyWorkHourAnalysisReport(
month,
staff,
timesheets,
leaves,
projectList,
MONTHLY_WORK_HOURS_ANALYSIS_REPORT
)

// Write the workbook to a ByteArrayOutputStream
val outputStream: ByteArrayOutputStream = ByteArrayOutputStream()
@@ -137,7 +156,7 @@ open class ReportService (
}

@Throws(IOException::class)
fun generateLateStartReport(project: Project): ByteArray {
fun generateLateStartReport(project: Project?): ByteArray {
val workbook: Workbook = createLateStartReport(project,LATE_START_REPORT)
val outputStream: ByteArrayOutputStream = ByteArrayOutputStream()
workbook.write(outputStream)
@@ -427,42 +446,42 @@ open class ReportService (
val sheet: Sheet = workbook.getSheetAt(0)

// accounting style + comma style
val accountingStyle = workbook.createDataFormat() .getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)")
val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)")

var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field
var columnIndex = 2
sheet.getRow(rowIndex).getCell(columnIndex).apply {
sheet.getRow(rowIndex).createCell(columnIndex).apply {
setCellValue(FORMATTED_TODAY)
}

rowIndex = 2
sheet.getRow(rowIndex).getCell(columnIndex).apply {
sheet.getRow(rowIndex).createCell(columnIndex).apply {
setCellValue(project.code)
}

rowIndex = 3
sheet.getRow(rowIndex).getCell(columnIndex).apply {
sheet.getRow(rowIndex).createCell(columnIndex).apply {
setCellValue(project.name)
}

rowIndex = 4
sheet.getRow(rowIndex).getCell(columnIndex).apply {
sheet.getRow(rowIndex).createCell(columnIndex).apply {
setCellValue(if (project.customer?.name == null) "N/A" else project.customer?.name)
}

rowIndex = 5
sheet.getRow(rowIndex).getCell(columnIndex).apply {
sheet.getRow(rowIndex).createCell(columnIndex).apply {
setCellValue(if (project.teamLead?.team?.name == null) "N/A" else project.teamLead?.team?.name)
}

rowIndex = 9
sheet.getRow(rowIndex).apply {
getCell(1).apply {
createCell(1).apply {
setCellValue(project.expectedTotalFee!!)
cellStyle.dataFormat = accountingStyle
}

getCell(2).apply {
createCell(2).apply {
setCellValue(project.expectedTotalFee!! / 0.8)
cellStyle.dataFormat = accountingStyle
}
@@ -470,15 +489,17 @@ open class ReportService (

rowIndex = 10
val actualIncome = invoices.sumOf { invoice -> invoice.paidAmount!! }
val actualExpenditure = timesheets.sumOf { timesheet -> timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed ?: 0.0)) }
val actualExpenditure = timesheets.sumOf { timesheet ->
timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed
?: 0.0))
}
sheet.getRow(rowIndex).apply {
getCell(1).apply {
// TODO: Replace by actual expenditure
createCell(1).apply {
setCellValue(actualExpenditure)
cellStyle.dataFormat = accountingStyle
}

getCell(2).apply {
createCell(2).apply {
setCellValue(actualIncome.toDouble())
cellStyle.dataFormat = accountingStyle
}
@@ -486,102 +507,137 @@ open class ReportService (

rowIndex = 11
sheet.getRow(rowIndex).apply {
getCell(1).apply {
// TODO: Replace by actual expenditure
createCell(1).apply {
cellFormula = "B10-B11"
cellStyle.dataFormat = accountingStyle
}

getCell(2).apply {
createCell(2).apply {
cellFormula = "C10-C11"
cellStyle.dataFormat = accountingStyle
}
}

// TODO: Add expenditure
rowIndex = 15
val combinedResults = (invoices.map { it.receiptDate } + timesheets.map { it.recordDate }).filterNotNull().sortedBy { it }
logger.info("combinedResults-------------- $combinedResults")
invoices.forEach{
logger.info("Invoice--------- $it. \n")
}


val dateFormatter = DateTimeFormatter.ofPattern("MMM YYYY")
combinedResults.forEach { result: LocalDate ->
val invoice = invoices.find { invoice: Invoice -> invoice.receiptDate == result }
val timesheet = timesheets.find { timesheet: Timesheet -> timesheet.recordDate == result}

logger.info("result--------------: $result")
if (invoice != null) {
sheet.getRow(rowIndex++)?.apply {

logger.info("INVOICE NOT NULL--------------:")
logger.info("getCell(0)--------------: ${getCell(0)}")
logger.info("dateFormatter--------------: $dateFormatter")
logger.info("result.format--------------: ${result.format(dateFormatter)}")
getCell(0)?.apply {
setCellValue(result.format(dateFormatter).toString())
val combinedResults =
(invoices.map { it.receiptDate } + timesheets.map { it.recordDate }).filterNotNull().sortedBy { it }
.map { it.format(dateFormatter) }.distinct()
val groupedTimesheets = timesheets.sortedBy { it.recordDate }
.groupBy { timesheetEntry -> timesheetEntry.recordDate?.format(dateFormatter).toString() }
.mapValues { (_, timesheetEntries) ->
timesheetEntries.map { timesheet ->
if (timesheet.normalConsumed != null) {
timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0).times(timesheet.staff!!.salary.hourlyRate.toDouble())
} else if (timesheet.otConsumed != null) {
timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0).times(timesheet.staff!!.salary.hourlyRate.toDouble())
} else {
0.0
}
}
}

getCell(1)?.apply {
setCellValue(0.0)
cellStyle.dataFormat = accountingStyle
}
val groupedInvoices = invoices.sortedBy { it.receiptDate }.filter { it.paidAmount != null }
.groupBy { invoiceEntry -> invoiceEntry.invoiceDate?.format(dateFormatter).toString() }
.mapValues { (_, invoiceEntries) ->
invoiceEntries.map { invoice ->
mapOf(
"paidAmount" to invoice.paidAmount?.toDouble(),
"description" to invoice.milestonePayment?.description
)
}
}

logger.info("invoice.paidAmount------------: ${invoice.paidAmount}")
logger.info("invoice.paidAmount------------: ${invoice.paidAmount!!.toDouble()}")
getCell(2)?.apply {
setCellValue(invoice.paidAmount!!.toDouble())
cellStyle.dataFormat = accountingStyle
}
groupedTimesheets.entries.forEach { (key, value) ->
logger.info("key: $key")
logger.info("value: " + value.sumOf { it })
}

getCell(3)?.apply {
val lastRow = rowIndex - 1
if (lastRow == 15) {
cellFormula = "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString())
} else {
cellFormula = "IF(B{currentRow}>0,D{lastRow}-B{currentRow},D{lastRow}+C{currentRow})".replace("{currentRow}", rowIndex.toString()).replace("{lastRow}", lastRow.toString())
groupedInvoices.entries.forEach { (key, value) ->
logger.info("key: $key")
logger.info("value: " + value)
groupedInvoices[key]!!.forEachIndexed { index, invoice ->
logger.info("index: $index")
logger.info("invoice: $invoice")
invoice.get("paidAmount")
}
}

combinedResults.forEach { result: String ->

if (groupedInvoices.containsKey(result)) {
groupedInvoices[result]!!.forEachIndexed { _, invoice ->
sheet.getRow(rowIndex++).apply {
createCell(0).apply {
setCellValue(result)
}

createCell(1).apply {
setCellValue(0.0)
cellStyle.dataFormat = accountingStyle
}

createCell(2).apply {
setCellValue(invoice["paidAmount"] as Double)
cellStyle.dataFormat = accountingStyle
}

createCell(3).apply {
val lastRow = rowIndex - 1
if (lastRow == 15) {
cellFormula =
"C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString())
} else {
cellFormula =
"IF(B{currentRow}>0,D{lastRow}-B{currentRow},D{lastRow}+C{currentRow})".replace(
"{currentRow}",
rowIndex.toString()
).replace("{lastRow}", lastRow.toString())
}
cellStyle.dataFormat = accountingStyle
}
cellStyle.dataFormat = accountingStyle
}

getCell(4)?.apply {
setCellValue(invoice.milestonePayment!!.description!!)
createCell(4)?.apply {
setCellValue(invoice["description"].toString())
}
}
}
}

if (timesheet != null) {
sheet.getRow(rowIndex++)?.apply {
if (groupedTimesheets.containsKey(result)) {
sheet.getRow(rowIndex++).apply {

logger.info("TIMESHEET NOT NULL--------------:")
logger.info("getCell(0)--------------: ${getCell(0)}")
logger.info("dateFormatter--------------: $dateFormatter")
logger.info("result.format--------------: ${result.format(dateFormatter)}")
getCell(0)?.apply {
createCell(0).apply {
setCellValue(result.format(dateFormatter))
}

getCell(1)?.apply {
setCellValue(timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed ?: 0.0)))
createCell(1).apply {
setCellValue(groupedTimesheets[result]!!.sum())
cellStyle.dataFormat = accountingStyle
}

getCell(2)?.apply {
createCell(2).apply {
setCellValue(0.0)
cellStyle.dataFormat = accountingStyle
}

getCell(3)?.apply {
createCell(3).apply {
val lastRow = rowIndex - 1
if (lastRow == 15) {
cellFormula = "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString())
} else {
cellFormula = "IF(B{currentRow}>0,D{lastRow}-B{currentRow},D{lastRow}+C{currentRow})".replace("{currentRow}", rowIndex.toString()).replace("{lastRow}", lastRow.toString())
cellFormula =
"IF(B{currentRow}>0,D{lastRow}-B{currentRow},D{lastRow}+C{currentRow})".replace(
"{currentRow}",
rowIndex.toString()
).replace("{lastRow}", lastRow.toString())
}
cellStyle.dataFormat = accountingStyle
}

getCell(4)?.apply {
createCell(4).apply {
setCellValue("Monthly Manpower Expenditure")
}
}
@@ -615,6 +671,9 @@ open class ReportService (
templatePath: String,
): Workbook {
// val yearMonth = YearMonth.of(2022, 5) // May 2022
println("t $timesheets")
println("l $leaves")
println("p $projectList")
val resource = ClassPathResource(templatePath)
val templateInputStream = resource.inputStream
val workbook: Workbook = XSSFWorkbook(templateInputStream)
@@ -643,205 +702,194 @@ open class ReportService (
var columnSize = 0

var dayInt = 0
// tempCell = tempRow.createCell(columnIndex)
sheet.getRow(rowIndex).getCell(columnIndex).apply {
setCellValue(FORMATTED_TODAY)
}
println(sheet.getRow(1).getCell(2))
var tempCell: Cell

tempCell = sheet.getRow(rowIndex).createCell(columnIndex)
tempCell.setCellValue(FORMATTED_TODAY)

rowIndex = 2
sheet.getRow(rowIndex).getCell(columnIndex).apply {
setCellValue(month)
cellStyle.dataFormat = monthStyle
}
tempCell = sheet.getRow(rowIndex).createCell(columnIndex)
tempCell.setCellValue(month)
tempCell.cellStyle.dataFormat = monthStyle

rowIndex = 3
sheet.getRow(rowIndex).getCell(columnIndex).apply {
setCellValue(staff.name)
}
tempCell. sheet.getRow(rowIndex).createCell(columnIndex)
tempCell.setCellValue(staff.name)

rowIndex = 4
sheet.getRow(rowIndex).getCell(columnIndex).apply {
setCellValue(staff.team.name)
}
tempCell.sheet.getRow(rowIndex).createCell(columnIndex)
tempCell.setCellValue(staff.team.name)

rowIndex = 5
sheet.getRow(rowIndex).getCell(columnIndex).apply {
setCellValue(staff.grade.code)
}
tempCell = sheet.getRow(rowIndex).createCell(columnIndex)
tempCell.setCellValue(staff.grade.code)

val DoubleBorderBottom: MutableMap<String?, Any?> = mutableMapOf()
DoubleBorderBottom["borderTop"] = BorderStyle.THIN
DoubleBorderBottom["borderBottom"] = BorderStyle.DOUBLE

val ThinBorderBottom: MutableMap<String?, Any?> = mutableMapOf()
ThinBorderBottom["borderBottom"] = BorderStyle.THIN
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
rowIndex = 7
daysOfMonth.forEach { dayInfo ->
rowIndex++
rowSize++
sheet.getRow(rowIndex).getCell(0).apply {
setCellValue(dayInfo.date)
cellStyle.dataFormat = dateStyle
cellStyle.setFont(boldFont)
}
sheet.getRow(rowIndex).getCell(1).apply {
setCellValue(dayInfo.weekday)
cellStyle.setFont(boldFont)
}
tempCell = sheet.getRow(rowIndex).createCell(0)
tempCell.setCellValue(dayInfo.date)
tempCell.cellStyle.dataFormat = dateStyle
tempCell.cellStyle.setFont(boldFont)
// cellStyle.alignment = HorizontalAlignment.LEFT
tempCell = sheet.getRow(rowIndex).createCell(1)
tempCell.setCellValue(dayInfo.weekday)
tempCell.cellStyle.setFont(boldFont)
// cellStyle.alignment = HorizontalAlignment.LEFT
}

rowIndex += 1
sheet.getRow(rowIndex).getCell(0).apply {
setCellValue("Sub-total")
cellStyle.setFont(boldFont)
// cellStyle.borderTop = BorderStyle.THIN
// cellStyle.borderBottom = BorderStyle.DOUBLE
cellStyle.alignment = HorizontalAlignment.CENTER
}
tempCell = sheet.getRow(rowIndex).createCell(0)
sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1))
//
tempCell.setCellValue("Sub-total")
tempCell.cellStyle.setFont(boldFont)
CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER)
CellUtil.setCellStyleProperties(tempCell, DoubleBorderBottom)
tempCell = sheet.getRow(rowIndex).createCell(1)
CellUtil.setCellStyleProperties(tempCell, DoubleBorderBottom)

rowIndex += 1
sheet.getRow(rowIndex).getCell(0).apply {
setCellValue("Total Normal Hours [A]")
// cellStyle.setFont(boldFont)
cellStyle.alignment = HorizontalAlignment.CENTER
}
tempCell = sheet.getRow(rowIndex).createCell(0)
tempCell.setCellValue("Total Normal Hours [A]")
CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER)

var normalConsumed = 0.0
var otConsumed = 0.0
var leaveHours = 0.0
if (timesheets.isNotEmpty()) {
timesheets.forEach { t ->
normalConsumed += t.normalConsumed!!
otConsumed += t.otConsumed!!
otConsumed += t.otConsumed ?: 0.0
}
}
sheet.getRow(rowIndex).getCell(2).apply {
setCellValue(normalConsumed)
cellStyle.alignment = HorizontalAlignment.CENTER
cellStyle.dataFormat = accountingStyle
}
tempCell = sheet.getRow(rowIndex).createCell(2)
tempCell.setCellValue(normalConsumed)
tempCell.cellStyle.dataFormat = accountingStyle

sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1))
//
rowIndex += 1
sheet.getRow(rowIndex).getCell(0).apply {
setCellValue("Total Other Hours [B]")
cellStyle.setFont(boldFont)
cellStyle.alignment = HorizontalAlignment.CENTER
}
sheet.getRow(rowIndex).getCell(2).apply {
setCellValue(otConsumed)
cellStyle.alignment = HorizontalAlignment.CENTER
cellStyle.dataFormat = accountingStyle
}
tempCell = sheet.getRow(rowIndex).createCell(0)
tempCell.setCellValue("Total Other Hours [B]")
CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER)
tempCell.cellStyle.setFont(boldFont)
tempCell = sheet.getRow(rowIndex).createCell(2)
tempCell.setCellValue(otConsumed)
tempCell.cellStyle.dataFormat = accountingStyle

sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1))

sheet.addMergedRegion(CellRangeAddress(rowIndex, rowIndex, 0, 1))

// Total Leave Hours
rowIndex += 1
sheet.getRow(rowIndex).getCell(0).apply {
setCellValue("Total Leave Hours")
cellStyle.alignment = HorizontalAlignment.CENTER
}
tempCell = sheet.getRow(rowIndex).createCell(0)
tempCell.setCellValue("Total Leave Hours")
CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER)
sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1))
if (leaves.isNotEmpty()) {
leaves.forEach { l ->
leaveHours += l.leaveHours!!
}
}
sheet.getRow(rowIndex).getCell(2).apply {
setCellValue(leaveHours)
cellStyle.dataFormat = accountingStyle
}
tempCell = sheet.getRow(rowIndex).createCell(2)
tempCell.setCellValue(leaveHours)
tempCell.cellStyle.dataFormat = accountingStyle

// Total Spent Manhours
rowIndex += 1
sheet.getRow(rowIndex).getCell(0).apply {
setCellValue("Total Spent Manhours [A+B]")
cellStyle.setFont(boldFont)
cellStyle.alignment = HorizontalAlignment.CENTER
// cellStyle.borderTop = BorderStyle.THIN
// cellStyle.borderBottom = BorderStyle.DOUBLE
}
sheet.getRow(rowIndex).getCell(2).apply {
cellFormula = "C${rowIndex-2}+C${rowIndex-1}"
cellStyle.dataFormat = accountingStyle
// cellStyle.borderTop = BorderStyle.THIN
// cellStyle.borderBottom = BorderStyle.DOUBLE
}
tempCell = sheet.getRow(rowIndex).createCell(0)
tempCell.setCellValue("Total Spent Manhours [A+B]")
tempCell.cellStyle.setFont(boldFont)
CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER)
CellUtil.setCellStyleProperties(tempCell, DoubleBorderBottom)
tempCell = sheet.getRow(rowIndex).createCell(1)
CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER)
CellUtil.setCellStyleProperties(tempCell, DoubleBorderBottom)

tempCell = sheet.getRow(rowIndex).createCell(2)
tempCell.cellFormula = "C${rowIndex-2}+C${rowIndex-1}"
tempCell.cellStyle.dataFormat = accountingStyle
CellUtil.setCellStyleProperties(tempCell, DoubleBorderBottom)

sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1))
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
rowIndex = 7
columnIndex = 2
projectList.forEachIndexed { index, title ->
sheet.getRow(7).getCell(columnIndex + index).apply {
setCellValue(title)
}
tempCell = sheet.getRow(7).createCell(columnIndex + index)
tempCell.setCellValue(title)
CellUtil.setAlignment(tempCell, HorizontalAlignment.RIGHT)
CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom)
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (timesheets.isNotEmpty()) {
projectList.forEach { _ ->
for (i in 0 until rowSize) {
tempCell = sheet.getRow(8 + i).createCell(columnIndex)
tempCell.setCellValue(0.0)
tempCell.cellStyle.dataFormat = accountingStyle
}
timesheets.forEach { timesheet ->
dayInt = timesheet.recordDate!!.dayOfMonth
sheet.getRow(dayInt.plus(7)).getCell(columnIndex).apply {
setCellValue(timesheet.normalConsumed!!)
}
tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex)
tempCell.setCellValue(timesheet.normalConsumed!!)
}
columnIndex++
}
}
// dates
if (leaves.isNotEmpty()) {
leaves.forEach { leave ->
dayInt = leave.recordDate!!.dayOfMonth
sheet.getRow(dayInt.plus(7)).getCell(columnIndex).apply {
setCellValue(leave.leaveHours!!)
for (i in 0 until rowSize) {
tempCell = sheet.getRow(8 + i).createCell(columnIndex)
tempCell.setCellValue(0.0)
tempCell.cellStyle.dataFormat = accountingStyle

}
dayInt = leave.recordDate!!.dayOfMonth
tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex)
tempCell.setCellValue(leave.leaveHours!!)

}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
sheet.getRow(rowIndex).apply {
getCell(columnIndex).setCellValue("Leave Hours")
getCell(columnIndex).cellStyle.alignment = HorizontalAlignment.CENTER
///////////////////////////////////////////////////////// Leave Hours ////////////////////////////////////////////////////////////////////
tempCell = sheet.getRow(rowIndex).createCell(columnIndex)
tempCell.setCellValue("Leave Hours")
CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom)

columnIndex += 1
tempCell = sheet.getRow(rowIndex).createCell(columnIndex)
tempCell.setCellValue("Daily Manhour Spent\n(Excluding Leave Hours)")
CellUtil.setCellStyleProperties(tempCell, ThinBorderBottom)

columnIndex += 1
getCell(columnIndex).setCellValue("Daily Manhour Spent\n" + "(Excluding Leave Hours)")
getCell(columnIndex).cellStyle.alignment = HorizontalAlignment.CENTER
columnSize = columnIndex
}
sheet.addMergedRegion(CellRangeAddress(6,6 , 2, columnIndex))
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for (i in 2 until columnIndex) {
for (k in 0 until rowSize) {
sheet.getRow(8+k).getCell(i).apply {
setCellValue(0.0)
cellStyle.dataFormat = accountingStyle
}
}
}

rowIndex = 8
if (sheet.getRow(rowIndex - 1).getCell(2).stringCellValue != "Leave Hours") {
// cal daily spent manhour
for (i in 0 until rowSize) {
val cell = sheet.getRow(rowIndex)?.getCell(columnIndex) ?: sheet.getRow(rowIndex + i)?.createCell(columnIndex)
cell?.cellFormula = "SUM(${getColumnAlphabet(2)}${rowIndex+1}:${getColumnAlphabet(columnIndex)}${rowIndex+1})" // should columnIndex - 2
rowIndex++
}
// cal subtotal
for (i in 0 until columnSize) { // minus last col
val cell = sheet.getRow(rowIndex)?.getCell(2) ?: sheet.getRow(rowIndex)?.createCell(2)
cell?.cellFormula = "SUM(${getColumnAlphabet(2)}${rowIndex}:${getColumnAlphabet(columnIndex + i)}${rowIndex})"
cell?.cellStyle?.dataFormat = accountingStyle
cell?.cellStyle?.setFont(boldFont)
// cell?.cellStyle?.borderTop = BorderStyle.THIN
// cell?.cellStyle?.borderBottom = BorderStyle.DOUBLE
}
} else { // just for preview when no data
// cal daily spent manhour
for (i in 0 until rowSize) {
val cell = sheet.getRow(rowIndex)?.getCell(columnIndex) ?: sheet.getRow(rowIndex + i)?.createCell(columnIndex)
cell?.setCellValue("daily spent manhour")
tempCell = sheet.getRow(rowIndex).createCell(columnIndex)
tempCell.cellFormula = "SUM(${getColumnAlphabet(2)}${rowIndex+1}:${getColumnAlphabet(columnIndex - 2)}${rowIndex+1})" // should columnIndex - 2
rowIndex++
}
// cal subtotal
for (i in 0 until columnSize) { // minus last col
val cell = sheet.getRow(rowIndex)?.getCell(2) ?: sheet.getRow(rowIndex)?.createCell(2)
cell?.setCellValue("testing subtotal")
cell?.cellStyle?.dataFormat = accountingStyle
cell?.cellStyle?.setFont(boldFont)
// cell?.cellStyle?.borderTop = BorderStyle.THIN
// cell?.cellStyle?.borderBottom = BorderStyle.DOUBLE
println(rowIndex)
for (i in 0 until columnSize - 2) { // minus last col
tempCell = sheet.getRow(rowIndex).createCell(2 + i)
tempCell.cellFormula = "SUM(${getColumnAlphabet(2 + i)}9:${getColumnAlphabet(2 + i)}${rowIndex})"
tempCell.cellStyle.dataFormat = accountingStyle
tempCell.cellStyle.setFont(boldFont)
CellUtil.setCellStyleProperties(tempCell, DoubleBorderBottom)
}
}
return workbook
@@ -850,7 +898,7 @@ open class ReportService (

@Throws(IOException::class)
private fun createSalaryList(
salarys : List<Salary>,
salarys: List<Salary>,
templatePath: String,
): Workbook {

@@ -888,11 +936,10 @@ open class ReportService (
}

private fun createLateStartReport(
project: Project,
project: Project?,
templatePath: String
):Workbook{

project
val resource = ClassPathResource(templatePath)
val templateInputStream = resource.inputStream
val workbook: Workbook = XSSFWorkbook(templateInputStream)
@@ -902,7 +949,20 @@ open class ReportService (
val formattedToday = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))
val dateCell = sheet.getRow(1)?.getCell(2) ?: sheet.getRow(1).createCell(2)
dateCell.setCellValue(formattedToday)

// // Start populating project data starting at row 7
// val startRow = 6 // 0-based index for row 7
// val projectDataRow = sheet.createRow(startRow)

// // Populate the row with project data
// projectDataRow.createCell(1).setCellValue(project.code) // Column B
// projectDataRow.createCell(2).setCellValue(project.name) // Column C
// projectDataRow.createCell(3).setCellValue(project.teamLead?.name) // Column D
// projectDataRow.createCell(4).setCellValue(project.custLeadName) // Column E
// projectDataRow.createCell(5).setCellValue(
// project.planStart?.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) ?: "N/A" // Column F
// )

// Styling for cell A1: Font size 16 and bold
val headerFont = workbook.createFont().apply {
bold = true
@@ -914,7 +974,7 @@ open class ReportService (
val headerCell = sheet.getRow(0)?.getCell(0) ?: sheet.getRow(0).createCell(0)
headerCell.cellStyle = headerCellStyle
headerCell.setCellValue("Report Title")
// Apply styles from A2 to A4 (bold)
val boldFont = workbook.createFont().apply { bold = true }
val boldCellStyle = workbook.createCellStyle().apply { setFont(boldFont) }
@@ -923,7 +983,7 @@ open class ReportService (
val cell = row?.getCell(0) ?: row.createCell(0)
cell.cellStyle = boldCellStyle
}
// Apply styles from A6 to J6 (bold, bottom border, center alignment)
val styleA6ToJ6 = workbook.createCellStyle().apply {
setFont(boldFont)
@@ -936,7 +996,7 @@ open class ReportService (
val cell = row?.getCell(cellAddress.column) ?: row.createCell(cellAddress.column)
cell.cellStyle = styleA6ToJ6
}
// Setting column widths dynamically based on content length (example logic)
val maxContentWidths = IntArray(10) { 8 } // Initial widths for A to J
for (rowIndex in 0..sheet.lastRowNum) {
@@ -954,20 +1014,20 @@ open class ReportService (
for (colIndex in 0..9) {
sheet.setColumnWidth(colIndex, (maxContentWidths[colIndex] + 2) * 256) // Set the width for each column
}
return workbook
}

open fun getFinancialStatus(projectId: Long?): List<Map<String, Any>> {
val sql = StringBuilder(
"with cte_invoice as (select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount "
+ " from invoice i"
+ " left join project p on p.code = i.projectCode"
+ " group by p.code"
"with cte_invoice as (select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount"
+ "from invoice i"
+ "left join project p on p.code = i.projectCode"
+ "group by p.code"
+ ")"
+ " Select p.code, p.description, c.name as client, t2.name as teamLead, p.planStart , p.planEnd , p.expectedTotalFee ,"
+ " s.name as staff , IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate,"
+ " IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount "
+ " Select p.code, p.description, c.name, t2.name, p.planStart , p.planEnd , p.expectedTotalFee ,"
+ " s.name , IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate,"
+ " cte_i.sumIssuedAmount, cte_i.sumPaidAmount"
+ " from timesheet t"
+ " left join project_task pt on pt.id = t.projectTaskId"
+ " left join project p ON p.id = pt.project_id"
@@ -978,7 +1038,7 @@ open class ReportService (
+ " left join cte_invoice cte_i on cte_i.code = p.code"
)

if (projectId!! > 0){
if (projectId!! > 0) {
sql.append(" where p.id = :projectId ")
}
sql.append(" order by p.code")


+ 2
- 1
src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt ファイルの表示

@@ -84,6 +84,7 @@ class ReportController(
val projects = timesheetRepository.findDistinctProjectTaskByStaffAndRecordDateBetweenOrderByRecordDate(staff, thisMonth, nextMonth)
val projectList: List<String> = projects.map { p -> "${p.projectTask!!.project!!.code}\n ${p.projectTask!!.project!!.name}" }


val reportResult: ByteArray = excelReportService.generateStaffMonthlyWorkHourAnalysisReport(thisMonth, staff, timesheets, leaves, projectList)
// val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
return ResponseEntity.ok()
@@ -100,7 +101,7 @@ class ReportController(

@PostMapping("/downloadLateStartReport")
fun downloadLateStartReport(): ResponseEntity<ByteArrayResource> {
val reportBytes = excelReportService.generateLateStartReport(Project())
val reportBytes = excelReportService.generateLateStartReport(null)
val headers = HttpHeaders()
headers.add("Content-Disposition", "attachment; filename=Late_Start_Report_${LocalDate.now()}.xlsx")



+ 23
- 1
src/main/java/com/ffii/tsms/modules/user/web/UserController.java ファイルの表示

@@ -131,7 +131,6 @@ public class UserController{
// })
@PatchMapping("/change-password")
@ResponseStatus(HttpStatus.NO_CONTENT)
// @PreAuthorize("hasAuthority('MAINTAIN_USER')")
public void changePassword(@RequestBody @Valid ChangePwdReq req) {
long id = SecurityUtils.getUser().get().getId();
User instance = userService.find(id).orElseThrow(NotFoundException::new);
@@ -151,6 +150,26 @@ public class UserController{
userService.save(instance);
}

@PatchMapping("/admin-change-password")
@ResponseStatus(HttpStatus.NO_CONTENT)
@PreAuthorize("hasAuthority('MAINTAIN_USER')")
public void adminChangePassword(@RequestBody @Valid ChangePwdReq req) {
long id = req.getId();
User instance = userService.find(id).orElseThrow(NotFoundException::new);

logger.info("TEST req: "+req.getPassword());
logger.info("TEST instance: "+instance.getPassword());
// if (!passwordEncoder.matches(req.getPassword(), instance.getPassword())) {
// throw new BadRequestException();
// }
PasswordRule rule = new PasswordRule(settingsService);
if (!PasswordUtils.checkPwd(req.getNewPassword(), rule)) {
throw new UnprocessableEntityException(ErrorCodes.USER_WRONG_NEW_PWD);
}
instance.setPassword(passwordEncoder.encode(req.getNewPassword()));
userService.save(instance);
}

// @Operation(summary = "reset password", responses = {
// @ApiResponse(responseCode = "204"),
// @ApiResponse(responseCode = "404", content = @Content),
@@ -170,11 +189,14 @@ public class UserController{
}

public static class ChangePwdReq {
private Long id;
@NotBlank
private String password;
@NotBlank
private String newPassword;

public Long getId() { return id; }
public Long setId(Long id) { return this.id = id; }
public String getPassword() {
return password;
}


読み込み中…
キャンセル
保存