package net.gepafin.tendermanagement.dao; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.CopyObjectRequest; import jakarta.persistence.criteria.Predicate; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.*; import net.gepafin.tendermanagement.enums.*; import net.gepafin.tendermanagement.model.request.CompanyDocumentRequest; import net.gepafin.tendermanagement.model.request.VersionHistoryRequest; import net.gepafin.tendermanagement.model.response.DocumentCategoryResponse; import net.gepafin.tendermanagement.model.response.CompanyDocumentResponseBean; import net.gepafin.tendermanagement.model.response.DocumentResponseBean; import net.gepafin.tendermanagement.model.response.UploadFileOnAmazonS3Response; import net.gepafin.tendermanagement.repositories.CompanyDocumentRepository; import net.gepafin.tendermanagement.repositories.DocumentRepository; import net.gepafin.tendermanagement.service.AmazonS3Service; import net.gepafin.tendermanagement.service.ApplicationService; import net.gepafin.tendermanagement.service.CompanyService; import net.gepafin.tendermanagement.service.impl.AmazonS3ServiceImpl; import net.gepafin.tendermanagement.util.LoggingUtil; import net.gepafin.tendermanagement.util.Utils; import net.gepafin.tendermanagement.util.Validator; import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException; import net.gepafin.tendermanagement.web.rest.api.errors.ResourceNotFoundException; import net.gepafin.tendermanagement.web.rest.api.errors.Status; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import static net.gepafin.tendermanagement.util.Utils.setIfUpdated; @Slf4j @Component public class CompanyDocumentDao { @Autowired private S3PathConfig s3ConfigBean; @Autowired private AmazonS3Service amazonS3Service; @Autowired private CompanyDocumentRepository companyDocumentRepository; @Autowired private LoggingUtil loggingUtil; @Autowired private HttpServletRequest request; @Autowired private DocumentCategoryDao categoryDao; @Autowired private CompanyService companyService; @Value("${aws.s3.bucket.name}") private String bucketName; @Autowired private AmazonS3Client s3Client; @Autowired private DocumentRepository documentRepository; @Autowired private CallDao callDao; @Autowired private ApplicationService applicationService; @Autowired private AmazonS3ServiceImpl amazonS3ServiceImpl; @Autowired private Validator validator; public List uploadFileForCompany(HttpServletRequest request, Long userId, List files, Long companyId, Long documentCategoryId, CompanyDocumentTypeEnum companyDocumentSourceTypeEnum, LocalDateTime expirationDate,String name){ DocumentCategoryEntity categoryEntity = categoryDao.validateCategory(documentCategoryId); validator.validateUserWithCompany(request,companyId); UserWithCompanyEntity userWithCompanyEntity=companyService.getUserWithCompany(userId,companyId); LocalDateTime currentDate = LocalDateTime.now(); if (expirationDate.isBefore(currentDate)) { throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.INVALID_EXPIRATION_DATE)); } List companyDocumentEntities = new ArrayList<>(); for (MultipartFile file : files){ UploadFileOnAmazonS3Response uploadFileOnAmazonS3Response = uploadFileOnAmazonS3(file, companyDocumentSourceTypeEnum, companyId); if (uploadFileOnAmazonS3Response != null) { CompanyDocumentEntity companyDocumentEntity = new CompanyDocumentEntity(); companyDocumentEntity.setFileName(uploadFileOnAmazonS3Response.getFileName()); companyDocumentEntity.setCompanyId(companyId); companyDocumentEntity.setType(companyDocumentSourceTypeEnum.getValue()); companyDocumentEntity.setFilePath(uploadFileOnAmazonS3Response.getFilePath()); companyDocumentEntity.setIsDeleted(false); companyDocumentEntity.setUploadedBy(userId); companyDocumentEntity.setName(name); if (expirationDate.isBefore(currentDate.plusDays(7))) { companyDocumentEntity.setStatus(CompanyDocumentStatusEnum.DUE.getValue()); } else { companyDocumentEntity.setStatus(CompanyDocumentStatusEnum.VALID.getValue()); } companyDocumentEntity.setCategoryEntity(categoryEntity); companyDocumentEntity.setUserWithCompany(userWithCompanyEntity); companyDocumentEntity.setExpirationDate(expirationDate); companyDocumentEntities.add(companyDocumentEntity); } } companyDocumentRepository.saveAll(companyDocumentEntities); /** This code is responsible for adding a version history log for the "Upload company document" operation. **/ companyDocumentEntities.forEach(entity -> loggingUtil.addVersionHistory( VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).oldData(null).newData(entity).build())); return companyDocumentEntities.stream() .map(this::convertToCompanyDocumentResponseBean) .collect(Collectors.toList()); } private UploadFileOnAmazonS3Response uploadFileOnAmazonS3(MultipartFile file, CompanyDocumentTypeEnum type, Long companyId) { try { String s3Path = generateS3PathForCompany(type,companyId); log.info("Generated S3 path {}", s3Path); return amazonS3Service.uploadFileOnAmazonS3(s3Path, file); } catch (Exception e) { throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.UPLOAD_ERROR_S3)); } } public String generateS3PathForCompany(CompanyDocumentTypeEnum typeOfDocument, Long companyId) { try { return s3ConfigBean.generateCompanyDocumentPath(typeOfDocument, companyId); } catch (IllegalArgumentException e) { throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.S3_PATH_GENERATION_ERROR_MSG)); } } public CompanyDocumentEntity validateCompanyDocument(Long id) { return companyDocumentRepository.findByIdAndNotDeleted(id).orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.COMPANY_DOCUMENT_NOT_FOUND))); } public CompanyDocumentResponseBean convertToCompanyDocumentResponseBean(CompanyDocumentEntity entity) { CompanyDocumentResponseBean responseBean = new CompanyDocumentResponseBean(); DocumentCategoryEntity categoryEntity = entity.getCategoryEntity(); DocumentCategoryResponse responseCategory = categoryDao.convertToResponseBean(categoryEntity); responseBean.setId(entity.getId()); responseBean.setFileName(entity.getFileName()); responseBean.setType(CompanyDocumentTypeEnum.valueOf(entity.getType())); responseBean.setFilePath(entity.getFilePath()); responseBean.setCompanyId(entity.getCompanyId()); responseBean.setExpirationDate(entity.getExpirationDate()); responseBean.setStatus(entity.getStatus()); responseBean.setUploadedBy(entity.getUploadedBy()); responseBean.setCategory(responseCategory); responseBean.setName(entity.getName()); responseBean.setUserWithCompanyId(entity.getUserWithCompany().getId()); responseBean.setCreatedDate(entity.getCreatedDate()); responseBean.setUpdatedDate(entity.getUpdatedDate()); return responseBean; } public CompanyDocumentResponseBean updateCompanyDocument(HttpServletRequest request,Long companyDocumentId, CompanyDocumentRequest companyDocumentRequest){ CompanyDocumentEntity companyDocumentEntity = validateCompanyDocument(companyDocumentId); validator.validateUserWithCompany(request,companyDocumentEntity.getCompanyId()); CompanyDocumentEntity oldCompanyDocumentData = Utils.getClonedEntityForData(companyDocumentEntity); LocalDateTime currentDate = LocalDateTime.now(); if (companyDocumentRequest.getExpirationDate() != null) { if (companyDocumentRequest.getExpirationDate().isBefore(currentDate)) { throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.INVALID_EXPIRATION_DATE)); } companyDocumentEntity.setExpirationDate(companyDocumentRequest.getExpirationDate()); if (companyDocumentRequest.getExpirationDate().isBefore(currentDate.plusDays(7))) { companyDocumentEntity.setStatus(CompanyDocumentStatusEnum.DUE.getValue()); } else { companyDocumentEntity.setStatus(CompanyDocumentStatusEnum.VALID.getValue()); } } if (companyDocumentRequest.getCategoryId() != null && companyDocumentRequest.getCategoryId() >0) { DocumentCategoryEntity categoryEntity = categoryDao.validateCategory(companyDocumentRequest.getCategoryId()); setIfUpdated(companyDocumentEntity::getCategoryEntity, companyDocumentEntity::setCategoryEntity, categoryEntity); } setIfUpdated(companyDocumentEntity::getName, companyDocumentEntity::setName, companyDocumentRequest.getName()); companyDocumentRepository.save(companyDocumentEntity); /** This code is responsible for adding a version history log for the "updating company document" operation. **/ loggingUtil.addVersionHistory( VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCompanyDocumentData).newData(companyDocumentEntity).build()); return convertToCompanyDocumentResponseBean(companyDocumentEntity); } public CompanyDocumentResponseBean getCompanyDocument(UserEntity user ,Long companyDocumentId) { CompanyDocumentEntity companyDocumentEntity = validateCompanyDocument(companyDocumentId); validator.validateUserWithCompany(request,companyDocumentEntity.getCompanyId()); return convertToCompanyDocumentResponseBean(companyDocumentEntity); } public void deleteCompanyFile(Long companyDocumentId){ CompanyDocumentEntity companyDocumentEntity = validateCompanyDocument(companyDocumentId); deleteCompanyFileFromS3(companyDocumentEntity); } public void deleteCompanyFileFromS3(CompanyDocumentEntity companyDocumentEntity){ try { CompanyDocumentEntity oldCompanyDocumentEntity = Utils.getClonedEntityForData(companyDocumentEntity); validator.validateUserWithCompany(request,companyDocumentEntity.getCompanyId()); String oldCompanyDocumentPath = companyDocumentEntity.getFilePath(); String newS3Path = s3ConfigBean.generateCompanyDocumentPathForOther(CompanyDocSourceTypeEnum.valueOf("DELETED_" + companyDocumentEntity.getType().toUpperCase()), companyDocumentEntity.getCompanyId()); UploadFileOnAmazonS3Response response = amazonS3Service.moveFile(companyDocumentEntity.getFileName(), oldCompanyDocumentPath, newS3Path); companyDocumentEntity.setFileName(response.getFileName()); companyDocumentEntity.setFilePath(response.getFilePath()); companyDocumentEntity.setIsDeleted(true); companyDocumentRepository.save(companyDocumentEntity); /** This code is responsible for adding a version history log for the "Soft delete document" operation. **/ loggingUtil.addVersionHistory( VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.SOFT_DELETE).oldData(oldCompanyDocumentEntity).newData(companyDocumentEntity).build()); log.info("File for company document ID {} successfully moved to deleted folder.", companyDocumentEntity.getId()); } catch (Exception e) { log.error("Error moving file for company document ID {} to deleted folder: {}", companyDocumentEntity.getId(), e.getMessage()); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.ERROR_MOVING_FILE_TO_DELETED_FOLDER)); } } public UserActionContextEnum getUserActionContextEnum(CompanyDocumentTypeEnum type){ UserActionContextEnum userActionContext = null; if (type.equals(CompanyDocumentTypeEnum.COMPANY_DOCUMENT)){ userActionContext = UserActionContextEnum.UPLOAD_COMPANY_DOCUMENT; } else if(type.equals(CompanyDocumentTypeEnum.PERSONAL_DOCUMENT)){ userActionContext = UserActionContextEnum.UPLOAD_COMPANY_PERSONAL_DOCUMENT; } return userActionContext; } public DocumentResponseBean validateAndDuplicateCompanyDocument(HttpServletRequest request , Long userId ,Long companyDocumentId , Long applicationId , DocumentTypeEnum documentTypeEnum){ ApplicationEntity applicationEntity = applicationService.validateApplication(applicationId); CompanyDocumentEntity companyDocumentEntity = validateCompanyDocument(companyDocumentId); validator.validateUserWithCompany(request,companyDocumentEntity.getCompanyId()); String oldS3Path = companyDocumentEntity.getFilePath(); String newS3Path = s3ConfigBean.generateDocumentPath(DocumentSourceTypeEnum.APPLICATION,applicationEntity.getCall().getId(),applicationId,0L); log.info("Original Paths - oldPath: {}, newPath: {}", oldS3Path, newS3Path); oldS3Path = amazonS3ServiceImpl.decodeS3Key(amazonS3ServiceImpl.cleanOldPath(oldS3Path)); newS3Path = amazonS3ServiceImpl.cleanNewPath(oldS3Path, newS3Path); log.info("Moving file from {} to {} in bucket {}", oldS3Path, newS3Path, bucketName); CopyObjectRequest copyRequest = new CopyObjectRequest(bucketName, oldS3Path, bucketName, newS3Path); s3Client.copyObject(copyRequest); log.info("File copied successfully from {} to {}", oldS3Path, newS3Path); DocumentEntity entity = new DocumentEntity(); entity.setFilePath(newS3Path); entity.setFileName(companyDocumentEntity.getFileName()); entity.setSource(DocumentSourceTypeEnum.APPLICATION.getValue()); entity.setType(documentTypeEnum.getValue()); entity.setSourceId(applicationId); entity.setUploadedBy(userId); documentRepository.save(entity); /** This code is responsible for adding a version history log for the "inserting data" operation. **/ loggingUtil.addVersionHistory( VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.INSERT).oldData(null).newData(entity).build()); DocumentResponseBean responseBean = callDao.convertToDocumentResponseBean(entity); return responseBean; } public List getAllCompanyDocument(UserEntity user , Long companyId, CompanyDocumentTypeEnum typeEnum){ validator.validateUserWithCompany(request, companyId); companyService.validateCompany(companyId); Specification spec = filterCompanyDocuments(companyId, user.getId(), typeEnum); List companyDocumentEntities = companyDocumentRepository.findAll(spec); return companyDocumentEntities.stream() .map(this::convertToCompanyDocumentResponseBean) .collect(Collectors.toList()); } private Specification filterCompanyDocuments(Long companyId, Long userId, CompanyDocumentTypeEnum typeEnum) { return (root, query, builder) -> { Predicate predicate = builder.equal(root.get("companyId"), companyId); predicate = builder.and(predicate, builder.isFalse(root.get("isDeleted"))); if (typeEnum != null) { if (typeEnum == CompanyDocumentTypeEnum.COMPANY_DOCUMENT) { // Case 1: Fetch only COMPANY_DOCUMENT type documents for the given company predicate = builder.and(predicate, builder.equal(root.get("type"), CompanyDocumentTypeEnum.COMPANY_DOCUMENT.getValue())); } else if (typeEnum == CompanyDocumentTypeEnum.PERSONAL_DOCUMENT) { // Case 2: Fetch only PERSONAL_DOCUMENT type documents for the logged-in user predicate = builder.and( predicate, builder.equal(root.get("type"), CompanyDocumentTypeEnum.PERSONAL_DOCUMENT.getValue()), builder.equal(root.get("userWithCompany").get("userId"), userId) ); } } // Case 3: If typeEnum is null, fetch all documents for the company and personal documents for the user Predicate companyPredicate = builder.equal(root.get("companyId"), companyId); Predicate personalPredicate = builder.and( builder.equal(root.get("type"), CompanyDocumentTypeEnum.PERSONAL_DOCUMENT.getValue()), builder.equal(root.get("userWithCompany").get("userId"), userId) ); predicate = builder.and(predicate, builder.or(companyPredicate, personalPredicate)); return predicate; }; } }