diff --git a/src/main/java/com/ffii/tsms/modules/common/service/ExcelReportService.kt b/src/main/java/com/ffii/tsms/modules/common/service/ExcelReportService.kt deleted file mode 100644 index 6441c0f..0000000 --- a/src/main/java/com/ffii/tsms/modules/common/service/ExcelReportService.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.ffii.tsms.modules.common.service - -import com.ffii.tsms.modules.project.entity.Project -import org.apache.poi.ss.usermodel.Cell -import org.apache.poi.ss.usermodel.CellStyle -import org.apache.poi.ss.usermodel.HorizontalAlignment -import org.apache.poi.ss.usermodel.Row -import org.apache.poi.ss.usermodel.Sheet -import org.apache.poi.ss.usermodel.Workbook -import org.apache.poi.ss.util.CellRangeAddress -import org.apache.poi.ss.util.CellUtil -import org.apache.poi.xssf.usermodel.XSSFWorkbook -import org.springframework.core.io.ClassPathResource -import org.springframework.stereotype.Service -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter - -@Service -open class ExcelReportService { - private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") - private val FORMATTED_TODAY = LocalDate.now().format(DATE_FORMATTER) - - private val EX02_PROJECT_CASH_FLOW_REPORT = "templates/report/EX02_Project Cash Flow Report.xlsx" - - // ==============================|| GENERATE REPORT ||============================== // - @Throws(IOException::class) - fun generateEX02ProjectCashFlowReport(project: Project): ByteArray { - // Generate the Excel report with query results - val workbook: Workbook = createEX02ProjectCashFlowReport(project, EX02_PROJECT_CASH_FLOW_REPORT) - - // Write the workbook to a ByteArrayOutputStream - val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() - workbook.write(outputStream) - workbook.close() - - return outputStream.toByteArray() - } - - // ==============================|| CREATE REPORT ||============================== // - @Throws(IOException::class) - private fun createEX02ProjectCashFlowReport( - project: Project, - templatePath: String, - ): Workbook { - // please create a new function for each report template - val resource = ClassPathResource(templatePath) - val templateInputStream = resource.inputStream - val workbook: Workbook = XSSFWorkbook(templateInputStream) - - val sheet: Sheet = workbook.getSheetAt(0) - -// val alignLeftStyle: CellStyle = workbook.createCellStyle().apply { -// alignment = HorizontalAlignment.LEFT // Set the alignment to left -// } -// -// val alignRightStyle: CellStyle = workbook.createCellStyle().apply { -// alignment = HorizontalAlignment.RIGHT // Set the alignment to right -// } - - 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 { - setCellValue(FORMATTED_TODAY) - } - - rowIndex = 2 - sheet.getRow(rowIndex).getCell(columnIndex).apply { - setCellValue(project.code) - } - - rowIndex = 3 - sheet.getRow(rowIndex).getCell(columnIndex).apply { - setCellValue(project.name) - } - - rowIndex = 4 - sheet.getRow(rowIndex).getCell(columnIndex).apply { - setCellValue(if (project.customer?.name == null) "N/A" else project.customer?.name) - } - - rowIndex = 5 - sheet.getRow(rowIndex).getCell(columnIndex).apply { - setCellValue(if (project.teamLead?.team?.name == null) "N/A" else project.teamLead?.team?.name) - } - - rowIndex = 9 - sheet.getRow(rowIndex).apply { - getCell(1).setCellValue(project.expectedTotalFee!! * 0.8) - getCell(2).setCellValue(project.expectedTotalFee!!) - } - - return workbook - } -} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/Invoice.kt b/src/main/java/com/ffii/tsms/modules/project/entity/Invoice.kt new file mode 100644 index 0000000..cd9115d --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/entity/Invoice.kt @@ -0,0 +1,43 @@ +package com.ffii.tsms.modules.project.entity + +import com.ffii.core.entity.BaseEntity +import com.ffii.tsms.modules.data.entity.Customer +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.JoinColumn +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table +import jakarta.validation.constraints.NotNull +import java.time.LocalDate + +@Entity +@Table(name = "invoice") +open class Invoice : BaseEntity(){ + + @NotNull + @Column(name = "invoiceNo", length = 45) + open var invoiceNo: String? = null + + @NotNull + @ManyToOne + @JoinColumn(name = "milestonePaymentId") + open var milestonePayment: MilestonePayment? = null + + @NotNull + @Column(name = "invoiceDate") + open var invoiceDate: LocalDate? = null + + @NotNull + @Column(name = "dueDate") + open var dueDate: LocalDate? = null + + @Column(name = "receiptDate") + open var receiptDate: LocalDate? = null + + @NotNull + @Column(name = "unpaidAmount") + open var unpaidAmount: Double? = null + + @Column(name = "paidAmount") + open var paidAmount: Double? = null +} \ No newline at end of file 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 new file mode 100644 index 0000000..f404d7d --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/entity/InvoiceRepository.kt @@ -0,0 +1,9 @@ +package com.ffii.tsms.modules.project.entity + +import com.ffii.core.support.AbstractRepository + +interface InvoiceRepository : AbstractRepository { + + fun findAllByPaidAmountIsNotNullAndMilestonePaymentIn(milestonePayment: List): List + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/Milestone.kt b/src/main/java/com/ffii/tsms/modules/project/entity/Milestone.kt index 8a760a0..945e616 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/Milestone.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/Milestone.kt @@ -1,5 +1,6 @@ package com.ffii.tsms.modules.project.entity +import com.fasterxml.jackson.annotation.JsonManagedReference import com.ffii.core.entity.IdEntity import jakarta.persistence.* import jakarta.validation.constraints.NotNull @@ -15,6 +16,7 @@ open class Milestone : IdEntity() { @Column(name = "description") open var description: String? = null + @JsonManagedReference @OneToMany(mappedBy = "milestone", cascade = [CascadeType.ALL], orphanRemoval = true) open var milestonePayments: MutableList = mutableListOf() diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/MilestonePayment.kt b/src/main/java/com/ffii/tsms/modules/project/entity/MilestonePayment.kt index 4dec858..14ddd1f 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/MilestonePayment.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/MilestonePayment.kt @@ -1,5 +1,6 @@ package com.ffii.tsms.modules.project.entity +import com.fasterxml.jackson.annotation.JsonBackReference import com.ffii.core.entity.IdEntity import jakarta.persistence.* import jakarta.validation.constraints.NotNull @@ -21,6 +22,7 @@ open class MilestonePayment : IdEntity() { open var description: String? = null @ManyToOne + @JsonBackReference @JoinColumn(name = "milestoneId") open var milestone: Milestone? = null } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/MilestonePaymentRepository.kt b/src/main/java/com/ffii/tsms/modules/project/entity/MilestonePaymentRepository.kt index 0bfbc92..6d7f057 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/MilestonePaymentRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/MilestonePaymentRepository.kt @@ -3,4 +3,5 @@ package com.ffii.tsms.modules.project.entity; import com.ffii.core.support.AbstractRepository interface MilestonePaymentRepository : AbstractRepository { + fun findAllByMilestoneIn(milestones: List): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/Project.kt b/src/main/java/com/ffii/tsms/modules/project/entity/Project.kt index 5155527..e51f7d2 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/Project.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/Project.kt @@ -1,5 +1,6 @@ package com.ffii.tsms.modules.project.entity +import com.fasterxml.jackson.annotation.JsonManagedReference import com.ffii.core.entity.BaseEntity import com.ffii.tsms.modules.data.entity.* import jakarta.persistence.* 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 dd16c96..c21a85a 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 @@ -4,9 +4,8 @@ import com.ffii.core.support.AbstractBaseEntityService import com.ffii.core.support.AbstractIdEntityService import com.ffii.core.support.JdbcDao import com.ffii.core.utils.PdfUtils +import com.ffii.tsms.modules.project.entity.* -import com.ffii.tsms.modules.project.entity.MilestonePayment -import com.ffii.tsms.modules.project.entity.MilestonePaymentRepository import com.ffii.tsms.modules.project.entity.projections.InvoicePDFReq import com.ffii.tsms.modules.project.entity.projections.InvoiceSearchInfo import net.sf.jasperreports.engine.JasperCompileManager @@ -21,9 +20,18 @@ import java.time.format.DateTimeFormatter @Service open class InvoiceService( + private val invoiceRepository: InvoiceRepository, + private val milestoneRepository: MilestoneRepository, private val milestonePaymentRepository: MilestonePaymentRepository, private val jdbcDao: JdbcDao, -) : AbstractIdEntityService(jdbcDao, milestonePaymentRepository){ +) : AbstractIdEntityService(jdbcDao, invoiceRepository){ + + open fun findAllByProjectAndPaidAmountIsNotNull(project: Project): List { + val milestones = milestoneRepository.findAllByProject(project) + val milestonePayments = milestonePaymentRepository.findAllByMilestoneIn(milestones) + return invoiceRepository.findAllByPaidAmountIsNotNullAndMilestonePaymentIn(milestonePayments) + } + open fun allMilestonePayments(): List> { val sql = StringBuilder(" select " + " mp.id, " 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 new file mode 100644 index 0000000..21bcbeb --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt @@ -0,0 +1,160 @@ +package com.ffii.tsms.modules.report.service + +import com.ffii.tsms.modules.project.entity.Invoice +import com.ffii.tsms.modules.project.entity.Project +import org.apache.poi.ss.usermodel.Sheet +import org.apache.poi.ss.usermodel.Workbook +import org.apache.poi.xssf.usermodel.XSSFDataFormat +import org.apache.poi.xssf.usermodel.XSSFWorkbook +import org.springframework.core.io.ClassPathResource +import org.springframework.stereotype.Service +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +@Service +open class ReportService { + private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") + private val FORMATTED_TODAY = LocalDate.now().format(DATE_FORMATTER) + + private val PROJECT_CASH_FLOW_REPORT = "templates/report/EX02_Project Cash Flow Report.xlsx" + + // ==============================|| GENERATE REPORT ||============================== // + @Throws(IOException::class) + fun generateProjectCashFlowReport(project: Project, invoices: List): ByteArray { + // Generate the Excel report with query results + val workbook: Workbook = createProjectCashFlowReport(project, invoices, PROJECT_CASH_FLOW_REPORT) + + // Write the workbook to a ByteArrayOutputStream + val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() + workbook.write(outputStream) + workbook.close() + + return outputStream.toByteArray() + } + + // ==============================|| CREATE REPORT ||============================== // + @Throws(IOException::class) + private fun createProjectCashFlowReport( + project: Project, + invoices: List, + templatePath: String, + ): Workbook { + // please create a new function for each report template + val resource = ClassPathResource(templatePath) + val templateInputStream = resource.inputStream + val workbook: Workbook = XSSFWorkbook(templateInputStream) + + val sheet: Sheet = workbook.getSheetAt(0) + + // accounting style + comma style + 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 { + setCellValue(FORMATTED_TODAY) + } + + rowIndex = 2 + sheet.getRow(rowIndex).getCell(columnIndex).apply { + setCellValue(project.code) + } + + rowIndex = 3 + sheet.getRow(rowIndex).getCell(columnIndex).apply { + setCellValue(project.name) + } + + rowIndex = 4 + sheet.getRow(rowIndex).getCell(columnIndex).apply { + setCellValue(if (project.customer?.name == null) "N/A" else project.customer?.name) + } + + rowIndex = 5 + sheet.getRow(rowIndex).getCell(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 { + setCellValue(project.expectedTotalFee!!) + cellStyle.dataFormat = accountingStyle + } + + getCell(2).apply { + setCellValue(project.expectedTotalFee!! / 0.8) + cellStyle.dataFormat = accountingStyle + } + } + + rowIndex = 10 + val actualIncome = invoices.sumOf { invoice -> invoice.paidAmount!! } + sheet.getRow(rowIndex).apply { + getCell(1).apply { + // TODO: Replace by actual expenditure + setCellValue(actualIncome * 0.8) + cellStyle.dataFormat = accountingStyle + } + + getCell(2).apply { + setCellValue(actualIncome) + cellStyle.dataFormat = accountingStyle + } + } + + rowIndex = 11 + sheet.getRow(rowIndex).apply { + getCell(1).apply { + // TODO: Replace by actual expenditure + cellFormula = "B10-B11" + cellStyle.dataFormat = accountingStyle + } + + getCell(2).apply { + cellFormula = "C10-C11" + cellStyle.dataFormat = accountingStyle + } + } + + // TODO: Add expenditure + // formula =IF(B17>0,D16-B17,D16+C17) + rowIndex = 15 + val dateFormatter = DateTimeFormatter.ofPattern("MMM YYYY") + invoices.forEach { invoice: Invoice -> + sheet.getRow(rowIndex++).apply { + getCell(0).apply { + setCellValue(invoice.receiptDate!!.format(dateFormatter)) + } + + getCell(1).apply { + setCellValue(0.0) + cellStyle.dataFormat = accountingStyle + } + + getCell(2).apply { + setCellValue(invoice.paidAmount!!) + cellStyle.dataFormat = accountingStyle + } + + getCell(3).apply { + val lastRow = rowIndex - 1 + if (lastRow == 15) { + cellFormula = "C{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 + } + + getCell(4).apply { + setCellValue(invoice.milestonePayment!!.description!!) + } + } + } + + return workbook + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt index b7ad3c6..d2a0247 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt @@ -1,36 +1,46 @@ package com.ffii.tsms.modules.report.web -import com.ffii.tsms.modules.common.service.ExcelReportService -import com.ffii.tsms.modules.project.entity.ProjectRepository -import com.ffii.tsms.modules.report.web.model.EX02ProjectCashFlowReportRequest +import com.ffii.tsms.modules.project.entity.* +import com.ffii.tsms.modules.report.service.ReportService +import com.ffii.tsms.modules.project.service.InvoiceService +import com.ffii.tsms.modules.report.web.model.ProjectCashFlowReportRequest import jakarta.validation.Valid import org.springframework.core.io.ByteArrayResource import org.springframework.core.io.Resource -import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.web.bind.ServletRequestBindingException +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import java.io.IOException import java.time.LocalDate @RestController @RequestMapping("/reports") -class ReportController(private val excelReportService: ExcelReportService, private val projectRepository: ProjectRepository) { +class ReportController(private val invoiceRepository: InvoiceRepository, private val milestonePaymentRepository: MilestonePaymentRepository, private val excelReportService: ReportService, private val projectRepository: ProjectRepository, private val invoiceService: InvoiceService) { - @PostMapping("/EX02-ProjectCashFlowReport") + @PostMapping("/ProjectCashFlowReport") @Throws(ServletRequestBindingException::class, IOException::class) - fun getEx02ProjectCashFlowReport(@RequestBody @Valid request: EX02ProjectCashFlowReportRequest): ResponseEntity { + fun getProjectCashFlowReport(@RequestBody @Valid request: ProjectCashFlowReportRequest): ResponseEntity { val project = projectRepository.findById(request.projectId).orElseThrow() + val invoices = invoiceService.findAllByProjectAndPaidAmountIsNotNull(project) - val reportResult: ByteArray = excelReportService.generateEX02ProjectCashFlowReport(project) + val reportResult: ByteArray = excelReportService.generateProjectCashFlowReport(project, invoices) // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") return ResponseEntity.ok() // .contentType(mediaType) - .header("filename", "EX02 - Project Cash Flow Report - " + LocalDate.now() + ".xlsx") + .header("filename", "Project Cash Flow Report - " + LocalDate.now() + ".xlsx") .body(ByteArrayResource(reportResult)) } + + @GetMapping("/test/{id}") + fun test(@PathVariable id: Long): List { + val project = projectRepository.findById(id).orElseThrow() + return invoiceService.findAllByProjectAndPaidAmountIsNotNull(project) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt index 083368a..99b6e61 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt @@ -1,5 +1,5 @@ package com.ffii.tsms.modules.report.web.model -data class EX02ProjectCashFlowReportRequest ( +data class ProjectCashFlowReportRequest ( val projectId: Long ) \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20240503_01_cyril/01_create_invoice.sql b/src/main/resources/db/changelog/changes/20240503_01_cyril/01_create_invoice.sql new file mode 100644 index 0000000..e344a6b --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240503_01_cyril/01_create_invoice.sql @@ -0,0 +1,39 @@ +-- liquibase formatted sql +-- changeset cyril:create invoice + +CREATE TABLE `invoice` ( + `id` INT NOT NULL AUTO_INCREMENT, + `version` INT NOT NULL DEFAULT '0', + `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` VARCHAR(30) NULL DEFAULT NULL, + `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` VARCHAR(30) NULL DEFAULT NULL, + `deleted` TINYINT(1) NOT NULL DEFAULT '0', + `invoiceNo` VARCHAR(45) NOT NULL, + `projectId` INT NOT NULL, + `milestonePaymentId` INT NOT NULL, + `clientId` INT NOT NULL, + `invoiceDate` DATE NOT NULL, + `dueDate` DATE NOT NULL, + `receiptDate` DATE NULL, + `unpaidAmount` DECIMAL(16,2) NOT NULL, + `paidAmount` DECIMAL(16,2) NULL, + PRIMARY KEY (`id`), + INDEX `FK_INVOICE_ON_PROJECTID` (`projectId` ASC) VISIBLE, + INDEX `FK_INVOICE_ON_MILESTONEPAYMENTID` (`milestonePaymentId` ASC) VISIBLE, + INDEX `FK_INVOICE_ON_CLIENTID` (`clientId` ASC) VISIBLE, + CONSTRAINT `FK_INVOICE_ON_PROJECTID` + FOREIGN KEY (`projectId`) + REFERENCES `project` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `FK_INVOICE_ON_MILESTONEPAYMENTID` + FOREIGN KEY (`milestonePaymentId`) + REFERENCES `milestone_payment` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT `FK_INVOICE_ON_CLIENTID` + FOREIGN KEY (`clientId`) + REFERENCES `customer` (`id`) + ON DELETE NO ACTION + ON UPDATE NO ACTION); diff --git a/src/main/resources/db/changelog/changes/20240503_01_cyril/02_update_invoice.sql b/src/main/resources/db/changelog/changes/20240503_01_cyril/02_update_invoice.sql new file mode 100644 index 0000000..e7ce87b --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240503_01_cyril/02_update_invoice.sql @@ -0,0 +1,9 @@ +-- liquibase formatted sql +-- changeset cyril:update invoice + +ALTER TABLE `invoice` +DROP FOREIGN KEY `FK_INVOICE_ON_CLIENTID`; +ALTER TABLE `invoice` +DROP COLUMN `clientId`, +DROP INDEX `FK_INVOICE_ON_CLIENTID` ; +; diff --git a/src/main/resources/db/changelog/changes/20240503_01_cyril/03_update_invoice.sql b/src/main/resources/db/changelog/changes/20240503_01_cyril/03_update_invoice.sql new file mode 100644 index 0000000..aaf6f33 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240503_01_cyril/03_update_invoice.sql @@ -0,0 +1,9 @@ +-- liquibase formatted sql +-- changeset cyril:update invoice + +ALTER TABLE `invoice` +DROP FOREIGN KEY `FK_INVOICE_ON_PROJECTID`; +ALTER TABLE `invoice` +DROP COLUMN `projectId`, +DROP INDEX `FK_INVOICE_ON_PROJECTID` ; +; diff --git a/src/main/resources/templates/report/EX02_Project Cash Flow Report.xlsx b/src/main/resources/templates/report/EX02_Project Cash Flow Report.xlsx index ac160ab..7b3763d 100644 Binary files a/src/main/resources/templates/report/EX02_Project Cash Flow Report.xlsx and b/src/main/resources/templates/report/EX02_Project Cash Flow Report.xlsx differ