Procházet zdrojové kódy

Update Salary export

Update PNL Report, show started Project till now
Update Fincal Status, add projected CPI
Update Invoice List API
tags/Baseline_30082024_BACKEND_UAT
MSI\2Fi před 1 rokem
rodič
revize
9a49dcf847
11 změnil soubory, kde provedl 357 přidání a 21 odebrání
  1. +4
    -1
      src/main/java/com/ffii/tsms/modules/project/entity/InvoiceRepository.kt
  2. +3
    -0
      src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt
  3. +21
    -0
      src/main/java/com/ffii/tsms/modules/project/entity/projections/ImportInvoiceInfo.kt
  4. +1
    -0
      src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoiceInfo.kt
  5. +12
    -0
      src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectPlanStartEnd.kt
  6. +157
    -3
      src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt
  7. +25
    -0
      src/main/java/com/ffii/tsms/modules/project/web/InvoiceController.kt
  8. +6
    -0
      src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt
  9. +128
    -17
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  10. binární
      src/main/resources/templates/report/EX01_Financial Status Report.xlsx
  11. binární
      src/main/resources/templates/report/Salary Template.xlsx

+ 4
- 1
src/main/java/com/ffii/tsms/modules/project/entity/InvoiceRepository.kt Zobrazit soubor

@@ -1,13 +1,16 @@
package com.ffii.tsms.modules.project.entity package com.ffii.tsms.modules.project.entity


import com.ffii.core.support.AbstractRepository import com.ffii.core.support.AbstractRepository
import com.ffii.tsms.modules.project.entity.projections.ImportInvoiceInfo
import com.ffii.tsms.modules.project.entity.projections.InvoiceInfo import com.ffii.tsms.modules.project.entity.projections.InvoiceInfo


interface InvoiceRepository : AbstractRepository<Invoice, Long> { interface InvoiceRepository : AbstractRepository<Invoice, Long> {


fun findAllByPaidAmountIsNotNullAndMilestonePaymentIn(milestonePayment: List<MilestonePayment>): List<Invoice> fun findAllByPaidAmountIsNotNullAndMilestonePaymentIn(milestonePayment: List<MilestonePayment>): List<Invoice>


fun findInvoiceInfoBy(): List<InvoiceInfo>
fun findInvoiceInfoByAndDeletedFalse(): List<InvoiceInfo>

fun findImportInvoiceInfoByAndDeletedFalse(): List<ImportInvoiceInfo>


fun findInvoiceInfoByPaidAmountIsNotNull(): List<InvoiceInfo> fun findInvoiceInfoByPaidAmountIsNotNull(): List<InvoiceInfo>




+ 3
- 0
src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt Zobrazit soubor

@@ -5,6 +5,7 @@ import com.ffii.tsms.modules.data.entity.Customer
import com.ffii.tsms.modules.data.entity.Staff import com.ffii.tsms.modules.data.entity.Staff
import com.ffii.tsms.modules.project.entity.projections.InvoiceInfoSearchInfo import com.ffii.tsms.modules.project.entity.projections.InvoiceInfoSearchInfo
import com.ffii.tsms.modules.project.entity.projections.InvoiceSearchInfo import com.ffii.tsms.modules.project.entity.projections.InvoiceSearchInfo
import com.ffii.tsms.modules.project.entity.projections.ProjectPlanStartEnd
import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo
import org.springframework.data.jpa.repository.Query import org.springframework.data.jpa.repository.Query
import org.springframework.lang.Nullable import org.springframework.lang.Nullable
@@ -36,4 +37,6 @@ interface ProjectRepository : AbstractRepository<Project, Long> {
fun findAllByTeamLeadAndCustomer(teamLead: Staff, customer: Customer): List<Project> fun findAllByTeamLeadAndCustomer(teamLead: Staff, customer: Customer): List<Project>


fun findByCode(code: String): Project? fun findByCode(code: String): Project?

fun findProjectPlanStartEndByIdAndDeletedFalse(id: Long): ProjectPlanStartEnd
} }

+ 21
- 0
src/main/java/com/ffii/tsms/modules/project/entity/projections/ImportInvoiceInfo.kt Zobrazit soubor

@@ -0,0 +1,21 @@
package com.ffii.tsms.modules.project.entity.projections

import org.springframework.beans.factory.annotation.Value
import java.math.BigDecimal
import java.time.LocalDate

interface ImportInvoiceInfo {
val invoiceNo: String?

val projectCode: String?

val invoiceDate: LocalDate?

val receiptDate: LocalDate?

@get:Value("#{target.paidAmount}")
val receivedAmount: BigDecimal?

@get:Value("#{target.issueAmount}")
val issuedAmount: BigDecimal?
}

+ 1
- 0
src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoiceInfo.kt Zobrazit soubor

@@ -3,6 +3,7 @@ package com.ffii.tsms.modules.project.entity.projections
import com.ffii.tsms.modules.project.entity.MilestonePayment import com.ffii.tsms.modules.project.entity.MilestonePayment
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
import org.springframework.cglib.core.Local import org.springframework.cglib.core.Local
import org.springframework.data.jpa.repository.Query
import java.math.BigDecimal import java.math.BigDecimal
import java.time.LocalDate import java.time.LocalDate




+ 12
- 0
src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectPlanStartEnd.kt Zobrazit soubor

@@ -0,0 +1,12 @@
package com.ffii.tsms.modules.project.entity.projections

import java.time.LocalDate
import java.time.YearMonth

interface ProjectPlanStartEnd {
val id: Long?
val planStart: LocalDate?
val planEnd: LocalDate?
val actualStart: LocalDate?
val actualEnd: LocalDate?
}

+ 157
- 3
src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt Zobrazit soubor

@@ -12,16 +12,15 @@ import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo
import com.ffii.tsms.modules.project.web.models.InvoiceResponse import com.ffii.tsms.modules.project.web.models.InvoiceResponse
import net.sf.jasperreports.engine.JasperCompileManager import net.sf.jasperreports.engine.JasperCompileManager
import net.sf.jasperreports.engine.JasperReport import net.sf.jasperreports.engine.JasperReport
import org.apache.poi.ss.usermodel.Cell
import org.apache.poi.ss.usermodel.CellType import org.apache.poi.ss.usermodel.CellType
import org.apache.poi.ss.usermodel.Sheet import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.ss.usermodel.Workbook import org.apache.poi.ss.usermodel.Workbook
import org.springframework.core.io.ClassPathResource import org.springframework.core.io.ClassPathResource
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
import java.io.InputStream import java.io.InputStream
import java.math.BigDecimal import java.math.BigDecimal
import java.time.LocalDate
import java.time.ZoneId import java.time.ZoneId




@@ -325,7 +324,20 @@ open class InvoiceService(
} }


