Procházet zdrojové kódy

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

tags/Baseline_30082024_BACKEND_UAT
cyril.tsui před 1 rokem
rodič
revize
21fdc72105
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

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

interface InvoiceRepository : AbstractRepository<Invoice, Long> {

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

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

fun findImportInvoiceInfoByAndDeletedFalse(): List<ImportInvoiceInfo>

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.project.entity.projections.InvoiceInfoSearchInfo
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 org.springframework.data.jpa.repository.Query
import org.springframework.lang.Nullable
@@ -36,4 +37,6 @@ interface ProjectRepository : AbstractRepository<Project, Long> {
fun findAllByTeamLeadAndCustomer(teamLead: Staff, customer: Customer): List<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 org.springframework.beans.factory.annotation.Value
import org.springframework.cglib.core.Local
import org.springframework.data.jpa.repository.Query
import java.math.BigDecimal
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 net.sf.jasperreports.engine.JasperCompileManager
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.Sheet
import org.apache.poi.ss.usermodel.Workbook
import org.springframework.core.io.ClassPathResource
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.InputStream
import java.math.BigDecimal
import java.time.LocalDate
import java.time.ZoneId


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

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>{
@@ -497,4 +509,146 @@ open class InvoiceService(

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

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

@GetMapping("/v2/allInvoices/paid")
fun allInvoice_paid_v2(): List<InvoiceInfo> {
return invoiceService.allInvoicePaid()
@@ -113,4 +118,24 @@ class InvoiceController(
println("--------- OK -------------")
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.project.entity.ProjectCategory
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.service.ProjectsService
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))
}

@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.Milestone
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.timesheet.entity.Timesheet
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.RegionUtil
import org.apache.poi.xssf.usermodel.XSSFCellStyle
import org.apache.poi.xssf.usermodel.XSSFColor
import org.springframework.core.io.ClassPathResource
import org.springframework.stereotype.Service
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.math.BigDecimal
import java.time.LocalDate
import java.time.Year
import java.time.YearMonth
import java.time.format.DateTimeFormatter
import java.time.ZoneId
@@ -42,6 +45,7 @@ data class DayInfo(val date: String?, val weekday: String?)
@Service
open class ReportService(
private val jdbcDao: JdbcDao,
private val projectRepository: ProjectRepository
) {
private val logger: Log = LogFactory.getLog(javaClass)
private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd")
@@ -487,16 +491,22 @@ open class ReportService(
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)
receivedAmountCell.apply {
setCellValue(paidAmount.toDouble())
cellStyle.dataFormat = accountingStyle
}

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

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

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)
var rowIndex = 2
@@ -1446,30 +1473,46 @@ open class ReportService(

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

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

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

}

val cell2 = getCell(2) ?: createCell(2)
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

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


val cell4 = getCell(4)?:createCell(4)
cell4.cellFormula =
"(((C{currentRow}+B{currentRow})/2)/20)/8".replace("{currentRow}", (rowIndex+1).toString())
// getCell(4).cellStyle.dataFormat = accountingStyle
cell4.cellStyle.dataFormat = accountingStyle
CellUtil.setAlignment(cell4, HorizontalAlignment.CENTER)
}
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,"
+ " 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, "
+ " (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,"
+ " s.name, s.staffId, g.code as gradeCode, g.name as gradeName, t2.code as teamCode, t2.name as teamName"
+ " from project p"
@@ -2060,11 +2104,23 @@ open class ReportService(
+ " and p.id = :projectId"
+ " 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(
"projectId" to projectId,
"startMonth" to startMonth,
"endMonth" to endMonth,
"startMonth" to queryStartMonth.toString(),
"endMonth" to queryEndMonth.toString(),
)

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

val staffInfoList = mutableListOf<Map<String, Any>>()
@@ -2109,6 +2165,9 @@ open class ReportService(
if (info["code"] == item["code"] && "subsidiary" !in info) {
info["subsidiary"] = item.getValue("subsidiary")
}
if (info["code"] == item["code"] && "expectedTotalFee" !in info) {
info["expectedTotalFee"] = item.getValue("expectedTotalFee")
}
if (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 {


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

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 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 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 monthRange: MutableList<Map<String, Any>> = ArrayList()
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)) {
monthRange.add(
mapOf(
@@ -2468,7 +2551,7 @@ open class ReportService(
val lastColumnIndex = getColumnAlphabet(3 + monthRange.size)
val totalManhourETitleCell = totalManhourERow.getCell(0) ?: totalManhourERow.createCell(0)
totalManhourETitleCell.apply {
setCellValue("Total Manhour Expenditure (HKD)")
setCellValue("Less: Total Manhour Expenditure (HKD)")
}
val totalManhourECell = totalManhourERow.getCell(1) ?: totalManhourERow.createCell(1)
totalManhourECell.apply {
@@ -2490,8 +2573,36 @@ open class ReportService(
cellFormula = "B${rowNum - 1}-B${rowNum}"
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)



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