|
|
@@ -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") |
|
|
|