open fun allInvoice(): List<InvoiceInfo>{ open fun allInvoice(): List<InvoiceInfo>{
return invoiceRepository.findInvoiceInfoBy()
return invoiceRepository.findInvoiceInfoByAndDeletedFalse()
}

open fun allInvoiceV3(): List<Map<String, Any>>{
val sql = StringBuilder(
"select i.id, i.invoiceNo, i.projectCode, " +
"p.name as projectName, t.code as team, i.invoiceDate, " +
"i.receiptDate, i.issueAmount , i.paidAmount " +
"from invoice i " +
"left join project p on i.projectCode = p.code " +
"left join team t on t.id = p.teamLead " +
"order by i.invoiceDate "
)
return jdbcDao.queryForList(sql.toString());
} }


open fun allInvoicePaid(): List<InvoiceInfo>{ open fun allInvoicePaid(): List<InvoiceInfo>{
@@ -497,4 +509,146 @@ open class InvoiceService(


return InvoiceResponse(true, "OK", newProjectCodes, emptyRowList, invoicesResult, duplicateItemsInInvoice, ArrayList()) return InvoiceResponse(true, "OK", newProjectCodes, emptyRowList, invoicesResult, duplicateItemsInInvoice, ArrayList())
} }

@Transactional(rollbackFor = [Exception::class])
open fun importInvoices(workbook: Workbook?): InvoiceResponse {
if (workbook == null) {
return InvoiceResponse(false, "No Excel import", ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList()) // if workbook is null
}
val invoiceRecords = repository.findImportInvoiceInfoByAndDeletedFalse()

val importInvoices: MutableList<Map<String, Any>> = mutableListOf();

val sheet: Sheet = workbook.getSheetAt(1)
println(sheet.lastRowNum)
for(i in 3 until sheet.lastRowNum){
val issueYear = ExcelUtils.getCell(sheet, i, convertAlphaToNumber("B")).numericCellValue
val issueMonth = ExcelUtils.getCell(sheet, i, convertAlphaToNumber("C")).numericCellValue
val issueDay = ExcelUtils.getCell(sheet, i, convertAlphaToNumber("D")).numericCellValue
val adjustedYear = if (issueYear < 100) 2000 + issueYear else issueYear
val issueDate = convertToLocalDate(adjustedYear.toInt(), issueMonth.toInt(), issueDay.toInt())

val projectPrefix = getCellValue(sheet, i, convertAlphaToNumber("E") )
val projectNo = getCellValue(sheet, i, convertAlphaToNumber("F") )
val projectCode = "${projectPrefix}-${projectNo}"
println("${i}: ${projectCode}")

val invoicePrefix1 = getCellValueWithBracket(sheet, i, convertAlphaToNumber("G") )
val invoicePrefix2 = getCellValueWithBracket(sheet, i, convertAlphaToNumber("H") )
val invoicePrefix3 = getCellValue(sheet, i, convertAlphaToNumber("I") )
val invoicePrefix4 = getCellValue(sheet, i, convertAlphaToNumber("J") )
// Put the cell into list, if cell is empty not join with "-"
val invoiceNo = listOf(projectCode, invoicePrefix1, invoicePrefix2, invoicePrefix3, invoicePrefix4)
.filter { it.isNotEmpty() }
.joinToString("-")

val issuedAmount = ExcelUtils.getCell(sheet, i, convertAlphaToNumber("M")).numericCellValue
val canceledAmount = ExcelUtils.getCell(sheet, i, convertAlphaToNumber("N")).numericCellValue
val settleYear = ExcelUtils.getCell(sheet, i, convertAlphaToNumber("Q")).numericCellValue
val settleMonth = ExcelUtils.getCell(sheet, i, convertAlphaToNumber("R")).numericCellValue
val settleDay = ExcelUtils.getCell(sheet, i, convertAlphaToNumber("S")).numericCellValue
val adjustedSettleYear = if (settleYear < 100) 2000 + settleYear else settleYear
val settleDate = convertToLocalDate(adjustedSettleYear.toInt(), settleMonth.toInt(), settleDay.toInt())
val receivedAmount = ExcelUtils.getCell(sheet, i, convertAlphaToNumber("T")).numericCellValue

val importInvoice: Map<String, Any> = mapOf(
"invoiceNo" to invoiceNo,
"projectCode" to projectCode,
"issuedAmount" to issuedAmount,
"canceledAmount" to canceledAmount,
"invoiceDate" to issueDate,
"receiptDate" to settleDate,
"receivedAmount" to receivedAmount,
)
importInvoices.add(importInvoice)
}

println("invoiceRecords: $invoiceRecords")
val (intersect, notIntersect) = importInvoices.partition { item ->
invoiceRecords.any { invoice ->
invoice.invoiceNo == item["invoiceNo"] &&
invoice.invoiceDate == item["invoiceDate"] &&
invoice.receiptDate == item["receiptDate"] &&
invoice.receivedAmount == item["receivedAmount"] &&
invoice.issuedAmount == item["issuedAmount"]
}
}

println("interect $intersect")
println("Not Intersect $notIntersect")


return InvoiceResponse(true, "OK", ArrayList(), ArrayList(), ArrayList(), ArrayList(), importInvoices)
}

fun getCellValue(sheet: Sheet, rowIndex: Int, columnIndex: Int): String {
val row = sheet.getRow(rowIndex)
val cell = row.getCell(columnIndex)

return when (cell.cellType) {
CellType.NUMERIC -> cell.numericCellValue.toInt().toString()
CellType.STRING -> cell.stringCellValue
// CellType.BOOLEAN -> cell.booleanCellValue
// CellType.FORMULA -> cell.setCellFormula(cell.cellFormula)
// CellType.BLANK -> null
else -> cell.toString()
}
}

fun getCellValueWithBracket(sheet: Sheet, rowIndex: Int, columnIndex: Int): String {
val row = sheet.getRow(rowIndex)
val cell = row.getCell(columnIndex)

return when (cell.cellType) {
CellType.NUMERIC -> "(${cell.numericCellValue.toInt()})"
CellType.STRING -> cell.stringCellValue
// CellType.BOOLEAN -> cell.booleanCellValue
// CellType.FORMULA -> cell.setCellFormula(cell.cellFormula)
// CellType.BLANK -> null
else -> cell.toString()
}
}

fun convertToLocalDate(year: Int, month: Int, day: Int): String {
// Check if the values are valid
if (year < 0 || month < 1 || month > 12 || day < 1 || day > 31) {
// throw IllegalArgumentException("Invalid date values: year=$year, month=$month, day=$day")
return "-"
}

// Adjust the year to be 2023 if the input year is 23
val adjustedYear = if (year < 100) 2000 + year else year

// Create and return the LocalDate object
return LocalDate.of(adjustedYear, month, day).toString()
}

fun convertAlphaToNumber(input: String): Int {
// Convert the input string to uppercase
val uppercaseInput = input.uppercase()

// Initialize the result to 0
var result = 0

// Iterate through each character in the input string
for (i in uppercaseInput.indices) {
// Get the current character
val c = uppercaseInput[i]

// Check if the character is a letter
if (c.isLetter()) {
// Calculate the numeric value of the letter
val value = c - 'A'

// Add the value to the result
result = result * 26 + value
} else {
// If the character is not a letter, throw an exception
throw IllegalArgumentException("Input string must contain only alphabetic characters: $c")
}
}

return result
}

} }

