diff --git a/build.gradle b/build.gradle index ea11e47..d10cc26 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,9 @@ java { repositories { mavenCentral() + maven { + url 'https://jaspersoft.jfrog.io/jaspersoft/third-party-ce-artifacts/' + } } dependencies { @@ -44,6 +47,9 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-reflect" + implementation group: 'net.sf.jasperreports', name: 'jasperreports', version: '6.21.0' + implementation group: 'net.sf.jasperreports', name: 'jasperreports-fonts', version: '6.21.0' + compileOnly group: 'jakarta.servlet', name: 'jakarta.servlet-api', version: '6.0.0' runtimeOnly 'com.mysql:mysql-connector-j' diff --git a/src/main/java/com/ffii/core/utils/PdfUtils.java b/src/main/java/com/ffii/core/utils/PdfUtils.java new file mode 100644 index 0000000..92a8690 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/PdfUtils.java @@ -0,0 +1,32 @@ +package com.ffii.core.utils; + +import org.springframework.util.ResourceUtils; +import java.io.File; +import net.sf.jasperreports.engine.JasperCompileManager; +import net.sf.jasperreports.engine.JasperPrint; +import net.sf.jasperreports.engine.JasperReport; +import java.util.Map; +import java.util.List; + +import net.sf.jasperreports.engine.JRDataSource; +import net.sf.jasperreports.engine.JREmptyDataSource; +import net.sf.jasperreports.engine.JasperFillManager; +import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource; + +public class PdfUtils { + + public static JasperReport loadJasperReport(String path) throws Exception { + File file = ResourceUtils.getFile(path); + JasperReport reportTemplate = JasperCompileManager.compileReport(file.getAbsolutePath()); + return reportTemplate; + } + + public static JasperPrint fillReport(JasperReport report, List loopList, Map params) + throws Exception { + JRDataSource dataSources = loopList.size() > 0 ? new JRBeanCollectionDataSource(loopList) + : new JREmptyDataSource(); + JasperPrint jasperPrint = JasperFillManager.fillReport(report, params, dataSources); + return jasperPrint; + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/config/WebConfig.java b/src/main/java/com/ffii/tsms/config/WebConfig.java index bf3c622..480cc88 100644 --- a/src/main/java/com/ffii/tsms/config/WebConfig.java +++ b/src/main/java/com/ffii/tsms/config/WebConfig.java @@ -2,6 +2,8 @@ package com.ffii.tsms.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -26,4 +28,9 @@ public class WebConfig implements WebMvcConfigurer { return new InternalResourceViewResolver(); } + @Override + public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { + configurer.defaultContentType(MediaType.APPLICATION_JSON); + } + } 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 new file mode 100644 index 0000000..0bfbc92 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/entity/MilestonePaymentRepository.kt @@ -0,0 +1,6 @@ +package com.ffii.tsms.modules.project.entity; + +import com.ffii.core.support.AbstractRepository + +interface MilestonePaymentRepository : AbstractRepository { +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/MilestoneRepository.kt b/src/main/java/com/ffii/tsms/modules/project/entity/MilestoneRepository.kt index 4b22c09..c5f630b 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/MilestoneRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/MilestoneRepository.kt @@ -1,6 +1,9 @@ package com.ffii.tsms.modules.project.entity; import com.ffii.core.support.AbstractRepository +import com.ffii.tsms.modules.project.entity.projections.InvoiceSearchInfo interface MilestoneRepository : AbstractRepository { + + fun findInvoiceSearchInfoBy(): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoicePDFReq.kt b/src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoicePDFReq.kt new file mode 100644 index 0000000..25ea368 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoicePDFReq.kt @@ -0,0 +1,6 @@ +package com.ffii.tsms.modules.project.entity.projections + +class InvoicePDFReq { + val amount: Int = 0 + val client: String = "" +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoiceSearchInfo.kt b/src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoiceSearchInfo.kt index 281e576..d36a3ba 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoiceSearchInfo.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/projections/InvoiceSearchInfo.kt @@ -1,14 +1,18 @@ package com.ffii.tsms.modules.project.entity.projections +import com.ffii.tsms.modules.project.entity.MilestonePayment import org.springframework.beans.factory.annotation.Value interface InvoiceSearchInfo { val id: Long? - @get:Value("#{target.name}") + @get:Value("#{target.project.name}") val projectName: String? - @get:Value("#{target.code}") + @get:Value("#{target.project.code}") val projectCode: String? + @get:Value("#{target.milestonePayment}") + val milestonePayment: MutableList? + } \ 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 new file mode 100644 index 0000000..d5fa798 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt @@ -0,0 +1,122 @@ +package com.ffii.tsms.modules.project.service + +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.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 +import net.sf.jasperreports.engine.JasperReport +import org.springframework.core.io.ClassPathResource +import org.springframework.stereotype.Service +import java.io.InputStream +import java.math.BigDecimal +import java.time.LocalDate +import java.time.format.DateTimeFormatter + + +@Service +open class InvoiceService( + private val milestonePaymentRepository: MilestonePaymentRepository, + private val jdbcDao: JdbcDao, +) : AbstractIdEntityService(jdbcDao, milestonePaymentRepository){ + open fun allMilestonePayments(): List> { + val sql = StringBuilder(" select " + + " mp.id, " + + " mp.date as paymentMilestoneDate, mp.amount, mp.description as comingPaymentMileStone, " + + " m.startDate, m.endDate, m.name, m.description as milestoneDescription, " + + " p.code as projectCode, p.name as projectName " + + " from milestone_payment mp " + + " left join milestone m on m.id = mp.milestoneId " + + " left join project p on p.id = m.projectId " + + " where p.deleted = false " + ) + return jdbcDao.queryForList(sql.toString()) + } + + open fun getProjectDetailsByMilestonePaymentId(id: Long): List> { + val sql = StringBuilder(" select " + + " mp.id, " + + " mp.date as paymentMilestoneDate, mp.amount, mp.description as comingPaymentMileStone, " + + " m.startDate, m.endDate, m.name, m.description as milestoneDescription, " + + " p.code as projectCode, p.name as projectName " + + " from milestone_payment mp " + + " left join milestone m on m.id = mp.milestoneId " + + " left join project p on p.id = m.projectId " + + " where p.deleted = false " + + " and mp.id = :id " + ) + val args: MutableMap = mutableMapOf( + "id" to id, + ) + return jdbcDao.queryForList(sql.toString(), args) + } + + open fun getInvoiceInfoByMilestonePaymentId(id: Long): List> { + val sql = StringBuilder(" select " + + " mp.id, " + + " c.name as client, c.address, " + + " p.custLeadName as attention, CURDATE() as invoiceDate, mp.date as dueDate, " + + " \'XXX\' as projectRefNo " + + " from milestone_payment mp " + + " left join milestone m on m.id = mp.milestoneId " + + " left join project p on p.id = m.projectId " + + " left join customer c on c.id = p.customerId " + + " where p.deleted = false " + + " and mp.id = :id " + ) + val args: MutableMap = mutableMapOf( + "id" to id, + ) + return jdbcDao.queryForList(sql.toString(), args) + } + + fun exportPDF(req: InvoicePDFReq): Map { + // Method implementation + try { + val INVOICE_PDF = "pdf/invoicePDF.jrxml" + val resource: ClassPathResource = ClassPathResource(INVOICE_PDF) + val inputStream: InputStream = resource.inputStream + + val invoicePDF: JasperReport = JasperCompileManager.compileReport(inputStream) + + val fields: MutableList> = ArrayList() + val fieldValue1: Map = mapOf( + "unitPrice" to BigDecimal(1), + "qty" to BigDecimal(2), + "paymentMilestone" to "1 - Completion of stage 1: Design and Cost Planning", + ) + val fieldValue2: Map = mapOf( + "unitPrice" to BigDecimal(200), + "qty" to BigDecimal(3), + "paymentMilestone" to "1 - Completion of stage 1: Design and Cost Planning", + ) + fields.add(fieldValue1) + fields.add(fieldValue2) + val currentDate: LocalDate = LocalDate.now() + + val params: MutableMap = HashMap() + params["Client"] = "CLIENT WONG" + params["Address"] = "Shop No. 17, B1/F, Airside, 2 Concorde Road, Kai Tak, Kowloon" + params["Attention"] = "002 Lee" + params["invoiceNo"] = "INV-20240424" + params["projectRefNo"] = "XXX" + params["curDate"] = currentDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + params["dueDate"] = "2024-05-01" + + return mapOf( + "report" to PdfUtils.fillReport(invoicePDF, fields, params), + "fileName" to params["Client"] as String + ) + }catch (e: Exception) { + println(e) + return mapOf( + "error" to e + ) + } + } +} 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 e30a538..9e7e911 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 @@ -1,32 +1,60 @@ package com.ffii.tsms.modules.project.web import com.ffii.tsms.modules.project.entity.projections.InvoiceInfoSearchInfo +import com.ffii.tsms.modules.project.entity.projections.InvoicePDFReq import com.ffii.tsms.modules.project.entity.projections.InvoiceSearchInfo +import com.ffii.tsms.modules.project.service.InvoiceService import com.ffii.tsms.modules.project.service.ProjectsService +import jakarta.servlet.http.HttpServletResponse +import net.sf.jasperreports.engine.JasperExportManager +import net.sf.jasperreports.engine.JasperPrint 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.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController +import java.io.OutputStream @RestController @RequestMapping("/invoices") class InvoiceController( - private val projectsService: ProjectsService + private val projectsService: ProjectsService, + private val invoiceService: InvoiceService ) { @GetMapping - fun allInvoice(): List { - return projectsService.allInvoices() + fun allInvoice(): List> { + return invoiceService.allMilestonePayments() } - @GetMapping("/getProjectDetailById") - fun getInvoiceById(@RequestParam("id") id: Long): List { - return projectsService.getProjectDetailById(id) + @GetMapping("/getProjectDetail/{id}") + fun getInvoiceById(@PathVariable id: Long): List> { + return invoiceService.getProjectDetailsByMilestonePaymentId(id) } - @GetMapping("/getInvoiceInfoById") - fun getInvoiceInfoById(@RequestParam("id") id: Long): List { - return projectsService.getInvoiceInfoById(id) + @GetMapping("/getInvoiceInfo/{id}") + fun getInvoiceInfoById(@PathVariable id: Long): List> { + return invoiceService.getInvoiceInfoByMilestonePaymentId(id) + } + + @PostMapping("/pdf") + fun generatePDF(req: InvoicePDFReq, response: HttpServletResponse) { + response.characterEncoding = "utf-8" + response.contentType = "application/pdf" + + val out: OutputStream = response.outputStream + val pdf: Map = invoiceService.exportPDF(req) + + val jasperPrint: JasperPrint = pdf["report"] as JasperPrint + response.setHeader("Content-Disposition", "attachment; filename=\"${pdf["fileName"]}.pdf\"") + + + try { + out.write(JasperExportManager.exportReportToPdf(jasperPrint)) + } finally { + // Any necessary cleanup or additional processing can be done here + } } } \ No newline at end of file diff --git a/src/main/resources/pdf/invoicePDF.jrxml b/src/main/resources/pdf/invoicePDF.jrxml new file mode 100644 index 0000000..2eb703e --- /dev/null +++ b/src/main/resources/pdf/invoicePDF.jrxml @@ -0,0 +1,363 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +