diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/InvoiceRepository.kt b/src/main/java/com/ffii/tsms/modules/project/entity/InvoiceRepository.kt index 940f2d2..a08be63 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/InvoiceRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/InvoiceRepository.kt @@ -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 { fun findAllByPaidAmountIsNotNullAndMilestonePaymentIn(milestonePayment: List): List - fun findInvoiceInfoBy(): List + fun findInvoiceInfoByAndDeletedFalse(): List + + fun findImportInvoiceInfoByAndDeletedFalse(): List fun findInvoiceInfoByPaidAmountIsNotNull(): List diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt index abc61dd..df7cd45 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt @@ -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 { fun findAllByTeamLeadAndCustomer(teamLead: Staff, customer: Customer): List fun findByCode(code: String): Project? + + fun findProjectPlanStartEndByIdAndDeletedFalse(id: Long): ProjectPlanStartEnd } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/projections/ImportInvoiceInfo.kt b/src/main/java/com/ffii/tsms/modules/project/entity/projections/ImportInvoiceInfo.kt new file mode 100644 index 0000000..ee9f8e2 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/entity/projections/ImportInvoiceInfo.kt @@ -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? +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoiceInfo.kt b/src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoiceInfo.kt index 5c76060..b208c47 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoiceInfo.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoiceInfo.kt @@ -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 diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectPlanStartEnd.kt b/src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectPlanStartEnd.kt new file mode 100644 index 0000000..367e8e5 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectPlanStartEnd.kt @@ -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? +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt b/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt index 950ab7e..fde8f71 100644 --- a/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt +++ b/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt @@ -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{ - return invoiceRepository.findInvoiceInfoBy() + return invoiceRepository.findInvoiceInfoByAndDeletedFalse() + } + + open fun allInvoiceV3(): List>{ + 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{ @@ -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> = 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 = 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 + } + } diff --git a/src/main/java/com/ffii/tsms/modules/project/web/InvoiceController.kt b/src/main/java/com/ffii/tsms/modules/project/web/InvoiceController.kt index a8c7abd..c143adf 100644 --- a/src/main/java/com/ffii/tsms/modules/project/web/InvoiceController.kt +++ b/src/main/java/com/ffii/tsms/modules/project/web/InvoiceController.kt @@ -75,6 +75,11 @@ class InvoiceController( return invoiceService.allInvoice() } + @GetMapping("/v3/allInvoices") + fun allInvoice_v3(): List> { + return invoiceService.allInvoiceV3() + } + @GetMapping("/v2/allInvoices/paid") fun allInvoice_paid_v2(): List { 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)) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt b/src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt index 61c070e..f1b172a 100644 --- a/src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt +++ b/src/main/java/com/ffii/tsms/modules/project/web/ProjectsController.kt @@ -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); + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt index d429f5f..f37d223 100644 --- a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt @@ -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>() @@ -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>, 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> = 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) diff --git a/src/main/resources/templates/report/EX01_Financial Status Report.xlsx b/src/main/resources/templates/report/EX01_Financial Status Report.xlsx index b269cbb..1279a4c 100644 Binary files a/src/main/resources/templates/report/EX01_Financial Status Report.xlsx and b/src/main/resources/templates/report/EX01_Financial Status Report.xlsx differ diff --git a/src/main/resources/templates/report/Salary Template.xlsx b/src/main/resources/templates/report/Salary Template.xlsx index 882cca1..c6bf84d 100644 Binary files a/src/main/resources/templates/report/Salary Template.xlsx and b/src/main/resources/templates/report/Salary Template.xlsx differ