+ 25
- 0
src/main/java/com/ffii/tsms/modules/project/web/InvoiceController.kt Zobrazit soubor

@@ -75,6 +75,11 @@ class InvoiceController(
return invoiceService.allInvoice() return invoiceService.allInvoice()
} }


@GetMapping("/v3/allInvoices")
fun allInvoice_v3(): List<Map<String, Any>> {
return invoiceService.allInvoiceV3()
}

@GetMapping("/v2/allInvoices/paid") @GetMapping("/v2/allInvoices/paid")
fun allInvoice_paid_v2(): List<InvoiceInfo> { fun allInvoice_paid_v2(): List<InvoiceInfo> {
return invoiceService.allInvoicePaid() return invoiceService.allInvoicePaid()
@@ -113,4 +118,24 @@ class InvoiceController(
println("--------- OK -------------") println("--------- OK -------------")
return ResponseEntity.ok(invoiceService.importReceivedInvoice(workbook)) return ResponseEntity.ok(invoiceService.importReceivedInvoice(workbook))
} }

/**
* Import the combined Invoice Summary (issued and received)
*/
@PostMapping("/import/v2")
fun importInvoices(request: HttpServletRequest): ResponseEntity<*> {
var workbook: Workbook? = null

try {
val multipartFile = (request as MultipartHttpServletRequest).getFile("multipartFileList")
if (multipartFile != null) {
workbook = XSSFWorkbook(multipartFile.inputStream)
}
} catch (e: Exception) {
println("Excel Wrong")
println(e)
}
// println("--------- OK -------------")
return ResponseEntity.ok(invoiceService.importInvoices(workbook))
}
} }

