From d84b08411d1bdf48ce5672d7bc4bcde70eb6b13c Mon Sep 17 00:00:00 2001 From: "adel.ka" Date: Tue, 9 Sep 2025 09:15:53 +0300 Subject: [PATCH] SUPPORT-9368: new journal --- backend/pom.xml | 39 +- .../EmployeeInfoFileUploadService.java | 4 +- .../controller/ValidationFileController.java | 88 ++++ .../ervu/exception/ExportException.java | 14 + .../ru/micord/ervu/journal/JournalDto.java | 51 ++- .../micord/ervu/journal/JournalFileInfo.java | 10 + .../ervu/journal/mapper/JournalDtoMapper.java | 10 +- .../ervu/kafka/ReplyingKafkaConfig.java | 119 ++++-- .../kafka/controller/ErvuKafkaController.java | 2 +- .../kafka/service/ReplyingKafkaService.java | 11 +- ...mpl.java => BaseReplyingKafkaService.java} | 39 +- .../impl/ErvuReplyingKafkaService.java | 31 ++ .../impl/ValidateReplyingKafkaService.java | 32 ++ .../ervu/model/ValidateExportRequest.java | 4 + .../ervu/model/ValidateExportResponse.java | 28 ++ .../micord/ervu/property/grid/FilterType.java | 3 +- .../esia/service/EsiaAuthService.java | 2 +- .../ervu/service/InteractionServiceImpl.java | 2 - .../ervu/service/ValidationFileService.java | 48 +++ .../JournalInMemoryStaticGridLoadService.java | 11 +- .../ervu/component/grid/InMemoryStaticGrid.ts | 24 +- .../filter/CustomGridColumnFilterUtils.ts | 3 + .../grid/filter/FileAvailableFilterComp.ts | 77 ++++ .../grid/renderer/FileDownloadCellRenderer.ts | 36 ++ frontend/src/ts/ervu/service/FilterService.ts | 17 + .../ts/ervu/service/ValidateFileService.ts | 34 ++ frontend/src/ts/modules/app/app.module.ts | 3 +- pom.xml | 15 + .../business-model/Журнал взаимодействия.page | 395 +++++++++++++++++- 29 files changed, 1045 insertions(+), 107 deletions(-) create mode 100644 backend/src/main/java/ru/micord/ervu/controller/ValidationFileController.java create mode 100644 backend/src/main/java/ru/micord/ervu/exception/ExportException.java rename backend/src/main/java/ru/micord/ervu/kafka/service/impl/{BaseReplyingKafkaServiceImpl.java => BaseReplyingKafkaService.java} (54%) create mode 100644 backend/src/main/java/ru/micord/ervu/kafka/service/impl/ErvuReplyingKafkaService.java create mode 100644 backend/src/main/java/ru/micord/ervu/kafka/service/impl/ValidateReplyingKafkaService.java create mode 100644 backend/src/main/java/ru/micord/ervu/model/ValidateExportRequest.java create mode 100644 backend/src/main/java/ru/micord/ervu/model/ValidateExportResponse.java create mode 100644 backend/src/main/java/ru/micord/ervu/service/ValidationFileService.java create mode 100644 frontend/src/ts/ervu/component/grid/filter/FileAvailableFilterComp.ts create mode 100644 frontend/src/ts/ervu/component/grid/renderer/FileDownloadCellRenderer.ts create mode 100644 frontend/src/ts/ervu/service/ValidateFileService.ts diff --git a/backend/pom.xml b/backend/pom.xml index d1592c8b..77e756f5 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -97,14 +97,6 @@ org.xerial.snappy snappy-java - - org.apache.kafka - kafka-clients - - - org.xerial.snappy - snappy-java - ru.cg.webbpm.modules inject @@ -206,9 +198,24 @@ org.apache.httpcomponents httpmime + + com.google.protobuf + protobuf-java + + + com.google.protobuf + protobuf-java-util + ${project.parent.artifactId} + + + kr.motd.maven + os-maven-plugin + 1.6.2 + + maven-compiler-plugin @@ -237,6 +244,22 @@ + + org.xolstice.maven.plugins + protobuf-maven-plugin + + + + compile + + + + + com.google.protobuf:protoc:4.27.3:exe:${os.detected.classifier} + ${project.parent.basedir}/backend/src/main/resources + ${project.parent.basedir}/backend/target/generated-sources/java/protobuf + + diff --git a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java index b7952719..40be9cca 100644 --- a/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java +++ b/backend/src/main/java/ervu/service/fileupload/EmployeeInfoFileUploadService.java @@ -87,7 +87,7 @@ public class EmployeeInfoFileUploadService { "messages/common_errors_messages"); private final WebDavClient webDavClient; private final EmployeeInfoKafkaMessageService employeeInfoKafkaMessageService; - private final ReplyingKafkaService replyingKafkaService; + private final ReplyingKafkaService replyingKafkaService; private final KafkaTemplate kafkaTemplate; private final InteractionService interactionService; private final UlDataService ulDataService; @@ -110,7 +110,7 @@ public class EmployeeInfoFileUploadService { public EmployeeInfoFileUploadService( WebDavClient webDavClient, EmployeeInfoKafkaMessageService employeeInfoKafkaMessageService, - ReplyingKafkaService replyingKafkaService, + ReplyingKafkaService replyingKafkaService, InteractionService interactionService, UlDataService ulDataService, AuditService auditService, diff --git a/backend/src/main/java/ru/micord/ervu/controller/ValidationFileController.java b/backend/src/main/java/ru/micord/ervu/controller/ValidationFileController.java new file mode 100644 index 00000000..6384ea29 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/controller/ValidationFileController.java @@ -0,0 +1,88 @@ +package ru.micord.ervu.controller; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.HttpServletRequest; + +import org.apache.kafka.shaded.com.google.protobuf.ByteString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.micord.ervu.audit.constants.AuditConstants; +import ru.micord.ervu.audit.service.AuditService; +import ru.micord.ervu.model.ValidateExportResponse; +import ru.micord.ervu.security.webbpm.jwt.UserIdsPair; +import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil; +import ru.micord.ervu.service.ValidationFileService; + +@RestController +@RequestMapping("/validate") +public class ValidationFileController { + private static final Logger LOGGER = LoggerFactory.getLogger(ValidationFileController.class); + private final AuditService auditService; + private final ValidationFileService validationFileService; + + public ValidationFileController(AuditService auditService, + ValidationFileService validationFileService) { + this.auditService = auditService; + this.validationFileService = validationFileService; + } + + @GetMapping("/export/{fileId}") + public ResponseEntity exportFile(HttpServletRequest servletRequest, + @PathVariable String fileId) { + int size = 0; + String fileName = null; + String status = null; + + try { + if (!StringUtils.hasText(fileId)) { + return ResponseEntity.notFound().build(); + } + + UserIdsPair userIdsPair = SecurityUtil.getUserIdsPair(); + String ervuId = userIdsPair.getErvuId(); + ValidateExportResponse validateExportResponse = validationFileService.exportFile(ervuId, + fileId + ); + + if (!validateExportResponse.hasFile()) { + LOGGER.error("Response does not contain file content for fileId: {}, user: {}", fileId, ervuId); + status = AuditConstants.FAILURE_STATUS_TYPE; + return ResponseEntity.noContent().build(); + } + + ByteString file = validateExportResponse.getFile(); + size = file.size(); + fileName = validateExportResponse.getFileName(); + String encodedFilename = URLEncoder.encode(fileName, StandardCharsets.UTF_8); + InputStreamResource resource = new InputStreamResource(file.newInput()); + + status = AuditConstants.SUCCESS_STATUS_TYPE; + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename*=UTF-8''" + encodedFilename + ) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(resource); + } + catch (Exception e) { + if (status == null) { + status = AuditConstants.FAILURE_STATUS_TYPE; + } + throw e; + } + finally { + auditService.processDownloadEvent(servletRequest, size, fileName, 2, status, null); + } + } +} diff --git a/backend/src/main/java/ru/micord/ervu/exception/ExportException.java b/backend/src/main/java/ru/micord/ervu/exception/ExportException.java new file mode 100644 index 00000000..5603f06f --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/exception/ExportException.java @@ -0,0 +1,14 @@ +package ru.micord.ervu.exception; + +/** + * @author Adel Kalimullin + */ +public class ExportException extends RuntimeException { + public ExportException(String message) { + super(message); + } + + public ExportException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/backend/src/main/java/ru/micord/ervu/journal/JournalDto.java b/backend/src/main/java/ru/micord/ervu/journal/JournalDto.java index 6adc2e2f..cbc23a70 100644 --- a/backend/src/main/java/ru/micord/ervu/journal/JournalDto.java +++ b/backend/src/main/java/ru/micord/ervu/journal/JournalDto.java @@ -2,14 +2,18 @@ package ru.micord.ervu.journal; public class JournalDto { + private Integer documentNumber; private String fileId; private String departureDateTime; private String fileName; private Integer filePatternCode; private String senderFio; private String status; - public Integer filesSentCount; - public Integer acceptedFilesCount; + private Integer rowsCount; + private Integer rowsSuccess; + private Integer rowsError; + private Boolean hasFailedRows; + public String getDepartureDateTime() { return departureDateTime; @@ -65,21 +69,48 @@ public class JournalDto { return this; } - public Integer getFilesSentCount() { - return filesSentCount; + public Integer getRowsCount() { + return rowsCount; } - public JournalDto setFilesSentCount(Integer filesSentCount) { - this.filesSentCount = filesSentCount; + public JournalDto setRowsCount(Integer rowsCount) { + this.rowsCount = rowsCount; return this; } - public Integer getAcceptedFilesCount() { - return acceptedFilesCount; + public Integer getRowsSuccess() { + return rowsSuccess; } - public JournalDto setAcceptedFilesCount(Integer acceptedFilesCount) { - this.acceptedFilesCount = acceptedFilesCount; + public JournalDto setRowsSuccess(Integer rowsSuccess) { + this.rowsSuccess = rowsSuccess; + return this; + } + + public Integer getRowsError() { + return rowsError; + } + + public JournalDto setRowsError(Integer rowsError) { + this.rowsError = rowsError; + return this; + } + + public Integer getDocumentNumber() { + return documentNumber; + } + + public JournalDto setDocumentNumber(Integer documentNumber) { + this.documentNumber = documentNumber; + return this; + } + + public Boolean isHasFailedRows() { + return hasFailedRows; + } + + public JournalDto setHasFailedRows(Boolean hasFailedRows) { + this.hasFailedRows = hasFailedRows; return this; } } diff --git a/backend/src/main/java/ru/micord/ervu/journal/JournalFileInfo.java b/backend/src/main/java/ru/micord/ervu/journal/JournalFileInfo.java index 6dc27a6e..7ffb5251 100644 --- a/backend/src/main/java/ru/micord/ervu/journal/JournalFileInfo.java +++ b/backend/src/main/java/ru/micord/ervu/journal/JournalFileInfo.java @@ -14,6 +14,7 @@ public class JournalFileInfo { private SenderInfo senderInfo; private Integer rowsCount; //Общее количество записей отправленных в файле private Integer rowsSuccess; //Количество записей принятых в файле + private Integer rowsError; //Количество записей непринятых в файле public List getPackFiles() { return packFiles; @@ -51,6 +52,15 @@ public class JournalFileInfo { return this; } + public Integer getRowsError() { + return rowsError; + } + + public JournalFileInfo setRowsError(Integer rowsError) { + this.rowsError = rowsError; + return this; + } + @JsonIgnoreProperties(ignoreUnknown = true) public static class JournalFileDetails { private String fileId; //ИД файла полученный при создании записи о файле в реестр организаций (в ЕРВУ) diff --git a/backend/src/main/java/ru/micord/ervu/journal/mapper/JournalDtoMapper.java b/backend/src/main/java/ru/micord/ervu/journal/mapper/JournalDtoMapper.java index cbc144d9..332e1e28 100644 --- a/backend/src/main/java/ru/micord/ervu/journal/mapper/JournalDtoMapper.java +++ b/backend/src/main/java/ru/micord/ervu/journal/mapper/JournalDtoMapper.java @@ -24,8 +24,9 @@ public class JournalDtoMapper { ) ) .setStatus(journalFileDetails.getFileStatus().getStatus()) - .setFilesSentCount(journalFileInfo.getRowsCount()) - .setAcceptedFilesCount(journalFileInfo.getRowsSuccess()); + .setRowsCount(journalFileInfo.getRowsCount()) + .setRowsSuccess(journalFileInfo.getRowsSuccess()) + .setRowsError(journalFileInfo.getRowsError()); } public static JournalDto mapToJournalDto(InteractionLogRecord record) { @@ -35,8 +36,9 @@ public class JournalDtoMapper { .setFilePatternCode(Integer.valueOf(record.getForm())) .setSenderFio(record.getSender()) .setStatus(record.getStatus()) - .setFilesSentCount(record.getRecordsSent()) - .setAcceptedFilesCount(record.getRecordsAccepted()) + .setRowsCount(0) + .setRowsSuccess(0) + .setRowsError(0) .setFileId(record.getFileId()); } } diff --git a/backend/src/main/java/ru/micord/ervu/kafka/ReplyingKafkaConfig.java b/backend/src/main/java/ru/micord/ervu/kafka/ReplyingKafkaConfig.java index 3f6df834..0c7b28a9 100644 --- a/backend/src/main/java/ru/micord/ervu/kafka/ReplyingKafkaConfig.java +++ b/backend/src/main/java/ru/micord/ervu/kafka/ReplyingKafkaConfig.java @@ -4,8 +4,10 @@ import org.apache.kafka.clients.CommonClientConfigs; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.common.config.SaslConfigs; +import org.apache.kafka.common.serialization.BytesDeserializer; import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringSerializer; +import org.apache.kafka.common.utils.Bytes; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -54,63 +56,102 @@ public class ReplyingKafkaConfig { private String saslMechanism; @Value("${av.kafka.download.response}") private String avReplyTopic; + @Value("${ervu.kafka.validate.export.reply.topic}") + private String validateReplyTopic; - @Bean("ervuProducerFactory") + @Bean public ProducerFactory producerFactory() { - Map configProps = new HashMap<>(); - configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); - configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + Map configProps = commonProducerConfig(); configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); - configProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol); - configProps.put(SaslConfigs.SASL_JAAS_CONFIG, loginModule + " required username=\"" - + username + "\" password=\"" + password + "\";"); - configProps.put(SaslConfigs.SASL_MECHANISM, saslMechanism); return new DefaultKafkaProducerFactory<>(configProps); } - @Bean - public ConsumerFactory consumerFactory() { + private Map commonProducerConfig() { Map configProps = new HashMap<>(); - configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); - configProps.put(ConsumerConfig.GROUP_ID_CONFIG, groupId + "-" + UUID.randomUUID()); - configProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest"); - configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); - configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); configProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol); - configProps.put(SaslConfigs.SASL_JAAS_CONFIG, loginModule + " required username=\"" - + username + "\" password=\"" + password + "\";"); + configProps.put(SaslConfigs.SASL_JAAS_CONFIG, loginModule + " required username=\"" + username + "\" password=\"" + password + "\";"); configProps.put(SaslConfigs.SASL_MECHANISM, saslMechanism); + configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + return configProps; + } + + @Bean("ervuConsumerFactory") + public ConsumerFactory ervuConsumerFactory() { + Map configProps = commonConsumerConfig(); + configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); return new DefaultKafkaConsumerFactory<>(configProps); } + @Bean("validateConsumerFactory") + public ConsumerFactory validateConsumerFactory() { + Map configProps = commonConsumerConfig(); + configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, BytesDeserializer.class); + return new DefaultKafkaConsumerFactory<>(configProps); + } + + private Map commonConsumerConfig() { + Map configProps = new HashMap<>(); + configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + configProps.put(ConsumerConfig.GROUP_ID_CONFIG, groupId + "-" + UUID.randomUUID()); + configProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol); + configProps.put(SaslConfigs.SASL_JAAS_CONFIG, loginModule + " required username=\"" + username + "\" password=\"" + password + "\";"); + configProps.put(SaslConfigs.SASL_MECHANISM, saslMechanism); + configProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest"); + configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + return configProps; + } + @Bean("ervuContainerFactory") - public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() { - ConcurrentKafkaListenerContainerFactory factory = - new ConcurrentKafkaListenerContainerFactory<>(); - factory.setConsumerFactory(consumerFactory()); + public ConcurrentKafkaListenerContainerFactory ervuContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(ervuConsumerFactory()); return factory; } - @Bean - public ConcurrentMessageListenerContainer replyContainer( - @Qualifier("ervuContainerFactory") - ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory) { - return kafkaListenerContainerFactory.createContainer(orgReplyTopic, excerptReplyTopic, - journalReplyTopic, avReplyTopic - ); + @Bean("validateContainerFactory") + public ConcurrentKafkaListenerContainerFactory validateContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConsumerFactory(validateConsumerFactory()); + return factory; } - @Bean - public ReplyingKafkaTemplate replyingKafkaTemplate( - @Qualifier("ervuProducerFactory") ProducerFactory producerFactory, - ConcurrentMessageListenerContainer replyContainer) { - ReplyingKafkaTemplate replyingKafkaTemplate = - new ReplyingKafkaTemplate<>(producerFactory, replyContainer); - replyingKafkaTemplate.setCorrelationHeaderName("messageId"); - replyingKafkaTemplate.setCorrelationIdStrategy(record -> + @Bean("ervuContainer") + public ConcurrentMessageListenerContainer ervuContainer( + @Qualifier("ervuContainerFactory") ConcurrentKafkaListenerContainerFactory factory) { + return factory.createContainer(orgReplyTopic, excerptReplyTopic, + journalReplyTopic, avReplyTopic); + } + + @Bean("validateContainer") + public ConcurrentMessageListenerContainer validateContainer( + @Qualifier("validateContainerFactory") ConcurrentKafkaListenerContainerFactory factory) { + return factory.createContainer(validateReplyTopic); + } + + @Bean("ervuReplyingTemplate") + public ReplyingKafkaTemplate ervuReplyingTemplate( + ProducerFactory producerFactory, + @Qualifier("ervuContainer") ConcurrentMessageListenerContainer container) { + ReplyingKafkaTemplate template = new ReplyingKafkaTemplate<>(producerFactory, container); + customizeTemplate(template); + return template; + } + + @Bean("validateReplyingTemplate") + public ReplyingKafkaTemplate validateReplyingTemplate( + ProducerFactory producerFactory, + @Qualifier("validateContainer") ConcurrentMessageListenerContainer container) { + ReplyingKafkaTemplate template = new ReplyingKafkaTemplate<>(producerFactory, container); + customizeTemplate(template); + return template; + } + + private void customizeTemplate(ReplyingKafkaTemplate template) { + template.setCorrelationHeaderName("messageId"); + template.setCorrelationIdStrategy(record -> new CorrelationKey(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8))); - replyingKafkaTemplate.setDefaultReplyTimeout(Duration.ofSeconds(replyTimeout)); - replyingKafkaTemplate.setSharedReplyTopic(true); - return replyingKafkaTemplate; + template.setDefaultReplyTimeout(Duration.ofSeconds(replyTimeout)); + template.setSharedReplyTopic(true); } } diff --git a/backend/src/main/java/ru/micord/ervu/kafka/controller/ErvuKafkaController.java b/backend/src/main/java/ru/micord/ervu/kafka/controller/ErvuKafkaController.java index 6b29a84e..1488cb8f 100644 --- a/backend/src/main/java/ru/micord/ervu/kafka/controller/ErvuKafkaController.java +++ b/backend/src/main/java/ru/micord/ervu/kafka/controller/ErvuKafkaController.java @@ -31,7 +31,7 @@ import ru.micord.ervu.util.UrlUtils; public class ErvuKafkaController { @Autowired - private ReplyingKafkaService replyingKafkaService; + private ReplyingKafkaService replyingKafkaService; @Autowired private AuditService auditService; diff --git a/backend/src/main/java/ru/micord/ervu/kafka/service/ReplyingKafkaService.java b/backend/src/main/java/ru/micord/ervu/kafka/service/ReplyingKafkaService.java index 49918e61..876048d6 100644 --- a/backend/src/main/java/ru/micord/ervu/kafka/service/ReplyingKafkaService.java +++ b/backend/src/main/java/ru/micord/ervu/kafka/service/ReplyingKafkaService.java @@ -1,8 +1,9 @@ package ru.micord.ervu.kafka.service; -public interface ReplyingKafkaService { - String sendMessageAndGetReply(String requestTopic, - String requestReplyTopic, - String requestMessage); -} +public interface ReplyingKafkaService { + + V sendMessageAndGetReply(String requestTopic, + String replyTopic, + T requestMessage); +} \ No newline at end of file diff --git a/backend/src/main/java/ru/micord/ervu/kafka/service/impl/BaseReplyingKafkaServiceImpl.java b/backend/src/main/java/ru/micord/ervu/kafka/service/impl/BaseReplyingKafkaService.java similarity index 54% rename from backend/src/main/java/ru/micord/ervu/kafka/service/impl/BaseReplyingKafkaServiceImpl.java rename to backend/src/main/java/ru/micord/ervu/kafka/service/impl/BaseReplyingKafkaService.java index 03e19f72..c849daa2 100644 --- a/backend/src/main/java/ru/micord/ervu/kafka/service/impl/BaseReplyingKafkaServiceImpl.java +++ b/backend/src/main/java/ru/micord/ervu/kafka/service/impl/BaseReplyingKafkaService.java @@ -6,12 +6,10 @@ import java.util.concurrent.ExecutionException; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.header.internals.RecordHeader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.kafka.requestreply.ReplyingKafkaTemplate; import org.springframework.kafka.requestreply.RequestReplyFuture; -import org.springframework.kafka.support.KafkaHeaders; import org.springframework.stereotype.Service; import ru.micord.ervu.kafka.exception.KafkaMessageException; import ru.micord.ervu.kafka.exception.KafkaMessageReplyTimeoutException; @@ -21,35 +19,30 @@ import ru.micord.ervu.kafka.service.ReplyingKafkaService; * @author Eduard Tihomirov */ @Service -public class BaseReplyingKafkaServiceImpl implements ReplyingKafkaService { +public abstract class BaseReplyingKafkaService implements ReplyingKafkaService { private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - private final ReplyingKafkaTemplate replyingKafkaTemplate; - public BaseReplyingKafkaServiceImpl( - ReplyingKafkaTemplate replyingKafkaTemplate) { - this.replyingKafkaTemplate = replyingKafkaTemplate; - } - - public String sendMessageAndGetReply(String requestTopic, - String replyTopic, - String requestMessage) { + @Override + public V sendMessageAndGetReply(String requestTopic, String replyTopic, T requestMessage) { long startTime = System.currentTimeMillis(); - ProducerRecord record = new ProducerRecord<>(requestTopic, requestMessage); - record.headers().add(new RecordHeader(KafkaHeaders.REPLY_TOPIC, replyTopic.getBytes())); - RequestReplyFuture replyFuture = replyingKafkaTemplate.sendAndReceive(record); + RequestReplyFuture replyFuture = getTemplate().sendAndReceive( + getProducerRecord(requestTopic, replyTopic, requestMessage)); try { - String result = Optional.ofNullable(replyFuture.get()) - .map(ConsumerRecord::value) - .orElseThrow(() -> new KafkaMessageException("Kafka return result is null.")); - LOGGER.info("Thread {} - KafkaSendMessageAndGetReply: {} ms", - Thread.currentThread().getId(), System.currentTimeMillis() - startTime); - return result; + ConsumerRecord result = Optional.ofNullable(replyFuture.get()) + .orElseThrow(() -> new KafkaMessageException("Kafka return result is null")); + LOGGER.info("Thread {} - KafkaSendMessageAndGetReply: {} ms, replyTopic: {}", + Thread.currentThread().getId(), System.currentTimeMillis() - startTime, replyTopic); + return result.value(); } catch (InterruptedException | ExecutionException e) { - LOGGER.error("Thread {} - KafkaSendMessageAndGetReply: {} ms", - Thread.currentThread().getId(), System.currentTimeMillis() - startTime); + LOGGER.error("Thread {} - KafkaSendMessageAndGetReply: {} ms, replyTopic: {}", + Thread.currentThread().getId(), System.currentTimeMillis() - startTime, replyTopic); throw new KafkaMessageReplyTimeoutException(e); } } + protected abstract ReplyingKafkaTemplate getTemplate(); + + protected abstract ProducerRecord getProducerRecord(String requestTopic, + String replyTopic, T requestMessage); } diff --git a/backend/src/main/java/ru/micord/ervu/kafka/service/impl/ErvuReplyingKafkaService.java b/backend/src/main/java/ru/micord/ervu/kafka/service/impl/ErvuReplyingKafkaService.java new file mode 100644 index 00000000..51c3d452 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/kafka/service/impl/ErvuReplyingKafkaService.java @@ -0,0 +1,31 @@ +package ru.micord.ervu.kafka.service.impl; + +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.header.internals.RecordHeader; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.kafka.requestreply.ReplyingKafkaTemplate; +import org.springframework.kafka.support.KafkaHeaders; +import org.springframework.stereotype.Service; + +@Service +public class ErvuReplyingKafkaService extends BaseReplyingKafkaService{ + private final ReplyingKafkaTemplate replyingKafkaTemplate; + + public ErvuReplyingKafkaService( + @Qualifier("ervuReplyingTemplate") ReplyingKafkaTemplate replyingKafkaTemplate) { + this.replyingKafkaTemplate = replyingKafkaTemplate; + } + + @Override + protected ReplyingKafkaTemplate getTemplate() { + return replyingKafkaTemplate; + } + + @Override + protected ProducerRecord getProducerRecord(String requestTopic, String replyTopic, + String requestMessage) { + ProducerRecord record = new ProducerRecord<>(requestTopic, requestMessage); + record.headers().add(new RecordHeader(KafkaHeaders.REPLY_TOPIC, replyTopic.getBytes())); + return record; + } +} diff --git a/backend/src/main/java/ru/micord/ervu/kafka/service/impl/ValidateReplyingKafkaService.java b/backend/src/main/java/ru/micord/ervu/kafka/service/impl/ValidateReplyingKafkaService.java new file mode 100644 index 00000000..10903afc --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/kafka/service/impl/ValidateReplyingKafkaService.java @@ -0,0 +1,32 @@ +package ru.micord.ervu.kafka.service.impl; + + +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.header.internals.RecordHeader; +import org.apache.kafka.common.utils.Bytes; +import org.springframework.kafka.requestreply.ReplyingKafkaTemplate; +import org.springframework.kafka.support.KafkaHeaders; +import org.springframework.stereotype.Service; + +@Service +public class ValidateReplyingKafkaService extends BaseReplyingKafkaService { + private final ReplyingKafkaTemplate replyingKafkaTemplate; + + public ValidateReplyingKafkaService( + ReplyingKafkaTemplate replyingKafkaTemplate) { + this.replyingKafkaTemplate = replyingKafkaTemplate; + } + + @Override + protected ReplyingKafkaTemplate getTemplate() { + return replyingKafkaTemplate; + } + + @Override + protected ProducerRecord getProducerRecord(String requestTopic, String replyTopic, + String requestMessage) { + ProducerRecord record = new ProducerRecord<>(requestTopic, requestMessage); + record.headers().add(new RecordHeader(KafkaHeaders.REPLY_TOPIC, replyTopic.getBytes())); + return record; + } +} diff --git a/backend/src/main/java/ru/micord/ervu/model/ValidateExportRequest.java b/backend/src/main/java/ru/micord/ervu/model/ValidateExportRequest.java new file mode 100644 index 00000000..65dbdd7a --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/model/ValidateExportRequest.java @@ -0,0 +1,4 @@ +package ru.micord.ervu.model; + +public record ValidateExportRequest(String orgId, String fileId) { +} diff --git a/backend/src/main/java/ru/micord/ervu/model/ValidateExportResponse.java b/backend/src/main/java/ru/micord/ervu/model/ValidateExportResponse.java new file mode 100644 index 00000000..8e8c8cd9 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/model/ValidateExportResponse.java @@ -0,0 +1,28 @@ +package ru.micord.ervu.model; + +import org.apache.kafka.shaded.com.google.protobuf.ByteString; +import org.apache.kafka.shaded.com.google.protobuf.InvalidProtocolBufferException; + +public class ValidateExportResponse { + private String fileName; + private ByteString file; + + public ValidateExportResponse(byte[] bytes) throws InvalidProtocolBufferException { + // TODO: Заменить ValidateExportResponseProto на реальный protobuf класс +// ValidateExportResponseProto protoResponse = ValidateExportResponseProto.parseFrom(bytes); +// this.fileName = protoResponse.getFileName(); +// this.file = protoResponse.getFile(); + } + + public String getFileName() { + return fileName; + } + + public ByteString getFile() { + return file; + } + + public boolean hasFile() { + return file != null && !file.isEmpty(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/ru/micord/ervu/property/grid/FilterType.java b/backend/src/main/java/ru/micord/ervu/property/grid/FilterType.java index 7795ebc0..8d3f53f1 100644 --- a/backend/src/main/java/ru/micord/ervu/property/grid/FilterType.java +++ b/backend/src/main/java/ru/micord/ervu/property/grid/FilterType.java @@ -7,5 +7,6 @@ public enum FilterType { TEXT, DATE, NUMBER, - SET + SET, + FILE } diff --git a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java index 63143c16..a9f403db 100644 --- a/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java +++ b/backend/src/main/java/ru/micord/ervu/security/esia/service/EsiaAuthService.java @@ -86,7 +86,7 @@ public class EsiaAuthService { @Autowired private JwtTokenService jwtTokenService; @Autowired - private ReplyingKafkaService replyingKafkaService; + private ReplyingKafkaService replyingKafkaService; @Autowired private OkopfService okopfService; @Autowired diff --git a/backend/src/main/java/ru/micord/ervu/service/InteractionServiceImpl.java b/backend/src/main/java/ru/micord/ervu/service/InteractionServiceImpl.java index c5688d16..b02c9e2c 100644 --- a/backend/src/main/java/ru/micord/ervu/service/InteractionServiceImpl.java +++ b/backend/src/main/java/ru/micord/ervu/service/InteractionServiceImpl.java @@ -41,8 +41,6 @@ public class InteractionServiceImpl implements InteractionService { .set(INTERACTION_LOG.SENT_DATE, timestamp) .set(INTERACTION_LOG.SENDER, sender) .set(INTERACTION_LOG.FILE_NAME, fileName) - .set(INTERACTION_LOG.RECORDS_SENT, 0) - .set(INTERACTION_LOG.RECORDS_ACCEPTED, 0) .set(INTERACTION_LOG.ERVU_ID, ervuId) .execute(); } diff --git a/backend/src/main/java/ru/micord/ervu/service/ValidationFileService.java b/backend/src/main/java/ru/micord/ervu/service/ValidationFileService.java new file mode 100644 index 00000000..c116bf26 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/service/ValidationFileService.java @@ -0,0 +1,48 @@ +package ru.micord.ervu.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.kafka.common.utils.Bytes; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import ru.micord.ervu.exception.ExportException; +import ru.micord.ervu.kafka.service.ReplyingKafkaService; +import ru.micord.ervu.model.ValidateExportRequest; +import ru.micord.ervu.model.ValidateExportResponse; + +/** + * @author Adel Kalimullin + */ +@Service +public class ValidationFileService { + private final ReplyingKafkaService replyingKafkaService; + private final ObjectMapper objectMapper; + private final String validateExportRequestTopic; + private final String validateExportReplyTopic; + + public ValidationFileService( + ReplyingKafkaService replyingKafkaService, + ObjectMapper objectMapper, + @Value("${ervu.kafka.validate.export.request.topic}") String validateExportRequestTopic, + @Value("${ervu.kafka.validate.export.reply.topic}") String validateExportReplyTopic) { + this.replyingKafkaService = replyingKafkaService; + this.objectMapper = objectMapper; + this.validateExportRequestTopic = validateExportRequestTopic; + this.validateExportReplyTopic = validateExportReplyTopic; + } + + + public ValidateExportResponse exportFile(String ervuId, String fileId) { + try { + ValidateExportRequest validateExportRequest = new ValidateExportRequest(ervuId, fileId); + + byte[] bytes = replyingKafkaService.sendMessageAndGetReply( + validateExportRequestTopic, validateExportReplyTopic, + objectMapper.writeValueAsString(validateExportRequest) + ).get(); + return new ValidateExportResponse(bytes); + } + catch (Exception e) { + throw new ExportException("Failed to export file: " + e.getMessage(), e); + } + } +} diff --git a/backend/src/main/java/ru/micord/ervu/service/grid/impl/JournalInMemoryStaticGridLoadService.java b/backend/src/main/java/ru/micord/ervu/service/grid/impl/JournalInMemoryStaticGridLoadService.java index 8c956256..71624244 100644 --- a/backend/src/main/java/ru/micord/ervu/service/grid/impl/JournalInMemoryStaticGridLoadService.java +++ b/backend/src/main/java/ru/micord/ervu/service/grid/impl/JournalInMemoryStaticGridLoadService.java @@ -5,6 +5,7 @@ import java.util.Comparator; import java.util.List; import java.util.HashSet; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; import javax.servlet.http.Cookie; @@ -33,7 +34,7 @@ public class JournalInMemoryStaticGridLoadService implements private final JwtTokenService jwtTokenService; private final InteractionService interactionService; - private final ReplyingKafkaService replyingKafkaService; + private final ReplyingKafkaService replyingKafkaService; private final ObjectMapper objectMapper; private final HttpServletRequest request; @@ -46,7 +47,7 @@ public class JournalInMemoryStaticGridLoadService implements public JournalInMemoryStaticGridLoadService(JwtTokenService jwtTokenService, InteractionService interactionService, - ReplyingKafkaService replyingKafkaService, + ReplyingKafkaService replyingKafkaService, ObjectMapper objectMapper, HttpServletRequest request) { this.jwtTokenService = jwtTokenService; this.interactionService = interactionService; @@ -80,10 +81,16 @@ public class JournalInMemoryStaticGridLoadService implements throw new JsonParsingException("Failed to parse JournalFileDataResponse.", e); } + AtomicInteger counter = new AtomicInteger(1); HashSet seenFileIds = new HashSet<>(); return Stream.concat(dbJournalList.stream(), ervuJournalList.stream()) .filter(journal -> seenFileIds.add(journal.getFileId())) .sorted(Comparator.comparing(JournalDto::getDepartureDateTime)) + .map(journal -> + journal + .setDocumentNumber(counter.getAndIncrement()) + .setHasFailedRows(journal.getRowsError() != null && journal.getRowsError() > 0) + ) .toList(); } diff --git a/frontend/src/ts/ervu/component/grid/InMemoryStaticGrid.ts b/frontend/src/ts/ervu/component/grid/InMemoryStaticGrid.ts index 99d7fd14..581b33d9 100644 --- a/frontend/src/ts/ervu/component/grid/InMemoryStaticGrid.ts +++ b/frontend/src/ts/ervu/component/grid/InMemoryStaticGrid.ts @@ -1,5 +1,4 @@ import { - Event, GridColumnIdUtils, GridRow, GridRowModelType, @@ -11,17 +10,19 @@ import {ChangeDetectionStrategy, Component} from "@angular/core"; import { ColDef, GridReadyEvent, - FilterChangedEvent, ICellRendererParams, ITooltipParams, ValueFormatterParams, - ValueGetterParams + ValueGetterParams, } from "ag-grid-community"; import {StaticColumnInitializer} from "./StaticColumnInitializer"; -import {InMemoryStaticGridRpcService} from "../../../generated/ru/micord/ervu/service/rpc/InMemoryStaticGridRpcService"; +import { + InMemoryStaticGridRpcService +} from "../../../generated/ru/micord/ervu/service/rpc/InMemoryStaticGridRpcService"; import {StaticGridColumn} from "../../../generated/ru/micord/ervu/property/grid/StaticGridColumn"; -import { FilterService } from "../../service/FilterService"; +import {FilterService} from "../../service/FilterService"; import {AuditConstants, AuditService, FilterInfo} from "../../service/AuditService"; +import {ValidateFileService} from "../../service/ValidateFileService"; @Component({ @@ -34,6 +35,7 @@ export class InMemoryStaticGrid extends GridV2 { private rpcService: InMemoryStaticGridRpcService; private auditService: AuditService; + private validateFileService: ValidateFileService; getRowModelType(): string { return GridRowModelType.CLIENT_SIDE; @@ -42,6 +44,7 @@ export class InMemoryStaticGrid extends GridV2 { protected initGrid() { super.initGrid(); this.auditService = this.injector.get(AuditService); + this.validateFileService = this.injector.get(ValidateFileService); this.rpcService = this.getScript(InMemoryStaticGridRpcService); if (this.rpcService) { this.rpcService.loadData().then(response => { @@ -85,6 +88,10 @@ export class InMemoryStaticGrid extends GridV2 { } } + public downloadFile(fileId : string){ + this.validateFileService.exportFile(fileId); + } + getColumns(): any[] { return this.getScriptsInChildren(GridV2Column) .map(columnV2 => columnV2.getScript(StaticGridColumn)); @@ -125,6 +132,13 @@ export class InMemoryStaticGrid extends GridV2 { return columnComp.valueFormatter.format(params); } } + + colDef.cellRendererParams = { + context : { + parentComponent: this + } + }; + return colDef; } diff --git a/frontend/src/ts/ervu/component/grid/filter/CustomGridColumnFilterUtils.ts b/frontend/src/ts/ervu/component/grid/filter/CustomGridColumnFilterUtils.ts index 19f8c910..620bd559 100644 --- a/frontend/src/ts/ervu/component/grid/filter/CustomGridColumnFilterUtils.ts +++ b/frontend/src/ts/ervu/component/grid/filter/CustomGridColumnFilterUtils.ts @@ -1,6 +1,7 @@ import {FilterType} from "../../../../generated/ru/micord/ervu/property/grid/FilterType"; import {DateFilter, NumberFilter, TextFilter} from "ag-grid-community"; import {SetFilter} from "./SetFilter"; +import {FileAvailableFilterComp} from "./FileAvailableFilterComp"; export class CustomGridColumnFilterUtils { @@ -16,6 +17,8 @@ export class CustomGridColumnFilterUtils { return DateFilter; case FilterType.SET: return SetFilter; + case FilterType.FILE: + return FileAvailableFilterComp; case FilterType.TEXT: default: return TextFilter; diff --git a/frontend/src/ts/ervu/component/grid/filter/FileAvailableFilterComp.ts b/frontend/src/ts/ervu/component/grid/filter/FileAvailableFilterComp.ts new file mode 100644 index 00000000..a1be012b --- /dev/null +++ b/frontend/src/ts/ervu/component/grid/filter/FileAvailableFilterComp.ts @@ -0,0 +1,77 @@ +import {BaseBooleanComboBoxFilter} from "@webbpm/base-package"; +import {IFilterParams, IDoesFilterPassParams, IFilterComp} from "ag-grid-community"; + + +export class FileAvailableFilterComp extends BaseBooleanComboBoxFilter implements IFilterComp { + private filterValue: boolean | undefined = undefined; + private params!: IFilterParams; + + init(params: IFilterParams): void { + this.params = params; + this.createComboBox("ag-combobox-file-filter"); + this.populateComboBoxWithFixedValues(); + + this.comboBox.addEventListener("change", () => this.onComboBoxChanged()); + } + + protected populateComboBoxWithFixedValues(): void { + const options = [ + { label: "Все", value: undefined }, + { label: "Файл присутствует", value: true }, + { label: "Файл отсутствует", value: false } + ]; + + this.comboBox.innerHTML = ""; + options.forEach(({ label }) => { + const option = document.createElement("option"); + option.textContent = label; + this.comboBox.appendChild(option); + }); + } + + protected onComboBoxChanged(): void { + const selectedIndex = this.comboBox.selectedIndex; + + this.filterValue = + selectedIndex === 1 ? true : + selectedIndex === 2 ? false : + undefined; + + this.params.filterChangedCallback(); + } + + doesFilterPass(params: IDoesFilterPassParams): boolean { + const cellValue = params.data[this.params.colDef.field!]; + return this.filterValue === undefined || cellValue === this.filterValue; + } + + isFilterActive(): boolean { + return this.filterValue !== undefined; + } + + getModel(): any { + return this.filterValue === undefined + ? undefined + : { filter: this.filterValue, type: "equals" }; + } + + setModel(model: any): void { + this.filterValue = model ? model.filter : undefined; + + if (this.filterValue === true) { + this.comboBox.selectedIndex = 1; + } + else if (this.filterValue === false) { + this.comboBox.selectedIndex = 2; + } + else { + this.comboBox.selectedIndex = 0; + } + + this.params.filterChangedCallback(); + } + + getGui(): HTMLElement { + return this.eGui; + } +} \ No newline at end of file diff --git a/frontend/src/ts/ervu/component/grid/renderer/FileDownloadCellRenderer.ts b/frontend/src/ts/ervu/component/grid/renderer/FileDownloadCellRenderer.ts new file mode 100644 index 00000000..1a71de29 --- /dev/null +++ b/frontend/src/ts/ervu/component/grid/renderer/FileDownloadCellRenderer.ts @@ -0,0 +1,36 @@ +import {ICellRendererParams} from "ag-grid-community"; +import {GridCellValueRenderer} from "@webbpm/base-package"; + +export class FileDownloadCellRenderer implements GridCellValueRenderer { + render(params: ICellRendererParams): HTMLElement { + const container = document.createElement('div'); + container.className = 'download-cell-renderer'; + + const button = document.createElement('button'); + button.className = 'download-btn'; + button.innerHTML = ''; + button.title = params.data.fileName || 'Скачать файл'; + + if (!params.value) { + button.style.display = 'none'; + } + + button.addEventListener('click', () => { + const fileId = params.data.fileId; + if (!fileId) { + return; + } + + const parentComponent = params.context.parentComponent; + if (parentComponent) { + parentComponent.downloadFile(fileId, params.data); + } + + button.blur(); + }); + + container.appendChild(button); + return container; + } + +} \ No newline at end of file diff --git a/frontend/src/ts/ervu/service/FilterService.ts b/frontend/src/ts/ervu/service/FilterService.ts index b9a9863d..db617807 100644 --- a/frontend/src/ts/ervu/service/FilterService.ts +++ b/frontend/src/ts/ervu/service/FilterService.ts @@ -1,6 +1,7 @@ import {DateFilter, NumberFilter, TextFilter} from "ag-grid-community"; import {SetFilter} from "../component/grid/filter/SetFilter"; import {FilterInfo} from "./AuditService"; +import {FileAvailableFilterComp} from "../component/grid/filter/FileAvailableFilterComp"; export class FilterService { static getFilterData(columnDef: any, agFilter: any): FilterInfo { @@ -15,6 +16,8 @@ export class FilterService { return this.processSetFilter(agFilter); case TextFilter: return this.processTextFilter(agFilter); + case FileAvailableFilterComp: + return this.processFileAvailableFilter(agFilter); default: return; } @@ -63,6 +66,20 @@ export class FilterService { return this.createDualConditionData(agFilter); } + private static processFileAvailableFilter(agFilter: any): FilterInfo { + const displayValue = agFilter.filter + ? "Файл присутствует" + : "Файл отсутствует"; + + return { + conditionOperator: undefined, + conditions: [{ + filterValue: displayValue, + filterType: "equals" + }] + }; + } + private static createSingleConditionData( filterValue: string, filterType: string, diff --git a/frontend/src/ts/ervu/service/ValidateFileService.ts b/frontend/src/ts/ervu/service/ValidateFileService.ts new file mode 100644 index 00000000..8db951ee --- /dev/null +++ b/frontend/src/ts/ervu/service/ValidateFileService.ts @@ -0,0 +1,34 @@ +import {Injectable} from "@angular/core"; +import {HttpClient} from "@angular/common/http"; +import {MessagesService} from "@webbpm/base-package"; + +@Injectable({ + providedIn: "root" + }) +export class ValidateFileService { + constructor(private httpClient: HttpClient, + private messageService: MessagesService) { + } + + public exportFile(fileId: string) { + this.httpClient.get('validate/export/' + fileId, { + responseType: 'blob', + observe: 'response' + }).toPromise() + .then((response) => { + const data = window.URL.createObjectURL(response.body); + const link = document.createElement("a"); + link.href = data; + const contentDisposition = response.headers.get('Content-Disposition'); + const fileNameMatch = contentDisposition.match(/filename\*=?UTF-8''(.+)/i); + link.download = decodeURIComponent(fileNameMatch[1].replace(/\+/g, '%20')); + document.body.appendChild(link); + link.click(); + window.URL.revokeObjectURL(data); + link.remove(); + }) + .catch(() => { + this.messageService.error("Не удалось скачать файл") + }); + } +} \ No newline at end of file diff --git a/frontend/src/ts/modules/app/app.module.ts b/frontend/src/ts/modules/app/app.module.ts index a24ea8fd..517811de 100644 --- a/frontend/src/ts/modules/app/app.module.ts +++ b/frontend/src/ts/modules/app/app.module.ts @@ -30,6 +30,7 @@ import {AuditService} from "../../ervu/service/AuditService"; import { ErvuFileUploadWithAdditionalFiles } from "../../ervu/component/fileupload/ErvuFileUploadWithAdditionalFiles"; +import {ValidateFileService} from "../../ervu/service/ValidateFileService"; registerLocaleData(localeRu); export const DIRECTIVES = [ @@ -70,7 +71,7 @@ export function checkAuthentication(authService: AuthenticationService): () => P DIRECTIVES ], providers: [ - AuthenticationService, AuditService, + AuthenticationService, AuditService, ValidateFileService, { provide: APP_INITIALIZER, useFactory: checkAuthentication, diff --git a/pom.xml b/pom.xml index 801331eb..5a6faaf2 100644 --- a/pom.xml +++ b/pom.xml @@ -270,6 +270,16 @@ 1.1.10.7 runtime + + com.google.protobuf + protobuf-java + 4.28.3 + + + com.google.protobuf + protobuf-java-util + 4.28.3 + org.slf4j slf4j-api @@ -392,6 +402,11 @@ + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + diff --git a/resources/src/main/resources/business-model/Журнал взаимодействия.page b/resources/src/main/resources/business-model/Журнал взаимодействия.page index 714d98cf..57f0567b 100644 --- a/resources/src/main/resources/business-model/Журнал взаимодействия.page +++ b/resources/src/main/resources/business-model/Журнал взаимодействия.page @@ -982,11 +982,106 @@ + + c556264f-221b-4af8-9e64-f380a67c41ec + 7094b2c2-60c7-43d0-84d7-3adbd3c77fe9 + №П/П + false + false + + + + floatingFilter + +false + + + + + + + + autoHeight + +true + + + + displayName + +"№П/П" + + + + displayPopup + +true + + + + field + + + + column + + "documentNumber" + + + + filterType + + "NUMBER" + + + + type + + "java.lang.Number" + + + + + + + filter + +true + + + + pinned + +"LEFT" + + + + sortable + +true + + + + width + +50 + + + + widthFixed + +null + + + + + c556264f-221b-4af8-9e64-f380a67c41ec 9b895ce1-494f-4f2e-b87b-78d019fc3760 Дата и время направления false + false false @@ -1064,7 +1159,7 @@ filter -true +null @@ -1118,6 +1213,12 @@ true + + disableHiding + +null + + displayName @@ -1489,7 +1590,7 @@ column - "filesSentCount" + "rowsCount" @@ -1539,6 +1640,7 @@ 4c070d5d-cac7-4cc4-8ee4-e9a4b9b289a5 Записей принято false + false false @@ -1577,7 +1679,7 @@ column - "acceptedFilesCount" + "rowsSuccess" @@ -1616,6 +1718,293 @@ widthFixed +null + + + + + + + c556264f-221b-4af8-9e64-f380a67c41ec + 7189ce80-0090-44c9-a8ba-b3860b1fefb8 + Записей не принято + false + false + false + + + + floatingFilter + +false + + + + + + + + autoHeight + +true + + + + displayName + +"Записей не принято" + + + + displayPopup + +true + + + + field + + + + column + + "rowsError" + + + + filterType + + "NUMBER" + + + + type + + "java.lang.Number" + + + + + + + filter + +true + + + + sortable + +true + + + + width + +80 + + + + widthFixed + +null + + + + + + + c556264f-221b-4af8-9e64-f380a67c41ec + 33376e71-d633-464c-bd0a-da95caeae54b + Загрузка файла + false + false + false + + + + floatingFilter + +false + + + + renderer + + + FileDownloadCellRenderer + ervu.component.grid.renderer + + + + + + + + + autoHeight + +true + + + + displayName + +"Описание ошибки" + + + + displayPopup + +true + + + + field + + + + column + + "hasFailedRows" + + + + filterType + + "FILE" + + + + type + + "java.lang.Boolean" + + + + + + + filter + +true + + + + hidden + +null + + + + sortable + +true + + + + width + +80 + + + + widthFixed + +null + + + + + + + c556264f-221b-4af8-9e64-f380a67c41ec + 438864af-918d-4264-bc37-192f94263673 + id Файла(скрытое) + false + false + + + + floatingFilter + +false + + + + + + + + autoHeight + +true + + + + displayName + +"id файла" + + + + displayPopup + +true + + + + field + + + + column + + "fileId" + + + + filterType + + "TEXT" + + + + type + + "java.lang.String" + + + + + + + filter + +true + + + + hidden + +true + + + + sortable + +true + + + + width + +null + + + + widthFixed + null