@@ -0,0 +1,69 @@ | |||
package com.ffii.lioner.modules.lioner.pdf.entity; | |||
import java.time.LocalDate; | |||
import java.time.LocalDateTime; | |||
import com.ffii.core.entity.BaseEntity; | |||
import jakarta.persistence.Column; | |||
import jakarta.persistence.Entity; | |||
import jakarta.persistence.Inheritance; | |||
import jakarta.persistence.InheritanceType; | |||
import jakarta.persistence.Table; | |||
import jakarta.validation.constraints.NotBlank; | |||
import jakarta.validation.constraints.NotNull; | |||
@Entity | |||
@Table(name = "filled_form") | |||
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) | |||
public class Pdf extends BaseEntity<Long>{ | |||
@NotNull | |||
@Column | |||
private Long templateId; | |||
@Column | |||
private Long clientId; | |||
@NotNull | |||
@Column | |||
private Long fileId; | |||
@Column | |||
private String remarks; | |||
public Long getTemplateId() { | |||
return templateId; | |||
} | |||
public void setTemplateId(Long templateId) { | |||
this.templateId = templateId; | |||
} | |||
public Long getClientId() { | |||
return clientId; | |||
} | |||
public void setClientId(Long clientId) { | |||
this.clientId = clientId; | |||
} | |||
public Long getFileId() { | |||
return fileId; | |||
} | |||
public void setFileId(Long fileId) { | |||
this.fileId = fileId; | |||
} | |||
public String getRemarks() { | |||
return remarks; | |||
} | |||
public void setRemarks(String remarks) { | |||
this.remarks = remarks; | |||
} | |||
} | |||
@@ -0,0 +1,10 @@ | |||
package com.ffii.lioner.modules.lioner.pdf.entity; | |||
import java.util.List; | |||
// import java.util.Map; | |||
import com.ffii.core.support.AbstractRepository; | |||
public interface PdfRepository extends AbstractRepository<Pdf, Long> { | |||
List<Pdf> findAllByClientId(Long clientId); | |||
} |
@@ -0,0 +1,47 @@ | |||
package com.ffii.lioner.modules.lioner.pdf.req; | |||
public class UpdatePdfReq { | |||
private Long id; | |||
private Long templateId; | |||
private Long clientId; | |||
private String remarks; | |||
public Long getId() { | |||
return id; | |||
} | |||
public void setId(Long id) { | |||
this.id = id; | |||
} | |||
public Long getTemplateId() { | |||
return templateId; | |||
} | |||
public void setTemplateId(Long templateId) { | |||
this.templateId = templateId; | |||
} | |||
public Long getClientId() { | |||
return clientId; | |||
} | |||
public void setClientId(Long clientId) { | |||
this.clientId = clientId; | |||
} | |||
public String getRemarks() { | |||
return remarks; | |||
} | |||
public void setRemarks(String remarks) { | |||
this.remarks = remarks; | |||
} | |||
} |
@@ -0,0 +1,237 @@ | |||
package com.ffii.lioner.modules.lioner.pdf.service; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.time.LocalDate; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Date; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.stream.Collectors; | |||
import org.springframework.stereotype.Service; | |||
import org.springframework.web.multipart.MultipartFile; | |||
import org.springframework.core.io.ByteArrayResource; | |||
import org.springframework.core.io.ClassPathResource; | |||
import com.ffii.lioner.modules.lioner.pdf.entity.Pdf; | |||
import com.ffii.lioner.modules.lioner.pdf.entity.PdfRepository; | |||
import com.ffii.lioner.modules.lioner.entity.ImpEvent; | |||
import com.ffii.lioner.modules.lioner.pdf.req.UpdatePdfReq; | |||
import com.ffii.lioner.modules.lioner.service.FileService; | |||
import com.ffii.lioner.modules.common.SecurityUtils; | |||
import com.ffii.lioner.modules.common.service.AuditLogService; | |||
import com.ffii.lioner.modules.master.entity.SubDivision; | |||
import com.fasterxml.jackson.databind.ObjectMapper; | |||
import com.ffii.core.exception.NotFoundException; | |||
import com.ffii.core.exception.UnprocessableEntityException; | |||
import com.ffii.core.support.AbstractBaseEntityService; | |||
import com.ffii.core.support.JdbcDao; | |||
import com.ffii.core.utils.BeanUtils; | |||
import com.ffii.core.utils.JsonUtils; | |||
import com.ffii.core.utils.Params; | |||
import jakarta.persistence.Table; | |||
@Service | |||
public class PdfService extends AbstractBaseEntityService<Pdf, Long, PdfRepository> { | |||
private String pdf_path = "templates/pdf/"; | |||
private AuditLogService auditLogService; | |||
private FileService fileService; | |||
public PdfService(JdbcDao jdbcDao, PdfRepository repository, AuditLogService auditLogService, FileService fileService) { | |||
super(jdbcDao, repository); | |||
this.auditLogService = auditLogService; | |||
this.fileService = fileService; | |||
} | |||
// public Map<String,Object> getAuditLogObject(Map<String,Object> req){ | |||
// StringBuilder sql = new StringBuilder("SELECT" | |||
// + " e.id, " | |||
// + " e.created, " | |||
// + " e.createdBy AS createdById, " | |||
// + " u1.name AS createdBy, " | |||
// + " e.version, " | |||
// + " e.modified, " | |||
// + " e.modifiedBy AS modifiedById, " | |||
// + " e.deleted, " | |||
// + " u2.name AS modifiedBy, " | |||
// + " e.name, " | |||
// + " e.nameCht, " | |||
// + " e.description, " | |||
// + " e.region, " | |||
// + " e.organization, " | |||
// + " e.eventType, " | |||
// + " e.frequency, " | |||
// + " e.series, " | |||
// + " e.startDate, " | |||
// + " e.eventFrom, " | |||
// + " e.eventTo, " | |||
// + " e.applicationDeadline, " | |||
// + " e.nextApplicationDate, " | |||
// + " e.announcementDate, " | |||
// + " e.awardDate, " | |||
// + " e.reminderFlag, " | |||
// + " e.reminderThreshold, " | |||
// + " e.reminderInterval, " | |||
// + " e.reminderLimit " | |||
// + " FROM event e " | |||
// + " LEFT JOIN `user` u1 ON u1.id = e.createdBy " | |||
// + " LEFT JOIN `user` u2 ON u2.id = e.modifiedBy " | |||
// + " WHERE e.id = :id " | |||
// ); | |||
// return jdbcDao.queryForMap(sql.toString(), req).get(); | |||
// } | |||
public Map<String, Object> saveOrUpdate(String recordJson, MultipartFile file) { | |||
ObjectMapper objectMapper = new ObjectMapper(); | |||
UpdatePdfReq req; | |||
try { | |||
req = objectMapper.readValue(recordJson, UpdatePdfReq.class); | |||
} catch (Exception e) { | |||
throw new UnprocessableEntityException("Invalid JSON format"); | |||
} | |||
Pdf instance; | |||
// List<SubDivision> onUsingIdList = new ArrayList<SubDivision>(); | |||
if(req.getId()>0){ | |||
instance = find(req.getId()).get(); | |||
fileService.deleteFile(instance.getFileId()); // Delete previous record - WIP | |||
// onUsingIdList = this.getSelectedSubDivisionList(req.getId()); | |||
} | |||
else{ | |||
instance = new Pdf(); | |||
} | |||
System.out.println(req.getTemplateId()); | |||
BeanUtils.copyProperties(req, instance); | |||
Map<String, Object> pdf = new HashMap<>(); | |||
Map<String, Object> response = new HashMap<>(); | |||
if (file.isEmpty()) { | |||
throw new UnprocessableEntityException("No file uploaded."); | |||
// return ResponseEntity.badRequest().body(Map.of("message", "No file uploaded.")); | |||
} | |||
try { | |||
// Convert the MultipartFile to a byte array | |||
byte[] pdfData = file.getBytes(); | |||
MultipartFile[] files = new MultipartFile[1]; | |||
files[0] = file; | |||
try { | |||
pdf = fileService.saveOrUpdate(files, (long) 0, "pdf"); | |||
} catch (Exception e) { | |||
System.out.println(e); | |||
} | |||
// Save the PDF data to the database | |||
// File pdfForm = new PdfForm(); | |||
// pdfForm.setPdfData(pdfData); | |||
// pdfFormRepository.save(pdfForm); // Assuming you have autowired this repository | |||
response.put("message", "PDF saved successfully."); | |||
// response.put("id", pdfForm.getId().toString()); // Return the ID of the saved PDF | |||
} catch (IOException e) { | |||
throw new UnprocessableEntityException( | |||
"Failed to save PDF: " | |||
+ e | |||
); | |||
// e.printStackTrace(); | |||
// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) | |||
// .body(Map.of("message", "Failed to save PDF: " + e.getMessage())); | |||
} | |||
Object fileIdList = pdf.get("fileIdList"); | |||
List<?> list = (List<?>) fileIdList; | |||
Long fileId = (long) list.get(0); | |||
instance.setFileId(fileId); | |||
instance = save(instance); | |||
response = Map.of( | |||
"id", instance.getId() | |||
); | |||
return response; | |||
} | |||
public byte[] getTemplateForm() { | |||
// Ensure the template PDF is in src/main/resources/template/pdf | |||
ClassPathResource pdfFile = new ClassPathResource(pdf_path + "HSBC B24102883_fillable_Financial Needs Analysis for Individual Nov2024.pdf"); | |||
if (!pdfFile.exists()) { | |||
throw new UnprocessableEntityException("PDF template not found."); | |||
} | |||
try (InputStream is = pdfFile.getInputStream()) { | |||
return is.readAllBytes(); | |||
} catch (Exception e) { | |||
throw new UnprocessableEntityException("Fail to load PDF: " + e); | |||
} | |||
// return fileService.getfileBlob((long) 2); | |||
} | |||
public List<Map<String,Object>> list(Map<String, Object> args){ | |||
StringBuilder sql = new StringBuilder("SELECT" | |||
+ " id, " | |||
+ " templateId, " | |||
+ " fileId, " | |||
+ " created, " | |||
+ " modified " | |||
+ " FROM filled_form " | |||
+ " WHERE deleted = FALSE " | |||
); | |||
if (args != null) { | |||
if (args.containsKey("clientId")) | |||
sql.append(" AND clientId = :clientId "); | |||
if (args.containsKey("templateId")) | |||
sql.append(" AND templateId = :templateId "); | |||
if (args.containsKey("remarks")) | |||
sql.append(" AND remarks LIKE :remarks "); | |||
if (args.containsKey("createDateFrom")) | |||
sql.append(" AND created >= :createDateFrom "); | |||
if (args.containsKey("createDateTo")) | |||
sql.append(" AND created <= :createDateTo "); | |||
} | |||
// sql.append(" ORDER BY id desc "); | |||
return jdbcDao.queryForList(sql.toString(), args); | |||
} | |||
public Map<String, Object> loadPDF(Long id) { | |||
// Pdf pdfInstance = find(id).orElseThrow(NotFoundException::new); | |||
// byte[] blobBytes = fileService.getfileBlob(pdfInstance.getFileId()); | |||
StringBuilder sql = new StringBuilder("SELECT" | |||
+ " ff.*, " | |||
+ " fb.bytes AS blobValue" | |||
+ " FROM filled_form ff " | |||
+ " LEFT JOIN file_blob fb ON ff.fileId = fb.fileId " | |||
+ " WHERE ff.deleted = FALSE" | |||
+ " AND ff.id = :id " | |||
); | |||
// Map<String, Object> result = jdbcDao.queryForMap(sql.toString(), Map.of("id", id)).orElseThrow(NotFoundException::new); | |||
// result.put("blobValue", new ByteArrayResource(blobBytes)); | |||
// return result; | |||
return jdbcDao.queryForMap(sql.toString(), Map.of("id", id)).orElseThrow(NotFoundException::new); | |||
} | |||
public Integer getIdByName(String name) { | |||
StringBuilder sql = new StringBuilder("SELECT" | |||
+ " e.id " | |||
+ " FROM event e " | |||
+ " WHERE e.deleted = FALSE" | |||
+ " AND e.name = :name " | |||
); | |||
return jdbcDao.queryForInt(sql.toString(), Map.of("name", name)); | |||
} | |||
} |
@@ -0,0 +1,112 @@ | |||
package com.ffii.lioner.modules.lioner.pdf.web; | |||
import java.io.IOException; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.nio.file.Paths; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.UUID; | |||
import org.springframework.http.HttpHeaders; | |||
import org.springframework.http.HttpStatus; | |||
import org.springframework.http.MediaType; | |||
import org.springframework.http.ResponseEntity; | |||
import org.springframework.web.bind.ServletRequestBindingException; | |||
import org.springframework.web.bind.annotation.CrossOrigin; | |||
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 org.springframework.transaction.annotation.Isolation; | |||
import org.springframework.transaction.annotation.Transactional; | |||
import org.springframework.web.multipart.MultipartFile; | |||
import com.ffii.core.response.DataRes; | |||
import com.ffii.core.response.RecordsRes; | |||
import com.ffii.core.utils.CriteriaArgsBuilder; | |||
import jakarta.servlet.http.HttpServletRequest; | |||
import jakarta.validation.Valid; | |||
import com.ffii.core.exception.NotFoundException; | |||
import com.ffii.core.utils.Params; | |||
import com.ffii.lioner.modules.lioner.pdf.req.UpdatePdfReq; | |||
import com.ffii.lioner.modules.lioner.pdf.service.PdfService; | |||
@RestController | |||
@RequestMapping("/pdf") | |||
@CrossOrigin(origins = "", allowedHeaders = "") | |||
public class PdfController { | |||
private PdfService pdfService; | |||
public PdfController(PdfService pdfService) { | |||
this.pdfService = pdfService; | |||
} | |||
// @GetMapping("/{id}") | |||
// public List<Map<String, Object>> get(@PathVariable Long id) { | |||
// return Map.of( | |||
// Params.DATA, pdfService.findByClientId(id).orElseThrow(NotFoundException::new) | |||
// ); | |||
// } | |||
@GetMapping | |||
public RecordsRes<Map<String, Object>> list(HttpServletRequest request) throws ServletRequestBindingException { | |||
return new RecordsRes<>(pdfService.list( | |||
CriteriaArgsBuilder.withRequest(request) | |||
.addInteger("clientId") | |||
.addInteger("templateId") | |||
.addDate("createDateFrom") | |||
.addDate("createDateTo") | |||
.addStringLike("remarks") | |||
.build())); | |||
} | |||
@GetMapping("/{id}") | |||
public Map<String, Object> get(@PathVariable Long id) { | |||
return pdfService.loadPDF(id); | |||
// return Map.of( | |||
// Params.DATA, pdfService.loadPDF(id) | |||
// ); | |||
} | |||
// Endpoint to serve the initial template PDF | |||
@GetMapping(value = "/template", produces = MediaType.APPLICATION_PDF_VALUE) | |||
public ResponseEntity<byte[]> getPdfTemplate() throws IOException { | |||
byte[] pdfBytes = pdfService.getTemplateForm(); | |||
HttpHeaders headers = new HttpHeaders(); | |||
headers.setContentDispositionFormData("inline", "template_form.pdf"); | |||
headers.setContentType(MediaType.APPLICATION_PDF); | |||
return new ResponseEntity<>(pdfBytes, headers, HttpStatus.OK); | |||
} | |||
// Endpoint to receive the filled PDF from the frontend | |||
@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) | |||
@PostMapping(value = "/save", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) | |||
// public Map<String, Object> saveFilledPdf(@RequestParam("pdf") @Valid UpdatePdfReq req, @RequestParam("file") MultipartFile file) { | |||
// public Map<String, Object> saveFilledPdf(@RequestParam("record") UpdatePdfReq req, @RequestParam("file") MultipartFile file) { | |||
public Map<String, Object> saveFilledPdf(@RequestParam("record") String recordJson, @RequestParam("file") MultipartFile file) { | |||
return Map.of( | |||
Params.DATA,pdfService.saveOrUpdate(recordJson, file) | |||
); | |||
} | |||
// Optional: Endpoint to receive just the form data (e.g., JSON) | |||
@PostMapping("/saveFormData") | |||
public ResponseEntity<Map<String, String>> saveFormData(@RequestBody Map<String, Object> formData) { | |||
System.out.println("Received form data: " + formData); | |||
Map<String, String> response = new HashMap<>(); | |||
response.put("message", "Form data received and processed successfully."); | |||
return ResponseEntity.ok(response); | |||
} | |||
} |
@@ -1,86 +0,0 @@ | |||
package com.ffii.lioner.modules.lioner.web; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.nio.file.Paths; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.UUID; | |||
import org.springframework.core.io.ClassPathResource; | |||
import org.springframework.http.HttpHeaders; | |||
import org.springframework.http.HttpStatus; | |||
import org.springframework.http.MediaType; | |||
import org.springframework.http.ResponseEntity; | |||
import org.springframework.web.bind.annotation.CrossOrigin; | |||
import org.springframework.web.bind.annotation.GetMapping; | |||
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 org.springframework.web.multipart.MultipartFile; | |||
@RestController | |||
@RequestMapping("/pdf") | |||
@CrossOrigin(origins = "", allowedHeaders = "") | |||
public class PdfController { | |||
private String pdf_path = "templates/pdf/"; | |||
// Endpoint to serve the initial template PDF | |||
@GetMapping(value = "/template", produces = MediaType.APPLICATION_PDF_VALUE) | |||
public ResponseEntity<byte[]> getPdfTemplate() throws IOException { | |||
// Ensure your template PDF is in src/main/resources/static/ | |||
ClassPathResource pdfFile = new ClassPathResource(pdf_path + "HSBC B24102883_fillable_Financial Needs Analysis for Individual Nov2024.pdf"); | |||
if (!pdfFile.exists()) { | |||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("PDF template not found.".getBytes()); | |||
} | |||
byte[] pdfBytes; | |||
try (InputStream is = pdfFile.getInputStream()) { | |||
pdfBytes = is.readAllBytes(); | |||
} | |||
HttpHeaders headers = new HttpHeaders(); | |||
headers.setContentDispositionFormData("inline", "template_form.pdf"); | |||
headers.setContentType(MediaType.APPLICATION_PDF); | |||
return new ResponseEntity<>(pdfBytes, headers, HttpStatus.OK); | |||
} | |||
// Endpoint to receive the filled PDF from the frontend | |||
@PostMapping(value = "/save", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) | |||
public ResponseEntity<Map<String, String>> saveFilledPdf(@RequestParam("file") MultipartFile file) { | |||
if (file.isEmpty()) { | |||
return ResponseEntity.badRequest().body(Map.of("message", "No file uploaded.")); | |||
} | |||
try { | |||
// Generate a unique filename and save to an 'uploads' directory | |||
String fileName = "filled_form_" + UUID.randomUUID().toString() + ".pdf"; | |||
Path filePath = Paths.get("uploads", fileName); | |||
Files.createDirectories(filePath.getParent()); // Ensure directory exists | |||
Files.copy(file.getInputStream(), filePath); | |||
Map<String, String> response = new HashMap<>(); | |||
response.put("message", "PDF saved successfully: " + fileName); | |||
response.put("filePath", filePath.toString()); | |||
return ResponseEntity.ok(response); | |||
} catch (IOException e) { | |||
e.printStackTrace(); | |||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) | |||
.body(Map.of("message", "Failed to save PDF: " + e.getMessage())); | |||
} | |||
} | |||
// Optional: Endpoint to receive just the form data (e.g., JSON) | |||
@PostMapping("/saveFormData") | |||
public ResponseEntity<Map<String, String>> saveFormData(@RequestBody Map<String, Object> formData) { | |||
System.out.println("Received form data: " + formData); | |||
Map<String, String> response = new HashMap<>(); | |||
response.put("message", "Form data received and processed successfully."); | |||
return ResponseEntity.ok(response); | |||
} | |||
} |
@@ -0,0 +1,20 @@ | |||
--liquibase formatted sql | |||
--changeset kelvin:add filled form table | |||
CREATE TABLE `filled_form` ( | |||
`id` int NOT NULL AUTO_INCREMENT, | |||
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||
`createdBy` int DEFAULT NULL, | |||
`version` int NOT NULL DEFAULT '0', | |||
`modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||
`modifiedBy` int DEFAULT NULL, | |||
`deleted` tinyint(1) NOT NULL DEFAULT '0', | |||
`templateId` int(11) NOT NULL, | |||
`clientId` int(11), | |||
`fileId` int(11) NOT NULL, | |||
`remarks` VARCHAR(600), | |||
PRIMARY KEY (`id`) | |||
) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
@@ -0,0 +1,7 @@ | |||
--liquibase formatted sql | |||
--changeset kelvin:update client table | |||
ALTER TABLE `lionerdb`.`client` | |||
CHANGE COLUMN `createdBy` `createdBy` INT NULL DEFAULT NULL , | |||
CHANGE COLUMN `modifiedBy` `modifiedBy` INT NULL DEFAULT NULL ; |