+ 6
- 0
src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt Zobrazit soubor

@@ -4,6 +4,7 @@ import com.ffii.core.exception.NotFoundException
import com.ffii.tsms.modules.data.entity.* import com.ffii.tsms.modules.data.entity.*
import com.ffii.tsms.modules.project.entity.ProjectCategory import com.ffii.tsms.modules.project.entity.ProjectCategory
import com.ffii.tsms.modules.project.entity.ProjectRepository import com.ffii.tsms.modules.project.entity.ProjectRepository
import com.ffii.tsms.modules.project.entity.projections.ProjectPlanStartEnd
import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo import com.ffii.tsms.modules.project.entity.projections.ProjectSearchInfo
import com.ffii.tsms.modules.project.service.ProjectsService import com.ffii.tsms.modules.project.service.ProjectsService
import com.ffii.tsms.modules.project.web.models.* import com.ffii.tsms.modules.project.web.models.*
@@ -107,4 +108,9 @@ class ProjectsController(private val projectsService: ProjectsService, private v


return ResponseEntity.ok(projectsService.importFile(workbook)) return ResponseEntity.ok(projectsService.importFile(workbook))
} }

@GetMapping("/test")
fun test(@RequestParam id: Long): ProjectPlanStartEnd{
return projectRepository.findProjectPlanStartEndByIdAndDeletedFalse(id);
}
} }

+ 128
- 17
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt Zobrazit soubor

@@ -10,6 +10,7 @@ import com.ffii.tsms.modules.data.entity.Team
import com.ffii.tsms.modules.project.entity.Invoice import com.ffii.tsms.modules.project.entity.Invoice
import com.ffii.tsms.modules.project.entity.Milestone import com.ffii.tsms.modules.project.entity.Milestone
import com.ffii.tsms.modules.project.entity.Project import com.ffii.tsms.modules.project.entity.Project
import com.ffii.tsms.modules.project.entity.ProjectRepository
import com.ffii.tsms.modules.report.web.model.costAndExpenseRequest import com.ffii.tsms.modules.report.web.model.costAndExpenseRequest
import com.ffii.tsms.modules.timesheet.entity.Timesheet import com.ffii.tsms.modules.timesheet.entity.Timesheet
import org.apache.commons.logging.Log import org.apache.commons.logging.Log
@@ -22,12 +23,14 @@ import org.apache.poi.ss.usermodel.FormulaEvaluator
import org.apache.poi.ss.util.CellReference import org.apache.poi.ss.util.CellReference
import org.apache.poi.ss.util.RegionUtil import org.apache.poi.ss.util.RegionUtil
import org.apache.poi.xssf.usermodel.XSSFCellStyle import org.apache.poi.xssf.usermodel.XSSFCellStyle
import org.apache.poi.xssf.usermodel.XSSFColor
import org.springframework.core.io.ClassPathResource import org.springframework.core.io.ClassPathResource
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.math.BigDecimal import java.math.BigDecimal
import java.time.LocalDate import java.time.LocalDate
import java.time.Year
import java.time.YearMonth import java.time.YearMonth
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.ZoneId import java.time.ZoneId
@@ -42,6 +45,7 @@ data class DayInfo(val date: String?, val weekday: String?)
@Service @Service
open class ReportService( open class ReportService(
private val jdbcDao: JdbcDao, private val jdbcDao: JdbcDao,
private val projectRepository: ProjectRepository
) { ) {
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")
@@ -487,16 +491,22 @@ open class ReportService(
cellStyle = boldFontCellStyle cellStyle = boldFontCellStyle
} }


val receivedAmountCell = row.createCell(14)
val projectedCpiCell = row.createCell(14)
projectedCpiCell.apply {
cellFormula = "(H${rowNum}/J${rowNum})"
cellStyle = boldFontCellStyle
}

val receivedAmountCell = row.createCell(15)
val paidAmount = item["paidAmount"]?.let { it as BigDecimal } ?: BigDecimal(0) val paidAmount = item["paidAmount"]?.let { it as BigDecimal } ?: BigDecimal(0)
receivedAmountCell.apply { receivedAmountCell.apply {
setCellValue(paidAmount.toDouble()) setCellValue(paidAmount.toDouble())
cellStyle.dataFormat = accountingStyle cellStyle.dataFormat = accountingStyle
} }


val unsettledAmountCell = row.createCell(15)
val unsettledAmountCell = row.createCell(16)
unsettledAmountCell.apply { unsettledAmountCell.apply {
cellFormula = "L${rowNum}-O${rowNum}"
cellFormula = "L${rowNum}-P${rowNum}"
cellStyle = boldFontCellStyle cellStyle = boldFontCellStyle
cellStyle.dataFormat = accountingStyle cellStyle.dataFormat = accountingStyle
} }
@@ -578,17 +588,27 @@ open class ReportService(
CellUtil.setCellStyleProperty(lastCpiCell, "borderTop", BorderStyle.THIN) CellUtil.setCellStyleProperty(lastCpiCell, "borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(lastCpiCell, "borderBottom", BorderStyle.DOUBLE) CellUtil.setCellStyleProperty(lastCpiCell, "borderBottom", BorderStyle.DOUBLE)


val sumRAmountCell = row.createCell(14)
sumRAmountCell.apply {
val lastPCpiCell = row.createCell(14)
lastPCpiCell.apply {
cellFormula = "SUM(O15:O${rowNum})" cellFormula = "SUM(O15:O${rowNum})"
cellStyle = boldFontCellStyle
cellStyle.dataFormat = accountingStyle
}
CellUtil.setCellStyleProperty(lastPCpiCell, "borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(lastPCpiCell, "borderBottom", BorderStyle.DOUBLE)


val sumRAmountCell = row.createCell(15)
sumRAmountCell.apply {
cellFormula = "SUM(P15:P${rowNum})"
cellStyle.dataFormat = accountingStyle cellStyle.dataFormat = accountingStyle
} }
CellUtil.setCellStyleProperty(sumRAmountCell, "borderTop", BorderStyle.THIN) CellUtil.setCellStyleProperty(sumRAmountCell, "borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(sumRAmountCell, "borderBottom", BorderStyle.DOUBLE) CellUtil.setCellStyleProperty(sumRAmountCell, "borderBottom", BorderStyle.DOUBLE)


val sumUnSettleCell = row.createCell(15)
val sumUnSettleCell = row.createCell(16)
sumUnSettleCell.apply { sumUnSettleCell.apply {
cellFormula = "SUM(P15:P${rowNum})"
cellFormula = "SUM(Q15:Q${rowNum})"
cellStyle = boldFontCellStyle cellStyle = boldFontCellStyle
cellStyle.dataFormat = accountingStyle cellStyle.dataFormat = accountingStyle
} }
@@ -1434,6 +1454,13 @@ open class ReportService(
val workbook: Workbook = XSSFWorkbook(templateInputStream) val workbook: Workbook = XSSFWorkbook(templateInputStream)


val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)")
val defaultStyle = workbook.createDataFormat().getFormat("###,##0")

// Fill Orange Style
val fillOrange = workbook.createCellStyle()
val customColor = XSSFColor(byteArrayOf(255.toByte(), 192.toByte(), 0.toByte()))
fillOrange.setFillForegroundColor(customColor);
fillOrange.setFillPattern(FillPatternType.SOLID_FOREGROUND)


val sheet: Sheet = workbook.getSheetAt(0) val sheet: Sheet = workbook.getSheetAt(0)
var rowIndex = 2 var rowIndex = 2
@@ -1446,30 +1473,46 @@ open class ReportService(


val cell = getCell(0) ?: createCell(0) val cell = getCell(0) ?: createCell(0)
cell.setCellValue(salary.salaryPoint.toDouble()) cell.setCellValue(salary.salaryPoint.toDouble())
CellUtil.setAlignment(cell, HorizontalAlignment.CENTER)
cell.cellStyle.dataFormat = defaultStyle

when (index) { when (index) {
0 -> { 0 -> {
val cell1 = getCell(1) ?: createCell(1) val cell1 = getCell(1) ?: createCell(1)
cell1.setCellValue(salary.lowerLimit.toDouble()) cell1.setCellValue(salary.lowerLimit.toDouble())
CellUtil.setAlignment(cell1, HorizontalAlignment.CENTER)
cell1.cellStyle.dataFormat = defaultStyle
} }


else -> { else -> {
val cell1 = getCell(1) ?: createCell(1) val cell1 = getCell(1) ?: createCell(1)
cell1.cellFormula = cell1.cellFormula =
"(C{previousRow}+1)".replace("{previousRow}", (rowIndex).toString()) "(C{previousRow}+1)".replace("{previousRow}", (rowIndex).toString())
CellUtil.setAlignment(cell1, HorizontalAlignment.CENTER)
cell1.cellStyle.dataFormat = defaultStyle
} }

} }

val cell2 = getCell(2) ?: createCell(2) val cell2 = getCell(2) ?: createCell(2)
cell2.cellFormula = "(B{currentRow}+D{currentRow})-1".replace("{currentRow}", (rowIndex+1).toString()) cell2.cellFormula = "(B{currentRow}+D{currentRow})-1".replace("{currentRow}", (rowIndex+1).toString())
CellUtil.setAlignment(cell2, HorizontalAlignment.CENTER)
cell2.cellStyle.dataFormat = defaultStyle
// getCell(2).cellStyle.dataFormat = accountingStyle // getCell(2).cellStyle.dataFormat = accountingStyle


val cell3 = getCell(3)?:createCell(3) val cell3 = getCell(3)?:createCell(3)
cell3.setCellValue(salary.increment.toDouble()) cell3.setCellValue(salary.increment.toDouble())
cell3.cellStyle = fillOrange
cell3.cellStyle.dataFormat = defaultStyle
CellUtil.setAlignment(cell3, HorizontalAlignment.CENTER)



val cell4 = getCell(4)?:createCell(4) val cell4 = getCell(4)?:createCell(4)
cell4.cellFormula = cell4.cellFormula =
"(((C{currentRow}+B{currentRow})/2)/20)/8".replace("{currentRow}", (rowIndex+1).toString()) "(((C{currentRow}+B{currentRow})/2)/20)/8".replace("{currentRow}", (rowIndex+1).toString())
// getCell(4).cellStyle.dataFormat = accountingStyle // getCell(4).cellStyle.dataFormat = accountingStyle
cell4.cellStyle.dataFormat = accountingStyle cell4.cellStyle.dataFormat = accountingStyle
CellUtil.setAlignment(cell4, HorizontalAlignment.CENTER)
} }
rowIndex++; rowIndex++;
} }
@@ -2042,6 +2085,7 @@ open class ReportService(
+ " select p.code, p.description, c.name as client, IFNULL(s2.name, \"N/A\") as subsidiary, concat(t.code, \" - \", t.name) as teamLead," + " select p.code, p.description, c.name as client, IFNULL(s2.name, \"N/A\") as subsidiary, concat(t.code, \" - \", t.name) as teamLead,"
+ " IFNULL(cte_ts.normalConsumed, 0) as normalConsumed, IFNULL(cte_ts.otConsumed, 0) as otConsumed, DATE_FORMAT(cte_ts.recordDate, '%Y-%m') as recordDate, " + " IFNULL(cte_ts.normalConsumed, 0) as normalConsumed, IFNULL(cte_ts.otConsumed, 0) as otConsumed, DATE_FORMAT(cte_ts.recordDate, '%Y-%m') as recordDate, "
+ " IFNULL(cte_ts.salaryPoint, 0) as salaryPoint, " + " IFNULL(cte_ts.salaryPoint, 0) as salaryPoint, "
+ " (p.expectedTotalFee - IFNULL(cte_i.sumIssuedAmount, 0)) as expectedTotalFee, "
+ " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate, IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount, IFNULL(cte_tss.sumManhourExpenditure, 0) as sumManhourExpenditure," + " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate, IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount, IFNULL(cte_tss.sumManhourExpenditure, 0) as sumManhourExpenditure,"
+ " s.name, s.staffId, g.code as gradeCode, g.name as gradeName, t2.code as teamCode, t2.name as teamName" + " s.name, s.staffId, g.code as gradeCode, g.name as gradeName, t2.code as teamCode, t2.name as teamName"
+ " from project p" + " from project p"
@@ -2060,11 +2104,23 @@ open class ReportService(
+ " and p.id = :projectId" + " and p.id = :projectId"
+ " order by g.code, s.name, cte_ts.recordDate" + " order by g.code, s.name, cte_ts.recordDate"
) )
val projectPlanStartEndMonth = projectRepository.findProjectPlanStartEndByIdAndDeletedFalse(projectId)
val compareStartMonth= YearMonth.parse(startMonth)
val compareEndMonth= YearMonth.parse(endMonth)
val actualStartMonth: YearMonth
(if (projectPlanStartEndMonth.actualStart == null){
YearMonth.now().plusMonths(1)
}else{
YearMonth.from(projectPlanStartEndMonth.actualStart)
}).also { actualStartMonth = it }

val queryStartMonth = if(compareStartMonth.isAfter(actualStartMonth)) compareStartMonth else actualStartMonth
val queryEndMonth = if(compareEndMonth.isAfter(YearMonth.now())) YearMonth.now() else compareEndMonth


val args = mapOf( val args = mapOf(
"projectId" to projectId, "projectId" to projectId,
"startMonth" to startMonth,
"endMonth" to endMonth,
"startMonth" to queryStartMonth.toString(),
"endMonth" to queryEndMonth.toString(),
) )


val manHoursSpent = jdbcDao.queryForList(sql.toString(), args) val manHoursSpent = jdbcDao.queryForList(sql.toString(), args)
@@ -2088,7 +2144,7 @@ open class ReportService(
"startMonth" to startMonth, "startMonth" to startMonth,
"endMonth" to endMonth, "endMonth" to endMonth,
"code" to projectsCode["code"], "code" to projectsCode["code"],
"description" to projectsCode["description"],
"description" to projectsCode["description"]
) )


val staffInfoList = mutableListOf<Map<String, Any>>() val staffInfoList = mutableListOf<Map<String, Any>>()
@@ -2109,6 +2165,9 @@ open class ReportService(
if (info["code"] == item["code"] && "subsidiary" !in info) { if (info["code"] == item["code"] && "subsidiary" !in info) {
info["subsidiary"] = item.getValue("subsidiary") info["subsidiary"] = item.getValue("subsidiary")
} }
if (info["code"] == item["code"] && "expectedTotalFee" !in info) {
info["expectedTotalFee"] = item.getValue("expectedTotalFee")
}
if (info["manhourExpenditure"] != item.getValue("sumManhourExpenditure")) { if (info["manhourExpenditure"] != item.getValue("sumManhourExpenditure")) {
info["manhourExpenditure"] = item.getValue("sumManhourExpenditure") info["manhourExpenditure"] = item.getValue("sumManhourExpenditure")
} }
@@ -2188,9 +2247,11 @@ open class ReportService(
} }


fun genPandLReport(projectId: Long, startMonth: String, endMonth: String): ByteArray { fun genPandLReport(projectId: Long, startMonth: String, endMonth: String): ByteArray {


val manhoursSpentList = getManhoursSpent(projectId, startMonth, endMonth) val manhoursSpentList = getManhoursSpent(projectId, startMonth, endMonth)


val workbook: Workbook = createPandLReportWorkbook(PandL_REPORT, manhoursSpentList, startMonth, endMonth)
val workbook: Workbook = createPandLReportWorkbook(PandL_REPORT, manhoursSpentList, startMonth, endMonth, projectId)


val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() val outputStream: ByteArrayOutputStream = ByteArrayOutputStream()
workbook.write(outputStream) workbook.write(outputStream)
@@ -2203,7 +2264,8 @@ open class ReportService(
templatePath: String, templatePath: String,
manhoursSpent: List<Map<String, Any>>, manhoursSpent: List<Map<String, Any>>,
startMonth: String, startMonth: String,
endMonth: String
endMonth: String,
projectId: Long
): Workbook { ): Workbook {
val resource = ClassPathResource(templatePath) val resource = ClassPathResource(templatePath)
val templateInputStream = resource.inputStream val templateInputStream = resource.inputStream
@@ -2211,14 +2273,35 @@ open class ReportService(


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


val projectPlanStartEndMonth = projectRepository.findProjectPlanStartEndByIdAndDeletedFalse(projectId)
val compareStartMonth= YearMonth.parse(startMonth)
val compareEndMonth= YearMonth.parse(endMonth)
val actualStartMonth: YearMonth
(if (projectPlanStartEndMonth.actualStart == null){
YearMonth.now().plusMonths(1)
}else{
YearMonth.from(projectPlanStartEndMonth.actualStart)
}).also { actualStartMonth = it }

val queryStartMonth = if(compareStartMonth.isAfter(actualStartMonth)) compareStartMonth else actualStartMonth
val queryEndMonth = if(compareEndMonth.isAfter(YearMonth.now())) YearMonth.now() else compareEndMonth

val monthFormat = DateTimeFormatter.ofPattern("MMM yyyy", Locale.ENGLISH) val monthFormat = DateTimeFormatter.ofPattern("MMM yyyy", Locale.ENGLISH)
val startDate = YearMonth.parse(startMonth, DateTimeFormatter.ofPattern("yyyy-MM"))
val startDate = YearMonth.parse(queryStartMonth.toString(), DateTimeFormatter.ofPattern("yyyy-MM"))
val convertStartMonth = YearMonth.of(startDate.year, startDate.month) val convertStartMonth = YearMonth.of(startDate.year, startDate.month)
val endDate = YearMonth.parse(endMonth, DateTimeFormatter.ofPattern("yyyy-MM"))
val endDate = YearMonth.parse(queryEndMonth.toString(), DateTimeFormatter.ofPattern("yyyy-MM"))
val convertEndMonth = YearMonth.of(endDate.year, endDate.month) val convertEndMonth = YearMonth.of(endDate.year, endDate.month)


val monthRange: MutableList<Map<String, Any>> = ArrayList() val monthRange: MutableList<Map<String, Any>> = ArrayList()
var currentDate = startDate var currentDate = startDate
if (currentDate == endDate){
monthRange.add(
mapOf(
"display" to YearMonth.of(currentDate.year, currentDate.month).format(monthFormat),
"date" to currentDate
)
)
}
while (!currentDate.isAfter(endDate)) { while (!currentDate.isAfter(endDate)) {
monthRange.add( monthRange.add(
mapOf( mapOf(
@@ -2468,7 +2551,7 @@ open class ReportService(
val lastColumnIndex = getColumnAlphabet(3 + monthRange.size) val lastColumnIndex = getColumnAlphabet(3 + monthRange.size)
val totalManhourETitleCell = totalManhourERow.getCell(0) ?: totalManhourERow.createCell(0) val totalManhourETitleCell = totalManhourERow.getCell(0) ?: totalManhourERow.createCell(0)
totalManhourETitleCell.apply { totalManhourETitleCell.apply {
setCellValue("Total Manhour Expenditure (HKD)")
setCellValue("Less: Total Manhour Expenditure (HKD)")
} }
val totalManhourECell = totalManhourERow.getCell(1) ?: totalManhourERow.createCell(1) val totalManhourECell = totalManhourERow.getCell(1) ?: totalManhourERow.createCell(1)
totalManhourECell.apply { totalManhourECell.apply {
@@ -2490,8 +2573,36 @@ open class ReportService(
cellFormula = "B${rowNum - 1}-B${rowNum}" cellFormula = "B${rowNum - 1}-B${rowNum}"
cellStyle.dataFormat = accountingStyle cellStyle.dataFormat = accountingStyle
} }
CellUtil.setCellStyleProperty(panlCellTitle, "borderBottom", BorderStyle.DOUBLE)
CellUtil.setCellStyleProperty(panlCell, "borderBottom", BorderStyle.DOUBLE)
// CellUtil.setCellStyleProperty(panlCellTitle, "borderBottom", BorderStyle.DOUBLE)
// CellUtil.setCellStyleProperty(panlCell, "borderBottom", BorderStyle.DOUBLE)
val profitAndLossRowNum = rowNum+1

rowNum += 1
val uninvoicedAmountRow: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum)
val uninvoicedAmountTitleCell = uninvoicedAmountRow.getCell(0) ?: uninvoicedAmountRow.createCell(0)
uninvoicedAmountTitleCell.apply {
setCellValue("Add: Uninvoice Amount (HKD)")
}
val uninvoicedAmountCell = uninvoicedAmountRow.getCell(1) ?: uninvoicedAmountRow.createCell(1)
uninvoicedAmountCell.apply {
setCellValue(if ((info.getValue("expectedTotalFee") as Double) < 0.0 ) 0.0 else info.getValue("expectedTotalFee") as Double)
cellStyle.dataFormat = accountingStyle
}

rowNum += 1
val projectedPnLRow: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum)
val projectedPnLTitleCell = projectedPnLRow.getCell(0) ?: projectedPnLRow.createCell(0)
projectedPnLTitleCell.apply {
setCellValue("Projected Profit / (Loss)")
}
val projectedPnLCell = projectedPnLRow.getCell(1) ?: projectedPnLRow.createCell(1)
projectedPnLCell.apply {
cellFormula = "B${profitAndLossRowNum}+B${profitAndLossRowNum+1}"
cellStyle.dataFormat = accountingStyle
}

CellUtil.setCellStyleProperty(projectedPnLTitleCell, "borderBottom", BorderStyle.DOUBLE)
CellUtil.setCellStyleProperty(projectedPnLCell, "borderBottom", BorderStyle.DOUBLE)


conditionalFormattingNegative(sheet) conditionalFormattingNegative(sheet)




binární
src/main/resources/templates/report/EX01_Financial Status Report.xlsx Zobrazit soubor


binární
src/main/resources/templates/report/Salary Template.xlsx Zobrazit soubor


Načítá se…
Zrušit
Uložit