diff --git a/source/backend/Pims.Scheduler.Test/Pims.Scheduler.Test.csproj b/source/backend/Pims.Scheduler.Test/Pims.Scheduler.Test.csproj
new file mode 100644
index 0000000000..2ec4df52a3
--- /dev/null
+++ b/source/backend/Pims.Scheduler.Test/Pims.Scheduler.Test.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/source/backend/Pims.Scheduler.Test/Repositories/PimsDocumentQueueRepositoryTests.cs b/source/backend/Pims.Scheduler.Test/Repositories/PimsDocumentQueueRepositoryTests.cs
new file mode 100644
index 0000000000..6083026ef6
--- /dev/null
+++ b/source/backend/Pims.Scheduler.Test/Repositories/PimsDocumentQueueRepositoryTests.cs
@@ -0,0 +1,105 @@
+using FluentAssertions;
+using Moq;
+using Pims.Api.Models.CodeTypes;
+using Pims.Api.Models.Concepts.Document;
+using Pims.Api.Models.Requests.Http;
+using Pims.Dal.Entities.Models;
+using Pims.Scheduler.Repositories;
+using Xunit;
+
+namespace Pims.Scheduler.Test.Repositories
+{
+ public class PimsDocumentQueueRepositoryTest
+ {
+ [Fact]
+ public async Task PollQueuedDocument_ValidDocument_ReturnsExternalResponse()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1 };
+ var expectedResponse = new ExternalResponse { Status = ExternalResponseStatus.Success };
+ var repositoryMock = new Mock();
+ repositoryMock.Setup(x => x.PollQueuedDocument(document)).ReturnsAsync(expectedResponse);
+
+ // Act
+ var result = await repositoryMock.Object.PollQueuedDocument(document);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Status.Should().Be(ExternalResponseStatus.Success);
+ repositoryMock.Verify(x => x.PollQueuedDocument(document), Times.Once);
+ }
+
+ [Fact]
+ public async Task UploadQueuedDocument_ValidDocument_ReturnsExternalResponse()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1 };
+ var expectedResponse = new ExternalResponse { Status = ExternalResponseStatus.Success };
+ var repositoryMock = new Mock();
+ repositoryMock.Setup(x => x.UploadQueuedDocument(document)).ReturnsAsync(expectedResponse);
+
+ // Act
+ var result = await repositoryMock.Object.UploadQueuedDocument(document);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Status.Should().Be(ExternalResponseStatus.Success);
+ repositoryMock.Verify(x => x.UploadQueuedDocument(document), Times.Once);
+ }
+
+ [Fact]
+ public async Task UpdateQueuedDocument_ValidDocument_ReturnsExternalResponse()
+ {
+ // Arrange
+ var documentQueueId = 1;
+ var document = new DocumentQueueModel { Id = documentQueueId };
+ var expectedResponse = new ExternalResponse { Status = ExternalResponseStatus.Success };
+ var repositoryMock = new Mock();
+ repositoryMock.Setup(x => x.UpdateQueuedDocument(documentQueueId, document)).ReturnsAsync(expectedResponse);
+
+ // Act
+ var result = await repositoryMock.Object.UpdateQueuedDocument(documentQueueId, document);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Status.Should().Be(ExternalResponseStatus.Success);
+ repositoryMock.Verify(x => x.UpdateQueuedDocument(documentQueueId, document), Times.Once);
+ }
+
+ [Fact]
+ public async Task SearchQueuedDocumentsAsync_ValidFilter_ReturnsExternalResponse()
+ {
+ // Arrange
+ var filter = new DocumentQueueFilter();
+ var expectedResponse = new ExternalResponse> { Status = ExternalResponseStatus.Success };
+ var repositoryMock = new Mock();
+ repositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(filter)).ReturnsAsync(expectedResponse);
+
+ // Act
+ var result = await repositoryMock.Object.SearchQueuedDocumentsAsync(filter);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Status.Should().Be(ExternalResponseStatus.Success);
+ repositoryMock.Verify(x => x.SearchQueuedDocumentsAsync(filter), Times.Once);
+ }
+
+ [Fact]
+ public async Task GetById_ValidDocumentQueueId_ReturnsExternalResponse()
+ {
+ // Arrange
+ var documentQueueId = 1;
+ var expectedResponse = new ExternalResponse { Status = ExternalResponseStatus.Success };
+ var repositoryMock = new Mock();
+ repositoryMock.Setup(x => x.GetById(documentQueueId)).ReturnsAsync(expectedResponse);
+
+ // Act
+ var result = await repositoryMock.Object.GetById(documentQueueId);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Status.Should().Be(ExternalResponseStatus.Success);
+ repositoryMock.Verify(x => x.GetById(documentQueueId), Times.Once);
+ }
+ }
+}
diff --git a/source/backend/Pims.Scheduler.Test/Services/DocumentQueueServiceTests.cs b/source/backend/Pims.Scheduler.Test/Services/DocumentQueueServiceTests.cs
new file mode 100644
index 0000000000..fe54a96092
--- /dev/null
+++ b/source/backend/Pims.Scheduler.Test/Services/DocumentQueueServiceTests.cs
@@ -0,0 +1,303 @@
+using FluentAssertions;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Moq;
+using Pims.Api.Models.Base;
+using Pims.Api.Models.CodeTypes;
+using Pims.Api.Models.Concepts.Document;
+using Pims.Api.Models.Requests.Http;
+using Pims.Dal.Entities.Models;
+using Pims.Scheduler.Http.Configuration;
+using Pims.Scheduler.Models;
+using Pims.Scheduler.Repositories;
+using Pims.Scheduler.Services;
+using Xunit;
+
+namespace Pims.Scheduler.Test.Services
+{
+ public class DocumentQueueServiceTests
+ {
+ private readonly Mock> _loggerMock;
+ private readonly Mock _documentQueueRepositoryMock;
+ private readonly Mock> _uploadOptionsMock;
+ private readonly Mock> _queryOptionsMock;
+ private readonly Mock> _retryOptionsMock;
+ private readonly DocumentQueueService _service;
+
+ public DocumentQueueServiceTests()
+ {
+ _loggerMock = new Mock>();
+ _documentQueueRepositoryMock = new Mock();
+ _uploadOptionsMock = new Mock>();
+ _queryOptionsMock = new Mock>();
+ _retryOptionsMock = new Mock>();
+ _uploadOptionsMock.Setup(x => x.CurrentValue).Returns(new UploadQueuedDocumentsJobOptions() { BatchSize = 10, MaxFileSize = 100 });
+ _queryOptionsMock.Setup(x => x.CurrentValue).Returns(new QueryProcessingDocumentsJobOptions() { BatchSize = 10, MaxProcessingMinutes = 100 });
+
+ _service = new DocumentQueueService(
+ _loggerMock.Object,
+ _uploadOptionsMock.Object,
+ _queryOptionsMock.Object,
+ _retryOptionsMock.Object,
+ _documentQueueRepositoryMock.Object
+ );
+ }
+
+ [Fact]
+ public async Task UploadQueuedDocuments_NoDocumentsToProcess_ReturnsSkipped()
+ {
+ // Arrange
+ var searchResponse = new ExternalResponse> { Status = ExternalResponseStatus.Success, Payload = new List() };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+
+ // Act
+ var result = await _service.UploadQueuedDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.SKIPPED);
+ result.Message.Should().Be("No documents to process, skipping execution.");
+ }
+
+ [Fact]
+ public async Task UploadQueuedDocuments_ErrorStatus_ReturnsError()
+ {
+ // Arrange
+ var searchResponse = new ExternalResponse> { Status = ExternalResponseStatus.Error, Message = "Error", Payload = new List() { new DocumentQueueModel() } };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+
+ // Act
+ var result = await _service.UploadQueuedDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.ERROR);
+ result.Message.Should().Be("Received error status from pims document queue service, aborting.");
+ }
+
+ [Fact]
+ public async Task UploadQueuedDocuments_SingleDocumentError_ReturnsError()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() } };
+ var searchResponse = new ExternalResponse>
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new List { document },
+ };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+ _documentQueueRepositoryMock.Setup(x => x.UploadQueuedDocument(document)).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Error,
+ Message = "Error uploading document.",
+ });
+
+ // Act
+ var result = await _service.UploadQueuedDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.ERROR);
+ result.DocumentQueueResponses.FirstOrDefault()?.Message.Should().Be("Received error response from UploadQueuedDocument for queued document 1 status Error message: Error uploading document.");
+ }
+
+ [Fact]
+ public async Task UploadQueuedDocuments_SingleDocumentError_ReturnsError_UpdatesQueue()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() } };
+ var searchResponse = new ExternalResponse>
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new List { document },
+ };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+ _documentQueueRepositoryMock.Setup(x => x.GetById(It.IsAny())).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = document,
+ });
+ _documentQueueRepositoryMock.Setup(x => x.UploadQueuedDocument(document)).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Error,
+ Message = "Error uploading document.",
+ });
+
+ // Act
+ var result = await _service.UploadQueuedDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.ERROR);
+ result.DocumentQueueResponses.FirstOrDefault()?.Message.Should().Be("Received error response from UploadQueuedDocument for queued document 1 status Error message: Error uploading document.");
+ }
+
+ [Fact]
+ public async Task UploadQueuedDocuments_SingleDocumentSuccess_ReturnsSuccess()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() } };
+ var searchResponse = new ExternalResponse>
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new List { document },
+ };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+ _documentQueueRepositoryMock.Setup(x => x.UploadQueuedDocument(document)).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = document,
+ });
+
+ // Act
+ var result = await _service.UploadQueuedDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.SUCCESS);
+ }
+
+ [Fact]
+ public async Task UploadQueuedDocuments_TwoDocumentsMixedResults_ReturnsPartialSuccess()
+ {
+ // Arrange
+ var document1 = new DocumentQueueModel { Id = 1, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() } };
+ var document2 = new DocumentQueueModel { Id = 2, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() } };
+ var searchResponse = new ExternalResponse>
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new List { document1, document2 },
+ };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+ _documentQueueRepositoryMock.Setup(x => x.UploadQueuedDocument(document1)).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = document1,
+ });
+ _documentQueueRepositoryMock.Setup(x => x.UploadQueuedDocument(document2)).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Error,
+ Message = "Error uploading document 2.",
+ });
+
+ // Act
+ var result = await _service.UploadQueuedDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.PARTIAL);
+ result.DocumentQueueResponses.Should().HaveCount(2);
+ result.DocumentQueueResponses.ToArray()[1].Message.Should().Be("Received error response from UploadQueuedDocument for queued document 2 status Error message: Error uploading document 2.");
+ }
+
+ [Fact]
+ public async Task RetryQueuedDocuments_ErrorStatus_ReturnsError()
+ {
+ // Arrange
+ var searchResponse = new ExternalResponse> { Status = ExternalResponseStatus.Error, Message = "Error", Payload = new List() { new DocumentQueueModel() } };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+
+ // Act
+ var result = await _service.RetryQueuedDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.ERROR);
+ result.Message.Should().Be("Received error status from pims document queue service, aborting.");
+ }
+
+ [Fact]
+ public async Task QueryProcessingDocuments_NoDocumentsToProcess_ReturnsSkipped()
+ {
+ // Arrange
+ var searchResponse = new ExternalResponse> { Status = ExternalResponseStatus.Success, Payload = new List() };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+
+ // Act
+ var result = await _service.QueryProcessingDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.SKIPPED);
+ result.Message.Should().Be("No documents to process, skipping execution.");
+ }
+
+ [Fact]
+ public async Task QueryProcessingDocuments_ErrorStatus_ReturnsError()
+ {
+ // Arrange
+ var searchResponse = new ExternalResponse> { Status = ExternalResponseStatus.Error, Message = "Error", Payload = new List() { new DocumentQueueModel() } };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+
+ // Act
+ var result = await _service.QueryProcessingDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.ERROR);
+ result.Message.Should().Be("Received error status from pims document queue service, aborting.");
+ }
+
+ [Fact]
+ public async Task QueryProcessingDocuments_OneDocumentError_ReturnsError()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() } };
+ var searchResponse = new ExternalResponse>
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new List { document },
+ };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+ _documentQueueRepositoryMock.Setup(x => x.PollQueuedDocument(document)).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Error,
+ Message = "Error processing document.",
+ });
+
+ // Act
+ var result = await _service.QueryProcessingDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.ERROR);
+ result.DocumentQueueResponses.FirstOrDefault()?.Message.Should().Be("Received error response from PollQueuedDocument for queued document 1 status Error message: Error processing document.");
+ }
+
+ [Fact]
+ public async Task QueryProcessingDocuments_OneDocumentSuccess_ReturnsSuccess()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() } };
+ var searchResponse = new ExternalResponse>
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new List { document },
+ };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+ _documentQueueRepositoryMock.Setup(x => x.PollQueuedDocument(document)).ReturnsAsync(new ExternalResponse
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new DocumentQueueModel { Id = document.Id, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.SUCCESS.ToString() } },
+ });
+
+ // Act
+ var result = await _service.QueryProcessingDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.SUCCESS);
+ }
+
+ [Fact]
+ public async Task QueryProcessingDocuments_OneDocumentExceededMaxProcessingTime_ReturnsError()
+ {
+ // Arrange
+ var document = new DocumentQueueModel { Id = 1, DocumentQueueStatusType = new CodeTypeModel() { Id = DocumentQueueStatusTypes.PROCESSING.ToString() }, DocumentProcessStartTimestamp = DateTime.UtcNow.AddDays(-2) };
+ var searchResponse = new ExternalResponse>
+ {
+ Status = ExternalResponseStatus.Success,
+ Payload = new List { document },
+ };
+ _documentQueueRepositoryMock.Setup(x => x.SearchQueuedDocumentsAsync(It.IsAny())).ReturnsAsync(searchResponse);
+
+ // Act
+ var result = await _service.QueryProcessingDocuments();
+
+ // Assert
+ result.Status.Should().Be(TaskResponseStatusTypes.ERROR);
+ result.DocumentQueueResponses.FirstOrDefault()?.Message.Should().Be("Document processing for document 1 has exceeded maximum processing time of 100");
+ }
+
+
+ }
+}
diff --git a/source/backend/api/Areas/CompensationRequisition/Controllers/CompensationRequisitionController.cs b/source/backend/api/Areas/CompensationRequisition/Controllers/CompensationRequisitionController.cs
index 0248f787b7..d1bcd51f94 100644
--- a/source/backend/api/Areas/CompensationRequisition/Controllers/CompensationRequisitionController.cs
+++ b/source/backend/api/Areas/CompensationRequisition/Controllers/CompensationRequisitionController.cs
@@ -4,17 +4,17 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
-using Pims.Core.Api.Exceptions;
using Pims.Api.Models.CodeTypes;
using Pims.Api.Models.Concepts.AcquisitionFile;
using Pims.Api.Models.Concepts.CompensationRequisition;
using Pims.Api.Models.Concepts.Lease;
-using Pims.Core.Api.Policies;
using Pims.Api.Services;
+using Pims.Core.Api.Exceptions;
+using Pims.Core.Api.Policies;
using Pims.Core.Extensions;
using Pims.Core.Json;
-using Pims.Dal.Entities;
using Pims.Core.Security;
+using Pims.Dal.Entities;
using Swashbuckle.AspNetCore.Annotations;
namespace Pims.Api.Areas.CompensationRequisition.Controllers
@@ -189,7 +189,7 @@ public IActionResult DeleteCompensation([FromRoute] long id)
[ProducesResponseType(typeof(List), 200)]
[SwaggerOperation(Tags = new[] { "compensation-requisition" })]
[TypeFilter(typeof(NullJsonResultFilter))]
- public IActionResult GetFileCompensations([FromRoute]FileTypes fileType, [FromRoute]long fileId)
+ public IActionResult GetFileCompensations([FromRoute] FileTypes fileType, [FromRoute] long fileId)
{
_logger.LogInformation(
"Request received by Controller: {Controller}, Action: {ControllerAction}, User: {User}, DateTime: {DateTime}",
diff --git a/source/backend/api/Areas/Documents/DocumentQueueController.cs b/source/backend/api/Areas/Documents/DocumentQueueController.cs
index 9fead6a65e..3a3fd79efa 100644
--- a/source/backend/api/Areas/Documents/DocumentQueueController.cs
+++ b/source/backend/api/Areas/Documents/DocumentQueueController.cs
@@ -1,11 +1,18 @@
+using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using MapsterMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Pims.Api.Models.Concepts.Document;
using Pims.Api.Services;
+using Pims.Core.Api.Exceptions;
using Pims.Core.Api.Policies;
+using Pims.Core.Extensions;
using Pims.Core.Json;
using Pims.Core.Security;
+using Pims.Dal.Entities;
using Pims.Dal.Entities.Models;
using Swashbuckle.AspNetCore.Annotations;
@@ -18,12 +25,13 @@ namespace Pims.Api.Controllers
[ApiController]
[ApiVersion("1.0")]
[Route("v{version:apiVersion}/documents/queue")]
- [Route("/documents")]
+ [Route("/documents/queue")]
public class DocumentQueueController : ControllerBase
{
#region Variables
private readonly IDocumentQueueService _documentQueueService;
private readonly IMapper _mapper;
+ private readonly ILogger _logger;
#endregion
#region Constructors
@@ -33,32 +41,155 @@ public class DocumentQueueController : ControllerBase
///
///
///
- public DocumentQueueController(IDocumentQueueService documentQueueService, IMapper mapper)
+ ///
+ public DocumentQueueController(IDocumentQueueService documentQueueService, IMapper mapper, ILogger logger)
{
_documentQueueService = documentQueueService;
_mapper = mapper;
+ _logger = logger;
}
#endregion
#region Endpoints
+ ///
+ /// Update a Queued Document.
+ ///
+ ///
+ [HttpPut("{documentQueueId:long}")]
+ [HasPermission(Permissions.SystemAdmin)]
+ [Produces("application/json")]
+ [ProducesResponseType(typeof(List), 200)]
+ [SwaggerOperation(Tags = new[] { "document-types" })]
+ [TypeFilter(typeof(NullJsonResultFilter))]
+ public IActionResult Update(long documentQueueId, [FromBody] DocumentQueueModel documentQueue)
+ {
+ _logger.LogInformation(
+ "Request received by Controller: {Controller}, Action: {ControllerAction}, User: {User}, DateTime: {DateTime}",
+ nameof(DocumentQueueController),
+ nameof(Update),
+ User.GetUsername(),
+ DateTime.Now);
+
+ documentQueue.ThrowIfNull(nameof(documentQueue));
+ if (documentQueueId != documentQueue.Id)
+ {
+ throw new BadRequestException("Invalid document queue id.");
+ }
+
+ var queuedDocuments = _documentQueueService.Update(_mapper.Map(documentQueue));
+ var updatedDocumentQueue = _mapper.Map(queuedDocuments);
+ return new JsonResult(updatedDocumentQueue);
+ }
+
+ ///
+ /// Poll a queud document to check on the upload status.
+ ///
+ ///
+ [HttpPost("{documentQueueId:long}/poll")]
+ [HasPermission(Permissions.SystemAdmin)]
+ [Produces("application/json")]
+ [ProducesResponseType(typeof(List), 200)]
+ [SwaggerOperation(Tags = new[] { "document-types" })]
+ [TypeFilter(typeof(NullJsonResultFilter))]
+ public async Task Poll(long documentQueueId, [FromBody] DocumentQueueModel documentQueue)
+ {
+ _logger.LogInformation(
+ "Request received by Controller: {Controller}, Action: {ControllerAction}, User: {User}, DateTime: {DateTime}",
+ nameof(DocumentQueueController),
+ nameof(Poll),
+ User.GetUsername(),
+ DateTime.Now);
+
+ documentQueue.ThrowIfNull(nameof(documentQueue));
+ if (documentQueueId != documentQueue.Id)
+ {
+ throw new BadRequestException("Invalid document queue id.");
+ }
+
+ var queuedDocuments = await _documentQueueService.PollForDocument(_mapper.Map(documentQueue));
+ var updatedDocumentQueue = _mapper.Map(queuedDocuments);
+ return new JsonResult(updatedDocumentQueue);
+ }
+
+ ///
+ /// Upload a Queued Document.
+ ///
+ ///
+ [HttpPost("{documentQueueId:long}/upload")]
+ [HasPermission(Permissions.SystemAdmin)]
+ [Produces("application/json")]
+ [ProducesResponseType(typeof(List), 200)]
+ [SwaggerOperation(Tags = new[] { "document-types" })]
+ [TypeFilter(typeof(NullJsonResultFilter))]
+ public async Task Upload(long documentQueueId, [FromBody] DocumentQueueModel documentQueue)
+ {
+ _logger.LogInformation(
+ "Request received by Controller: {Controller}, Action: {ControllerAction}, User: {User}, DateTime: {DateTime}",
+ nameof(DocumentQueueController),
+ nameof(Upload),
+ User.GetUsername(),
+ DateTime.Now);
+
+ documentQueue.ThrowIfNull(nameof(documentQueue));
+ if (documentQueueId != documentQueue.Id)
+ {
+ throw new BadRequestException("Invalid document queue id.");
+ }
+
+ var queuedDocuments = await _documentQueueService.Upload(_mapper.Map(documentQueue));
+ var updatedDocumentQueue = _mapper.Map(queuedDocuments);
+ return new JsonResult(updatedDocumentQueue);
+ }
+
///
/// Search for Document Queue items via filter.
///
///
- [HttpGet("search")]
+ [HttpPost("search")]
[HasPermission(Permissions.SystemAdmin)]
[Produces("application/json")]
- [ProducesResponseType(typeof(List), 200)]
+ [ProducesResponseType(typeof(List), 200)]
[SwaggerOperation(Tags = new[] { "document-types" })]
[TypeFilter(typeof(NullJsonResultFilter))]
- public IActionResult GetDocumentTypes([FromBody] DocumentQueueFilter filter)
+ public IActionResult SearchQueuedDocuments([FromBody] DocumentQueueFilter filter)
{
+ _logger.LogInformation(
+ "Request received by Controller: {Controller}, Action: {ControllerAction}, User: {User}, DateTime: {DateTime}",
+ nameof(DocumentQueueController),
+ nameof(SearchQueuedDocuments),
+ User.GetUsername(),
+ DateTime.Now);
+
var queuedDocuments = _documentQueueService.SearchDocumentQueue(filter);
- var documentQueueModels = _mapper.Map>(queuedDocuments);
+ var documentQueueModels = _mapper.Map>(queuedDocuments);
return new JsonResult(documentQueueModels);
}
+ ///
+ /// Get Document Queue item via id.
+ ///
+ ///
+ [HttpGet("{documentQueueId:long}")]
+ [HasPermission(Permissions.SystemAdmin)]
+ [Produces("application/json")]
+ [ProducesResponseType(typeof(List), 200)]
+ [SwaggerOperation(Tags = new[] { "document-types" })]
+ [TypeFilter(typeof(NullJsonResultFilter))]
+ public IActionResult GetQueuedDocument(long documentQueueId)
+ {
+ _logger.LogInformation(
+ "Request received by Controller: {Controller}, Action: {ControllerAction}, User: {User}, DateTime: {DateTime}",
+ nameof(DocumentQueueController),
+ nameof(GetQueuedDocument),
+ User.GetUsername(),
+ DateTime.Now);
+
+ var queuedDocuments = _documentQueueService.GetById(documentQueueId);
+ var documentQueueModel = _mapper.Map(queuedDocuments);
+ return new JsonResult(documentQueueModel);
+ }
+
#endregion
}
}
diff --git a/source/backend/api/Areas/Documents/DocumentRelationshipController.cs b/source/backend/api/Areas/Documents/DocumentRelationshipController.cs
index 5202ab1026..e0ca25a724 100644
--- a/source/backend/api/Areas/Documents/DocumentRelationshipController.cs
+++ b/source/backend/api/Areas/Documents/DocumentRelationshipController.cs
@@ -4,15 +4,15 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Pims.Api.Constants;
-using Pims.Core.Api.Exceptions;
using Pims.Api.Models.CodeTypes;
using Pims.Api.Models.Concepts.Document;
using Pims.Api.Models.Requests.Document.Upload;
-using Pims.Core.Api.Policies;
using Pims.Api.Services;
+using Pims.Core.Api.Exceptions;
+using Pims.Core.Api.Policies;
using Pims.Core.Json;
-using Pims.Dal.Entities;
using Pims.Core.Security;
+using Pims.Dal.Entities;
using Swashbuckle.AspNetCore.Annotations;
namespace Pims.Api.Controllers
@@ -142,19 +142,27 @@ public async Task UploadDocumentWithParent(
string parentId,
[FromForm] DocumentUploadRequest uploadRequest)
{
- var response = relationshipType switch
+ switch (relationshipType)
{
- DocumentRelationType.AcquisitionFiles => await _documentFileService.UploadAcquisitionDocumentAsync(long.Parse(parentId), uploadRequest),
- DocumentRelationType.ResearchFiles => await _documentFileService.UploadResearchDocumentAsync(long.Parse(parentId), uploadRequest),
- DocumentRelationType.Templates => await _formDocumentService.UploadFormDocumentTemplateAsync(parentId, uploadRequest),
- DocumentRelationType.Projects => await _documentFileService.UploadProjectDocumentAsync(long.Parse(parentId), uploadRequest),
- DocumentRelationType.Leases => await _documentFileService.UploadLeaseDocumentAsync(long.Parse(parentId), uploadRequest),
- DocumentRelationType.ManagementFiles => await _documentFileService.UploadPropertyActivityDocumentAsync(long.Parse(parentId), uploadRequest),
- DocumentRelationType.DispositionFiles => await _documentFileService.UploadDispositionDocumentAsync(long.Parse(parentId), uploadRequest),
- _ => throw new BadRequestException("Relationship type not valid for upload."),
- };
+ case DocumentRelationType.AcquisitionFiles:
+ await _documentFileService.UploadAcquisitionDocument(long.Parse(parentId), uploadRequest); break;
+ case DocumentRelationType.ResearchFiles:
+ await _documentFileService.UploadResearchDocument(long.Parse(parentId), uploadRequest); break;
+ case DocumentRelationType.Projects:
+ await _documentFileService.UploadProjectDocument(long.Parse(parentId), uploadRequest); break;
+ case DocumentRelationType.Leases:
+ await _documentFileService.UploadLeaseDocument(long.Parse(parentId), uploadRequest); break;
+ case DocumentRelationType.ManagementFiles:
+ await _documentFileService.UploadPropertyActivityDocument(long.Parse(parentId), uploadRequest); break;
+ case DocumentRelationType.DispositionFiles:
+ await _documentFileService.UploadDispositionDocument(long.Parse(parentId), uploadRequest); break;
+ case DocumentRelationType.Templates:
+ await _formDocumentService.UploadFormDocumentTemplateAsync(parentId, uploadRequest); break;
+ default:
+ throw new BadRequestException("Relationship type not valid for upload.");
+ }
- return new JsonResult(response);
+ return Ok();
}
///
@@ -169,7 +177,7 @@ public async Task UploadDocumentWithParent(
[ProducesResponseType(typeof(bool), 200)]
[SwaggerOperation(Tags = new[] { "document" })]
[TypeFilter(typeof(NullJsonResultFilter))]
- public async Task DeleteDocumentRelationship(DocumentRelationType relationshipType, [FromBody] DocumentRelationshipModel model)
+ public async Task DeleteDocumentRelationship([FromRoute]DocumentRelationType relationshipType, [FromBody]DocumentRelationshipModel model)
{
switch (relationshipType)
{
diff --git a/source/backend/api/Controllers/LookupController.cs b/source/backend/api/Controllers/LookupController.cs
index 600effdc15..21e902429e 100644
--- a/source/backend/api/Controllers/LookupController.cs
+++ b/source/backend/api/Controllers/LookupController.cs
@@ -133,6 +133,11 @@ public IActionResult GetAll()
var leasePaymentCategoryTypes = _mapper.Map(_lookupRepository.GetAllLeasePaymentCategoryTypes());
var consultationOutcomeTypes = _mapper.Map(_lookupRepository.GetAllConsultationOutcomeTypes());
var subfileInterestTypes = _mapper.Map(_lookupRepository.GetAllSubfileInterestTypes());
+ var acquisitionFileProgressStatuses = _mapper.Map(_lookupRepository.GetAllAcquisitionFileProgressStatusTypes());
+ var acquisitionFileAppraisalStatuses = _mapper.Map(_lookupRepository.GetAllAcquisitionFileAppraisalStatusTypes());
+ var acquisitionFileLegalSurveyStatuses = _mapper.Map(_lookupRepository.GetAllAcquisitionFileLegalSurveyStatusTypes());
+ var acquisitionFileTakeTypesStatuses = _mapper.Map(_lookupRepository.GetAllAcquisitionFileTakeStatusTypes());
+ var acquisitionFileExpropiationRiskStatuses = _mapper.Map(_lookupRepository.GetAllAcquisitionFileExpropiationRiskStatusTypes());
var codes = new List
public class DocumentFileService : BaseService, IDocumentFileService
{
- private readonly IAcquisitionFileDocumentRepository acquisitionFileDocumentRepository;
- private readonly IResearchFileDocumentRepository researchFileDocumentRepository;
- private readonly IDocumentService documentService;
+ private readonly IAcquisitionFileDocumentRepository _acquisitionFileDocumentRepository;
+ private readonly IResearchFileDocumentRepository _researchFileDocumentRepository;
+ private readonly IDocumentService _documentService;
private readonly IProjectRepository _projectRepository;
private readonly IDocumentRepository _documentRepository;
+ private readonly IDocumentQueueRepository _documentQueueRepository;
private readonly ILeaseRepository _leaseRepository;
private readonly IPropertyActivityDocumentRepository _propertyActivityDocumentRepository;
private readonly IDispositionFileDocumentRepository _dispositionFileDocumentRepository;
- private readonly IMapper mapper;
public DocumentFileService(
ClaimsPrincipal user,
@@ -39,23 +41,23 @@ public DocumentFileService(
IAcquisitionFileDocumentRepository acquisitionFileDocumentRepository,
IResearchFileDocumentRepository researchFileDocumentRepository,
IDocumentService documentService,
- IMapper mapper,
IProjectRepository projectRepository,
IDocumentRepository documentRepository,
ILeaseRepository leaseRepository,
IPropertyActivityDocumentRepository propertyActivityDocumentRepository,
- IDispositionFileDocumentRepository dispositionFileDocumentRepository)
+ IDispositionFileDocumentRepository dispositionFileDocumentRepository,
+ IDocumentQueueRepository documentQueueRepository)
: base(user, logger)
{
- this.acquisitionFileDocumentRepository = acquisitionFileDocumentRepository;
- this.researchFileDocumentRepository = researchFileDocumentRepository;
- this.documentService = documentService;
- this.mapper = mapper;
+ _acquisitionFileDocumentRepository = acquisitionFileDocumentRepository;
+ _researchFileDocumentRepository = researchFileDocumentRepository;
+ _documentService = documentService;
_projectRepository = projectRepository;
_documentRepository = documentRepository;
_leaseRepository = leaseRepository;
_propertyActivityDocumentRepository = propertyActivityDocumentRepository;
_dispositionFileDocumentRepository = dispositionFileDocumentRepository;
+ _documentQueueRepository = documentQueueRepository;
}
public IList GetFileDocuments(FileType fileType, long fileId)
@@ -68,10 +70,10 @@ public IList GetFileDocuments(FileType fileType, long fileId)
{
case FileType.Research:
User.ThrowIfNotAuthorized(Permissions.ResearchFileView);
- return researchFileDocumentRepository.GetAllByResearchFile(fileId).Select(f => f as T).ToArray();
+ return _researchFileDocumentRepository.GetAllByResearchFile(fileId).Select(f => f as T).ToArray();
case FileType.Acquisition:
User.ThrowIfNotAuthorized(Permissions.AcquisitionFileView);
- return acquisitionFileDocumentRepository.GetAllByAcquisitionFile(fileId).Select(f => f as T).ToArray();
+ return _acquisitionFileDocumentRepository.GetAllByAcquisitionFile(fileId).Select(f => f as T).ToArray();
case FileType.Project:
User.ThrowIfNotAuthorized(Permissions.ProjectView);
return _projectRepository.GetAllProjectDocuments(fileId).Select(f => f as T).ToArray();
@@ -89,210 +91,136 @@ public IList GetFileDocuments(FileType fileType, long fileId)
}
}
- public async Task UploadResearchDocumentAsync(long researchFileId, DocumentUploadRequest uploadRequest)
+ public async Task UploadAcquisitionDocument(long acquisitionFileId, DocumentUploadRequest uploadRequest)
{
- Logger.LogInformation("Uploading document for single research file");
- User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.ResearchFileEdit);
-
- // Do not call Mayan if uploaded file is empty (zero-size)
- ValidateZeroLengthFile(uploadRequest);
+ Logger.LogInformation("Uploading document for single acquisition file");
+ User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.AcquisitionFileEdit);
+ uploadRequest.ThrowInvalidFileSize();
- DocumentUploadResponse uploadResult = await documentService.UploadDocumentAsync(uploadRequest);
+ PimsDocument pimsDocument = CreatePimsDocument(uploadRequest);
+ _documentQueueRepository.SaveChanges();
- DocumentUploadRelationshipResponse relationshipResponse = new DocumentUploadRelationshipResponse()
+ PimsAcquisitionFileDocument newAcquisitionDocument = new()
{
- UploadResponse = uploadResult,
+ AcquisitionFileId = acquisitionFileId,
+ DocumentId = pimsDocument.DocumentId,
};
+ _acquisitionFileDocumentRepository.AddAcquisition(newAcquisitionDocument);
- // Throw an error if Mayan returns a null document. This means it wasn't able to store it.
- ValidateDocumentUploadResponse(uploadResult);
+ await GenerateQueuedDocumentItem(pimsDocument.DocumentId, uploadRequest);
+ _acquisitionFileDocumentRepository.CommitTransaction();
- if (uploadResult.Document is not null && uploadResult.Document.Id != 0)
- {
- // Create the pims document research file relationship
- PimsResearchFileDocument newResearchFileDocument = new PimsResearchFileDocument()
- {
- ResearchFileId = researchFileId,
- DocumentId = uploadResult.Document.Id,
- };
- newResearchFileDocument = researchFileDocumentRepository.AddResearch(newResearchFileDocument);
- researchFileDocumentRepository.CommitTransaction();
-
- relationshipResponse.DocumentRelationship = mapper.Map(newResearchFileDocument);
- }
-
- return relationshipResponse;
+ return;
}
- public async Task UploadAcquisitionDocumentAsync(long acquisitionFileId, DocumentUploadRequest uploadRequest)
+ public async Task UploadResearchDocument(long researchFileId, DocumentUploadRequest uploadRequest)
{
- Logger.LogInformation("Uploading document for single acquisition file");
- User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.AcquisitionFileEdit);
-
- // Do not call Mayan if uploaded file is empty (zero-size)
- ValidateZeroLengthFile(uploadRequest);
+ Logger.LogInformation("Uploading document for single research file");
+ User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.ResearchFileEdit);
+ uploadRequest.ThrowInvalidFileSize();
- DocumentUploadResponse uploadResult = await documentService.UploadDocumentAsync(uploadRequest);
+ PimsDocument pimsDocument = CreatePimsDocument(uploadRequest);
+ _documentQueueRepository.SaveChanges();
- DocumentUploadRelationshipResponse relationshipResponse = new DocumentUploadRelationshipResponse()
+ PimsResearchFileDocument newFileDocument = new()
{
- UploadResponse = uploadResult,
+ ResearchFileId = researchFileId,
+ DocumentId = pimsDocument.DocumentId,
};
+ _researchFileDocumentRepository.AddResearch(newFileDocument);
- // Throw an error if Mayan returns a null document. This means it wasn't able to store it.
- ValidateDocumentUploadResponse(uploadResult);
+ await GenerateQueuedDocumentItem(pimsDocument.DocumentId, uploadRequest);
+ _documentQueueRepository.CommitTransaction();
- if (uploadResult.Document is not null && uploadResult.Document.Id != 0)
- {
- // Create the pims document acquisition file relationship
- PimsAcquisitionFileDocument newAcquisitionDocument = new PimsAcquisitionFileDocument()
- {
- AcquisitionFileId = acquisitionFileId,
- DocumentId = uploadResult.Document.Id,
- };
- newAcquisitionDocument = acquisitionFileDocumentRepository.AddAcquisition(newAcquisitionDocument);
- acquisitionFileDocumentRepository.CommitTransaction();
-
- relationshipResponse.DocumentRelationship = mapper.Map(newAcquisitionDocument);
- }
-
- return relationshipResponse;
+ return;
}
- public async Task UploadProjectDocumentAsync(long projectId, DocumentUploadRequest uploadRequest)
+ public async Task UploadProjectDocument(long projectId, DocumentUploadRequest uploadRequest)
{
Logger.LogInformation("Uploading document for single Project");
User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.ProjectEdit);
+ uploadRequest.ThrowInvalidFileSize();
- // Do not call Mayan if uploaded file is empty (zero-size)
- ValidateZeroLengthFile(uploadRequest);
+ PimsDocument pimsDocument = CreatePimsDocument(uploadRequest);
+ _documentQueueRepository.SaveChanges();
- DocumentUploadResponse uploadResult = await documentService.UploadDocumentAsync(uploadRequest);
-
- DocumentUploadRelationshipResponse relationshipResponse = new()
+ PimsProjectDocument newFileDocument = new()
{
- UploadResponse = uploadResult,
+ ProjectId = projectId,
+ DocumentId = pimsDocument.DocumentId,
};
+ _projectRepository.AddProjectDocument(newFileDocument);
- // Throw an error if Mayan returns a null document. This means it wasn't able to store it.
- ValidateDocumentUploadResponse(uploadResult);
-
- if (uploadResult.Document is not null && uploadResult.Document.Id != 0)
- {
- PimsProjectDocument newProjectDocument = new()
- {
- ProjectId = projectId,
- DocumentId = uploadResult.Document.Id,
- };
- newProjectDocument = _projectRepository.AddProjectDocument(newProjectDocument);
- _projectRepository.CommitTransaction();
-
- relationshipResponse.DocumentRelationship = mapper.Map(newProjectDocument);
- }
+ await GenerateQueuedDocumentItem(pimsDocument.DocumentId, uploadRequest);
+ _documentQueueRepository.CommitTransaction();
- return relationshipResponse;
+ return;
}
- public async Task UploadLeaseDocumentAsync(long leaseId, DocumentUploadRequest uploadRequest)
+ public async Task UploadLeaseDocument(long leaseId, DocumentUploadRequest uploadRequest)
{
Logger.LogInformation("Uploading document for single Lease");
User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.LeaseEdit);
+ uploadRequest.ThrowInvalidFileSize();
- // Do not call Mayan if uploaded file is empty (zero-size)
- ValidateZeroLengthFile(uploadRequest);
-
- DocumentUploadResponse uploadResult = await documentService.UploadDocumentAsync(uploadRequest);
+ PimsDocument pimsDocument = CreatePimsDocument(uploadRequest);
+ _documentQueueRepository.SaveChanges();
- DocumentUploadRelationshipResponse relationshipResponse = new()
+ PimsLeaseDocument newFileDocument = new()
{
- UploadResponse = uploadResult,
+ LeaseId = leaseId,
+ DocumentId = pimsDocument.DocumentId,
};
+ _leaseRepository.AddLeaseDocument(newFileDocument);
- // Throw an error if Mayan returns a null document. This means it wasn't able to store it.
- ValidateDocumentUploadResponse(uploadResult);
-
- if (uploadResult.Document is not null && uploadResult.Document.Id != 0)
- {
- PimsLeaseDocument newDocument = new()
- {
- LeaseId = leaseId,
- DocumentId = uploadResult.Document.Id,
- };
- newDocument = _leaseRepository.AddLeaseDocument(newDocument);
- _leaseRepository.CommitTransaction();
-
- relationshipResponse.DocumentRelationship = mapper.Map(newDocument);
- }
+ await GenerateQueuedDocumentItem(pimsDocument.DocumentId, uploadRequest);
+ _documentQueueRepository.CommitTransaction();
- return relationshipResponse;
+ return;
}
- public async Task UploadPropertyActivityDocumentAsync(long propertyActivityId, DocumentUploadRequest uploadRequest)
+ public async Task UploadPropertyActivityDocument(long propertyActivityId, DocumentUploadRequest uploadRequest)
{
Logger.LogInformation("Uploading document for single Property Activity");
User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.ManagementEdit);
+ uploadRequest.ThrowInvalidFileSize();
- // Do not call Mayan if uploaded file is empty (zero-size)
- ValidateZeroLengthFile(uploadRequest);
-
- DocumentUploadResponse uploadResult = await documentService.UploadDocumentAsync(uploadRequest);
+ PimsDocument pimsDocument = CreatePimsDocument(uploadRequest);
+ _documentQueueRepository.SaveChanges();
- DocumentUploadRelationshipResponse relationshipResponse = new()
+ PimsPropertyActivityDocument newFileDocument = new()
{
- UploadResponse = uploadResult,
+ PimsPropertyActivityId = propertyActivityId,
+ DocumentId = pimsDocument.DocumentId,
};
+ _propertyActivityDocumentRepository.AddPropertyActivityDocument(newFileDocument);
- // Throw an error if Mayan returns a null document. This means it wasn't able to store it.
- ValidateDocumentUploadResponse(uploadResult);
+ await GenerateQueuedDocumentItem(pimsDocument.DocumentId, uploadRequest);
+ _documentQueueRepository.CommitTransaction();
- if (uploadResult.Document is not null && uploadResult.Document.Id != 0)
- {
- PimsPropertyActivityDocument newDocument = new()
- {
- PimsPropertyActivityId = propertyActivityId,
- DocumentId = uploadResult.Document.Id,
- };
- newDocument = _propertyActivityDocumentRepository.AddPropertyActivityDocument(newDocument);
- _propertyActivityDocumentRepository.CommitTransaction();
-
- relationshipResponse.DocumentRelationship = mapper.Map(newDocument);
- }
-
- return relationshipResponse;
+ return;
}
- public async Task UploadDispositionDocumentAsync(long dispositionFileId, DocumentUploadRequest uploadRequest)
+ public async Task UploadDispositionDocument(long dispositionFileId, DocumentUploadRequest uploadRequest)
{
Logger.LogInformation("Uploading document for single disposition file");
User.ThrowIfNotAllAuthorized(Permissions.DocumentAdd, Permissions.DispositionEdit);
+ uploadRequest.ThrowInvalidFileSize();
- // Do not call Mayan if uploaded file is empty (zero-size)
- ValidateZeroLengthFile(uploadRequest);
-
- DocumentUploadResponse uploadResult = await documentService.UploadDocumentAsync(uploadRequest);
+ PimsDocument pimsDocument = CreatePimsDocument(uploadRequest);
+ _documentQueueRepository.SaveChanges();
- DocumentUploadRelationshipResponse relationshipResponse = new()
+ PimsDispositionFileDocument newFileDocument = new()
{
- UploadResponse = uploadResult,
+ DispositionFileId = dispositionFileId,
+ DocumentId = pimsDocument.DocumentId,
};
+ _dispositionFileDocumentRepository.AddDispositionDocument(newFileDocument);
- // Throw an error if Mayan returns a null document. This means it wasn't able to store it.
- ValidateDocumentUploadResponse(uploadResult);
-
- if (uploadResult.Document is not null && uploadResult.Document.Id != 0)
- {
- PimsDispositionFileDocument newDocument = new()
- {
- DispositionFileId = dispositionFileId,
- DocumentId = uploadResult.Document.Id,
- };
- newDocument = _dispositionFileDocumentRepository.AddDispositionDocument(newDocument);
- _dispositionFileDocumentRepository.CommitTransaction();
-
- relationshipResponse.DocumentRelationship = mapper.Map(newDocument);
- }
+ await GenerateQueuedDocumentItem(pimsDocument.DocumentId, uploadRequest);
+ _documentQueueRepository.CommitTransaction();
- return relationshipResponse;
+ return;
}
public async Task> DeleteResearchDocumentAsync(PimsResearchFileDocument researchFileDocument)
@@ -300,17 +228,26 @@ public async Task> DeleteResearchDocumentAsync(PimsRese
Logger.LogInformation("Deleting PIMS document for single research file");
User.ThrowIfNotAllAuthorized(Permissions.DocumentDelete, Permissions.ResearchFileEdit);
- var relationshipCount = _documentRepository.DocumentRelationshipCount(researchFileDocument.DocumentId);
- if (relationshipCount == 1)
- {
- return await documentService.DeleteDocumentAsync(researchFileDocument.Document);
- }
- else
+ var result = new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ PimsDocument currentDocument = _documentRepository.Find(researchFileDocument.Document.DocumentId);
+
+ if (currentDocument.MayanId.HasValue && currentDocument.MayanId.Value > 0)
{
- researchFileDocumentRepository.DeleteResearch(researchFileDocument);
- researchFileDocumentRepository.CommitTransaction();
- return new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ result = await DeleteMayanDocument((long)currentDocument.MayanId);
+ currentDocument = RemoveDocumentMayanID(currentDocument);
+ _documentRepository.CommitTransaction();
}
+
+ using var transaction = _documentRepository.BeginTransaction();
+
+ DeleteQueuedDocumentItem(currentDocument.DocumentId);
+ _researchFileDocumentRepository.DeleteResearch(researchFileDocument);
+ DeleteDocument(currentDocument);
+
+ _documentRepository.SaveChanges();
+ transaction.Commit();
+
+ return result;
}
public async Task> DeleteProjectDocumentAsync(PimsProjectDocument projectDocument)
@@ -318,17 +255,26 @@ public async Task> DeleteProjectDocumentAsync(PimsProje
Logger.LogInformation("Deleting PIMS document for single Project");
User.ThrowIfNotAllAuthorized(Permissions.DocumentDelete, Permissions.ProjectEdit);
- var relationshipCount = _documentRepository.DocumentRelationshipCount(projectDocument.DocumentId);
- if (relationshipCount == 1)
- {
- return await documentService.DeleteDocumentAsync(projectDocument.Document);
- }
- else
+ var result = new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ PimsDocument currentDocument = _documentRepository.Find(projectDocument.Document.DocumentId);
+
+ if (currentDocument.MayanId.HasValue && currentDocument.MayanId.Value > 0)
{
- _projectRepository.DeleteProjectDocument(projectDocument.ProjectDocumentId);
- _projectRepository.CommitTransaction();
- return new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ result = await DeleteMayanDocument((long)currentDocument.MayanId);
+ currentDocument = RemoveDocumentMayanID(currentDocument);
+ _documentRepository.CommitTransaction();
}
+
+ using var transaction = _documentRepository.BeginTransaction();
+
+ DeleteQueuedDocumentItem(currentDocument.DocumentId);
+ _projectRepository.DeleteProjectDocument(projectDocument.ProjectDocumentId);
+ DeleteDocument(currentDocument);
+
+ _documentRepository.SaveChanges();
+ transaction.Commit();
+
+ return result;
}
public async Task> DeleteAcquisitionDocumentAsync(PimsAcquisitionFileDocument acquisitionFileDocument)
@@ -336,17 +282,28 @@ public async Task> DeleteAcquisitionDocumentAsync(PimsA
Logger.LogInformation("Deleting PIMS document for single acquisition file");
User.ThrowIfNotAllAuthorized(Permissions.DocumentDelete, Permissions.AcquisitionFileEdit);
- var relationshipCount = _documentRepository.DocumentRelationshipCount(acquisitionFileDocument.DocumentId);
- if (relationshipCount == 1)
- {
- return await documentService.DeleteDocumentAsync(acquisitionFileDocument.Document);
- }
- else
+ var result = new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ PimsDocument currentDocument = _documentRepository.Find(acquisitionFileDocument.Document.DocumentId);
+
+ if (currentDocument.MayanId.HasValue && currentDocument.MayanId.Value > 0)
{
- acquisitionFileDocumentRepository.DeleteAcquisition(acquisitionFileDocument);
- acquisitionFileDocumentRepository.CommitTransaction();
- return new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ result = await DeleteMayanDocument((long)acquisitionFileDocument.Document.MayanId);
+ currentDocument = RemoveDocumentMayanID(currentDocument);
+ _documentRepository.CommitTransaction();
}
+
+ using var transaction = _documentRepository.BeginTransaction();
+
+ DeleteQueuedDocumentItem(acquisitionFileDocument.DocumentId);
+
+ _acquisitionFileDocumentRepository.DeleteAcquisition(acquisitionFileDocument);
+
+ DeleteDocument(currentDocument);
+
+ _documentRepository.SaveChanges();
+ transaction.Commit();
+
+ return result;
}
public async Task> DeleteLeaseDocumentAsync(PimsLeaseDocument leaseDocument)
@@ -354,17 +311,28 @@ public async Task> DeleteLeaseDocumentAsync(PimsLeaseDo
Logger.LogInformation("Deleting PIMS document for single lease");
User.ThrowIfNotAllAuthorized(Permissions.DocumentDelete, Permissions.LeaseEdit);
- var relationshipCount = _documentRepository.DocumentRelationshipCount(leaseDocument.DocumentId);
- if (relationshipCount == 1)
- {
- return await documentService.DeleteDocumentAsync(leaseDocument.Document);
- }
- else
+ var result = new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ PimsDocument currentDocument = _documentRepository.Find(leaseDocument.Document.DocumentId);
+
+ // 1 - Delete Mayan first.
+ if (currentDocument.MayanId.HasValue && currentDocument.MayanId.Value > 0)
{
- _leaseRepository.DeleteLeaseDocument(leaseDocument.LeaseDocumentId);
- _leaseRepository.CommitTransaction();
- return new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ result = await DeleteMayanDocument((long)currentDocument.MayanId);
+ currentDocument = RemoveDocumentMayanID(currentDocument);
+ _documentRepository.CommitTransaction();
}
+
+ using var transaction = _documentRepository.BeginTransaction();
+
+ DeleteQueuedDocumentItem(currentDocument.DocumentId);
+ _leaseRepository.DeleteLeaseDocument(leaseDocument.LeaseDocumentId);
+
+ DeleteDocument(currentDocument);
+
+ _documentRepository.SaveChanges();
+ transaction.Commit();
+
+ return result;
}
public async Task> DeletePropertyActivityDocumentAsync(PimsPropertyActivityDocument propertyActivityDocument)
@@ -372,17 +340,27 @@ public async Task> DeletePropertyActivityDocumentAsync(
Logger.LogInformation("Deleting PIMS document for single Property Activity");
User.ThrowIfNotAllAuthorized(Permissions.DocumentDelete, Permissions.ManagementEdit);
- var relationshipCount = _documentRepository.DocumentRelationshipCount(propertyActivityDocument.DocumentId);
- if (relationshipCount == 1)
- {
- return await documentService.DeleteDocumentAsync(propertyActivityDocument.Document);
- }
- else
+ var result = new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ PimsDocument currentDocument = _documentRepository.Find(propertyActivityDocument.Document.DocumentId);
+
+ if (currentDocument.MayanId.HasValue && currentDocument.MayanId.Value > 0)
{
- _propertyActivityDocumentRepository.DeletePropertyActivityDocument(propertyActivityDocument);
- _propertyActivityDocumentRepository.CommitTransaction();
- return new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ result = await DeleteMayanDocument((long)currentDocument.MayanId);
+ currentDocument = RemoveDocumentMayanID(currentDocument);
+ _documentRepository.CommitTransaction();
}
+
+ using var transaction = _documentRepository.BeginTransaction();
+
+ DeleteQueuedDocumentItem(currentDocument.DocumentId);
+
+ _propertyActivityDocumentRepository.DeletePropertyActivityDocument(propertyActivityDocument);
+ DeleteDocument(currentDocument);
+
+ _documentRepository.SaveChanges();
+ transaction.Commit();
+
+ return result;
}
public async Task> DeleteDispositionDocumentAsync(PimsDispositionFileDocument dispositionFileDocument)
@@ -390,32 +368,99 @@ public async Task> DeleteDispositionDocumentAsync(PimsD
Logger.LogInformation("Deleting PIMS document for single disposition file");
User.ThrowIfNotAllAuthorized(Permissions.DocumentDelete, Permissions.DispositionEdit);
- var relationshipCount = _documentRepository.DocumentRelationshipCount(dispositionFileDocument.DocumentId);
- if (relationshipCount == 1)
+ var result = new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ PimsDocument currentDocument = _documentRepository.Find(dispositionFileDocument.DocumentId);
+
+ if (currentDocument.MayanId.HasValue && currentDocument.MayanId.Value > 0)
{
- return await documentService.DeleteDocumentAsync(dispositionFileDocument.Document);
+ result = await DeleteMayanDocument((long)currentDocument.MayanId);
+ currentDocument = RemoveDocumentMayanID(currentDocument);
+ _documentRepository.CommitTransaction(); // leave trace when mayan document deleted.
}
- else
+
+ using var transaction = _documentRepository.BeginTransaction();
+
+ DeleteQueuedDocumentItem(currentDocument.DocumentId);
+
+ _dispositionFileDocumentRepository.DeleteDispositionDocument(dispositionFileDocument);
+
+ DeleteDocument(currentDocument);
+
+ _documentRepository.SaveChanges();
+ transaction.Commit();
+
+ return result;
+ }
+
+ private PimsDocument CreatePimsDocument(DocumentUploadRequest uploadRequest, string documentExternalId = null)
+ {
+ // Create the pims document
+ PimsDocument newPimsDocument = new()
+ {
+ FileName = uploadRequest.File.FileName,
+ DocumentTypeId = uploadRequest.DocumentTypeId,
+ DocumentStatusTypeCode = uploadRequest.DocumentStatusCode,
+ MayanId = null,
+ DocumentExternalId = documentExternalId,
+ };
+
+ _documentRepository.Add(newPimsDocument);
+
+ return newPimsDocument;
+ }
+
+ private async Task GenerateQueuedDocumentItem(long documentId, DocumentUploadRequest uploadRequest)
+ {
+ PimsDocumentQueue queueDocument = new()
{
- _dispositionFileDocumentRepository.DeleteDispositionDocument(dispositionFileDocument);
- _dispositionFileDocumentRepository.CommitTransaction();
- return new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
+ DocumentId = documentId,
+ Document = await uploadRequest.File.GetBytes(),
+ DocumentMetadata = uploadRequest.DocumentMetadata != null ? JsonSerializer.Serialize(uploadRequest.DocumentMetadata) : null,
+ };
+ _documentQueueRepository.Add(queueDocument);
+ }
+
+ private async Task> DeleteMayanDocument(long mayanDocumentId)
+ {
+ var result = await _documentService.DeleteMayanStorageDocumentAsync(mayanDocumentId);
+ if (result.HttpStatusCode == HttpStatusCode.NotFound)
+ {
+ result.Status = ExternalResponseStatus.Success;
}
+
+ return result;
}
- private static void ValidateZeroLengthFile(DocumentUploadRequest uploadRequest)
+ private PimsDocument RemoveDocumentMayanID(PimsDocument doc)
{
- if (uploadRequest.File is not null && uploadRequest.File.Length == 0)
+ doc.MayanId = null;
+ return _documentRepository.Update(doc, false);
+ }
+
+ private void DeleteQueuedDocumentItem(long documentId)
+ {
+ var documentQueuedItem = _documentQueueRepository.GetByDocumentId(documentId);
+ if (documentQueuedItem.DocumentQueueStatusTypeCode == DocumentQueueStatusTypes.PENDING.ToString()
+ || documentQueuedItem.DocumentQueueStatusTypeCode == DocumentQueueStatusTypes.PROCESSING.ToString())
+ {
+ throw new BadRequestException("Doucment in process can not be deleted");
+ }
+
+ bool deleted = _documentQueueRepository.Delete(documentQueuedItem);
+ if(!deleted)
{
- throw new BadRequestException("The submitted file is empty");
+ Logger.LogWarning("Failed to delete Queued Document {documentId}", documentId);
+ throw new InvalidOperationException("Could not delete document queue item");
}
}
- private static void ValidateDocumentUploadResponse(DocumentUploadResponse uploadResult)
+ private void DeleteDocument(PimsDocument document)
{
- if (uploadResult.Document is null)
+ bool deleted = _documentRepository.DeleteDocument(document);
+ if (!deleted)
{
- throw new BadRequestException("Unexpected exception uploading file", new System.Exception(uploadResult.DocumentExternalResponse.Message));
+ Logger.LogWarning("Failed to delete Document {documentId}", document.DocumentId);
+ throw new InvalidOperationException("Could not delete document");
}
}
}
diff --git a/source/backend/api/Services/DocumentQueueService.cs b/source/backend/api/Services/DocumentQueueService.cs
index 4ffb2c4363..b4389ed017 100644
--- a/source/backend/api/Services/DocumentQueueService.cs
+++ b/source/backend/api/Services/DocumentQueueService.cs
@@ -1,7 +1,19 @@
+using System;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using System.Security.Claims;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using Pims.Api.Models.CodeTypes;
+using Pims.Api.Models.Concepts.Document;
+using Pims.Api.Models.Mayan.Document;
+using Pims.Api.Models.Requests.Document.Upload;
+using Pims.Api.Models.Requests.Http;
+using Pims.Core.Api.Exceptions;
using Pims.Core.Api.Services;
using Pims.Core.Extensions;
using Pims.Core.Http.Configuration;
@@ -17,26 +29,328 @@ namespace Pims.Api.Services
///
public class DocumentQueueService : BaseService, IDocumentQueueService
{
- private readonly IDocumentQueueRepository documentQueueRepository;
- private readonly IOptionsMonitor keycloakOptions;
+ private readonly IDocumentQueueRepository _documentQueueRepository;
+ private readonly IDocumentRepository _documentRepository;
+ private readonly IDocumentTypeRepository _documentTypeRepository;
+ private readonly IDocumentService _documentService;
+ private readonly IOptionsMonitor _keycloakOptions;
public DocumentQueueService(
ClaimsPrincipal user,
ILogger logger,
IDocumentQueueRepository documentQueueRepository,
+ IDocumentRepository documentRepository,
+ IDocumentTypeRepository documentTypeRepository,
+ IDocumentService documentService,
IOptionsMonitor options)
: base(user, logger)
{
- this.documentQueueRepository = documentQueueRepository;
- this.keycloakOptions = options;
+ this._documentQueueRepository = documentQueueRepository;
+ this._documentRepository = documentRepository;
+ this._documentTypeRepository = documentTypeRepository;
+ this._documentService = documentService;
+ this._keycloakOptions = options;
}
+ ///
+ /// Get document in the document queue based on the specified id.
+ ///
+ /// The id of the document in the queue.
+ /// that match the id criteria.
+ /// Thrown when the user is not authorized to perform this operation.
+ /// If the requested Id does not exist.
+ public PimsDocumentQueue GetById(long documentQueueId)
+ {
+ this.Logger.LogInformation("Retrieving queued PIMS document using id {documentQueueId}", documentQueueId);
+ this.User.ThrowIfNotAuthorizedOrServiceAccount(Permissions.SystemAdmin, this._keycloakOptions);
+
+ var documentQueue = _documentQueueRepository.TryGetById(documentQueueId);
+ if (documentQueue == null)
+ {
+ throw new KeyNotFoundException($"Unable to find queued document by id: ${documentQueueId}");
+ }
+
+ return documentQueue;
+ }
+
+ ///
+ /// Searches for documents in the document queue based on the specified filter.
+ ///
+ /// The filter criteria to apply when searching the document queue.
+ /// An enumerable collection of that match the filter criteria.
+ /// Thrown when the user is not authorized to perform this operation.
public IEnumerable SearchDocumentQueue(DocumentQueueFilter filter)
{
- this.Logger.LogInformation("Retrieving queued PIMS documents using filter {filter}", filter);
- this.User.ThrowIfNotAuthorizedOrServiceAccount(Permissions.SystemAdmin, this.keycloakOptions);
+ this.Logger.LogInformation("Retrieving queued PIMS documents using filter {filter}", filter.Serialize());
+ this.User.ThrowIfNotAuthorizedOrServiceAccount(Permissions.SystemAdmin, this._keycloakOptions);
+
+ var queuedDocuments = _documentQueueRepository.GetAllByFilter(filter);
+
+ if (filter.MaxFileSize != null)
+ {
+ List documentsBelowMaxFileSize = new List();
+ long totalFileSize = 0;
+ queuedDocuments.ForEach(currentDocument =>
+ {
+ if (currentDocument.DocumentSize + totalFileSize <= filter.MaxFileSize)
+ {
+ totalFileSize += currentDocument.DocumentSize;
+ documentsBelowMaxFileSize.Add(currentDocument);
+ }
+ });
+ if(documentsBelowMaxFileSize.Count == 0 && queuedDocuments.Any())
+ {
+ documentsBelowMaxFileSize.Add(queuedDocuments.FirstOrDefault());
+ }
+ this.Logger.LogDebug("returning {length} documents below file size", documentsBelowMaxFileSize.Count);
+ return documentsBelowMaxFileSize;
+ }
+ return queuedDocuments;
+ }
+
+ ///
+ /// Updates the specified document queue.
+ ///
+ /// The document queue object to update.
+ /// The updated document queue object.
+ /// Thrown when the user is not authorized to perform this operation.
+ public PimsDocumentQueue Update(PimsDocumentQueue documentQueue)
+ {
+ this.Logger.LogInformation("Updating queued document {documentQueueId}", documentQueue.DocumentQueueId);
+ this.Logger.LogDebug("Incoming queued document {document}", documentQueue.Serialize());
+
+ this.User.ThrowIfNotAuthorizedOrServiceAccount(Permissions.SystemAdmin, this._keycloakOptions);
+
+ _documentQueueRepository.Update(documentQueue);
+ _documentQueueRepository.CommitTransaction();
+ return documentQueue;
+ }
+
+ ///
+ /// Polls for the status of a document in mayan, and updates the queue based on the result.
+ ///
+ /// The document queue object containing the document details.
+ /// A task that represents the asynchronous operation. The task result contains the updated document queue object, or null if the polling failed.
+ /// Thrown when the user is not authorized to perform this operation.
+ /// Thrown when the document queue does not have a valid document ID or related document.
+ public async Task PollForDocument(PimsDocumentQueue documentQueue)
+ {
+ this.Logger.LogInformation("Polling queued document {documentQueueId}", documentQueue.DocumentQueueId);
+ this.Logger.LogDebug("Polling queued document {document}", documentQueue.Serialize());
+
+ this.User.ThrowIfNotAuthorizedOrServiceAccount(Permissions.SystemAdmin, this._keycloakOptions);
+ if (documentQueue.DocumentId == null)
+ {
+ this.Logger.LogError("polled queued document does not have a document Id {documentQueueId}", documentQueue.DocumentQueueId);
+ throw new InvalidDataException("DocumentId is required to poll for a document.");
+ }
+
+ var databaseDocumentQueue = _documentQueueRepository.TryGetById(documentQueue.DocumentQueueId);
+ if (databaseDocumentQueue == null)
+ {
+ this.Logger.LogError("Unable to find document queue with {id}", documentQueue.DocumentQueueId);
+ throw new KeyNotFoundException($"Unable to find document queue with matching id: {documentQueue.DocumentQueueId}");
+ }
+ else if (databaseDocumentQueue.DocumentQueueStatusTypeCode != DocumentQueueStatusTypes.PROCESSING.ToString())
+ {
+ this.Logger.LogError("Document Queue {documentQueueId} is not in valid state, aborting poll.", documentQueue.DocumentQueueId);
+ return databaseDocumentQueue;
+ }
+
+ var relatedDocument = _documentRepository.TryGet(documentQueue.DocumentId.Value);
+
+ if (relatedDocument?.MayanId == null || relatedDocument?.MayanId < 0)
+ {
+ this.Logger.LogError("Queued Document {documentQueueId} has no mayan id and is invalid.", documentQueue.DocumentQueueId);
+ databaseDocumentQueue.MayanError = "Document does not have a valid MayanId.";
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.PIMS_ERROR);
+ return databaseDocumentQueue;
+ }
+
+ ExternalResponse documentDetailsResponse = await _documentService.GetStorageDocumentDetail(relatedDocument.MayanId.Value);
+
+ if (documentDetailsResponse.Status != ExternalResponseStatus.Success || documentDetailsResponse?.Payload == null)
+ {
+ this.Logger.LogError("Polling for queued document {documentQueueId} failed with status {documentDetailsResponseStatus}", documentQueue.DocumentQueueId, documentDetailsResponse.Status);
+ databaseDocumentQueue.MayanError = "Document Polling failed.";
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.PIMS_ERROR);
+ return databaseDocumentQueue;
+ }
+
+ if (documentDetailsResponse.Payload.FileLatest?.Id == null)
+ {
+ this.Logger.LogInformation("Polling for queued document {documentQueueId} complete, file still processing", documentQueue.DocumentQueueId);
+ }
+ else
+ {
+ this.Logger.LogInformation("Polling for queued document {documentQueueId} complete, file uploaded successfully", documentQueue.DocumentQueueId);
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.SUCCESS);
+ }
- return documentQueueRepository.GetAllByFilter(filter);
+ return databaseDocumentQueue;
+ }
+
+ ///
+ /// Uploads the specified document queue.
+ ///
+ /// The document queue object containing the document to upload.
+ /// A task that represents the asynchronous operation. The task result contains the updated document queue object, or null if the upload failed.
+ /// Thrown when the user is not authorized to perform this operation.
+ /// Thrown when the document queue does not have a valid document ID or related document.
+ public async Task Upload(PimsDocumentQueue documentQueue)
+ {
+ this.Logger.LogInformation("Uploading queued document {documentQueueId}", documentQueue.DocumentQueueId);
+ this.Logger.LogDebug("Uploading queued document {document}", documentQueue.Serialize());
+
+ this.User.ThrowIfNotAuthorizedOrServiceAccount(Permissions.SystemAdmin, this._keycloakOptions);
+
+ var databaseDocumentQueue = _documentQueueRepository.TryGetById(documentQueue.DocumentQueueId);
+ if (databaseDocumentQueue == null)
+ {
+ this.Logger.LogError("Unable to find document queue with {id}", documentQueue.DocumentQueueId);
+ throw new KeyNotFoundException($"Unable to find document queue with matching id: {documentQueue.DocumentQueueId}");
+ }
+ databaseDocumentQueue.DocProcessStartDt = DateTime.UtcNow;
+
+ // if the document queued for upload is already in an error state, update the retries.
+ if (databaseDocumentQueue.DocumentQueueStatusTypeCode == DocumentQueueStatusTypes.PIMS_ERROR.ToString() || databaseDocumentQueue.DocumentQueueStatusTypeCode == DocumentQueueStatusTypes.MAYAN_ERROR.ToString())
+ {
+ this.Logger.LogDebug("Document Queue {documentQueueId}, previously errored, retrying", documentQueue.DocumentQueueId);
+ databaseDocumentQueue.DocProcessRetries += 1;
+ databaseDocumentQueue.DocProcessEndDt = null;
+ }
+
+ bool isValid = ValidateQueuedDocument(databaseDocumentQueue, documentQueue);
+ if (!isValid)
+ {
+ this.Logger.LogDebug("Document Queue {documentQueueId}, invalid, aborting upload.", documentQueue.DocumentQueueId);
+ databaseDocumentQueue.MayanError = "Document is invalid.";
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.PIMS_ERROR);
+ return databaseDocumentQueue;
+ }
+
+ PimsDocument relatedDocument = null;
+ relatedDocument = _documentRepository.TryGetDocumentRelationships(databaseDocumentQueue.DocumentId.Value);
+ if (relatedDocument?.DocumentTypeId == null)
+ {
+ databaseDocumentQueue.MayanError = "Document does not have a valid DocumentType.";
+ this.Logger.LogError("Queued document {documentQueueId} does not have a related PIMS_DOCUMENT {documentId} with valid DocumentType, aborting.", databaseDocumentQueue.DocumentQueueId, relatedDocument?.DocumentId);
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.PIMS_ERROR);
+ return databaseDocumentQueue;
+ }
+ else if (relatedDocument?.MayanId != null && relatedDocument?.MayanId > 0)
+ {
+ this.Logger.LogInformation("Queued document {documentQueueId} already has a mayan id {mayanid}, no further processing required.", databaseDocumentQueue.DocumentQueueId, relatedDocument.MayanId);
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.SUCCESS);
+ return databaseDocumentQueue; // The document poll job should pick this up and fix the document queue status.
+ }
+
+ try
+ {
+ PimsDocumentTyp documentTyp = _documentTypeRepository.GetById(relatedDocument.DocumentTypeId); // throws KeyNotFoundException if not found.
+
+ IFormFile file = null;
+ using MemoryStream memStream = new(databaseDocumentQueue.Document);
+ file = new FormFile(memStream, 0, databaseDocumentQueue.Document.Length, relatedDocument.FileName, relatedDocument.FileName);
+
+ DocumentUploadRequest request = new DocumentUploadRequest()
+ {
+ File = file,
+ DocumentStatusCode = relatedDocument.DocumentStatusTypeCode,
+ DocumentTypeId = relatedDocument.DocumentTypeId,
+ DocumentTypeMayanId = documentTyp.MayanId,
+ DocumentId = relatedDocument.DocumentId,
+ DocumentMetadata = databaseDocumentQueue.DocumentMetadata != null ? JsonSerializer.Deserialize>(databaseDocumentQueue.DocumentMetadata) : null,
+ };
+ this.Logger.LogDebug("Document Queue {documentQueueId}, beginning upload.", documentQueue.DocumentQueueId);
+ DocumentUploadResponse response = await _documentService.UploadDocumentAsync(request, true);
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.PROCESSING); // Set the status to processing, as the document is now being uploaded. Must be set after the mayan id is set, so that the poll logic functions correctly.
+
+ if (response.DocumentExternalResponse.Status != ExternalResponseStatus.Success || response?.DocumentExternalResponse?.Payload == null)
+ {
+ this.Logger.LogError(
+ "Queued document upload failed {databaseDocumentQueueDocumentQueueId} {databaseDocumentQueueDocumentQueueStatusTypeCode}, {documentExternalResponseStatus}",
+ databaseDocumentQueue.DocumentQueueId,
+ databaseDocumentQueue.DocumentQueueStatusTypeCode,
+ response.DocumentExternalResponse.Status);
+
+ databaseDocumentQueue.MayanError = $"Failed to upload document, mayan error: {response.DocumentExternalResponse.Message}";
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.MAYAN_ERROR);
+ return databaseDocumentQueue;
+ }
+ response.MetadataExternalResponse.Where(r => r.Status != ExternalResponseStatus.Success).ForEach(r => this.Logger.LogError("url: ${url} status: ${status} message ${message}", r.Payload.Url, r.Status, r.Message)); // Log any metadata errors, but don't fail the upload.
+
+ // Mayan may have already returned a file id from the original upload. If not, this job will remain in the processing state (to be periodically checked for completion in another job).
+ if (response.DocumentExternalResponse?.Payload?.FileLatest?.Id != null)
+ {
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.SUCCESS);
+ }
+ }
+ catch (Exception ex) when (ex is BadRequestException || ex is KeyNotFoundException || ex is InvalidDataException || ex is JsonException)
+ {
+ this.Logger.LogError($"Error: {ex.Message}");
+ databaseDocumentQueue.MayanError = ex.Message;
+ UpdateDocumentQueueStatus(databaseDocumentQueue, DocumentQueueStatusTypes.PIMS_ERROR);
+ }
+ return databaseDocumentQueue;
+ }
+
+ ///
+ /// Updates the status of the specified document queue.
+ ///
+ /// The document queue object to update.
+ /// The new status type to set for the document queue.
+ ///
+ /// This method updates the document queue's status and commits the transaction.
+ /// If the status is a final state, it also updates the processing end date.
+ ///
+ private void UpdateDocumentQueueStatus(PimsDocumentQueue documentQueue, DocumentQueueStatusTypes statusType)
+ {
+ documentQueue.DocumentQueueStatusTypeCode = statusType.ToString();
+ bool removeDocument = false;
+
+ // Any final states should update the processing end date.
+ if (statusType != DocumentQueueStatusTypes.PROCESSING && statusType != DocumentQueueStatusTypes.PENDING)
+ {
+ documentQueue.DocProcessEndDt = DateTime.UtcNow;
+ if (statusType == DocumentQueueStatusTypes.SUCCESS)
+ {
+ documentQueue.Document = null;
+ removeDocument = true;
+ }
+ }
+ _documentQueueRepository.Update(documentQueue, removeDocument);
+ _documentQueueRepository.CommitTransaction();
+ }
+
+ ///
+ /// Validates the queued document against the database document queue.
+ ///
+ /// The document queue object from the database.
+ /// The document queue object to validate against the database.
+ /// True if the queued document is valid; otherwise, false.
+ ///
+ /// This method checks if the status type, process retries, and document content are valid.
+ /// It also ensures that at least one file document ID is associated with the document.
+ ///
+ private bool ValidateQueuedDocument(PimsDocumentQueue databaseDocumentQueue, PimsDocumentQueue externalDocument)
+ {
+ if (databaseDocumentQueue.DocumentQueueStatusTypeCode != externalDocument.DocumentQueueStatusTypeCode)
+ {
+ this.Logger.LogError("Requested document queue status: {documentQueueStatusTypeCode} does not match current database status: {documentQueueStatusTypeCode}", externalDocument.DocumentQueueStatusTypeCode, databaseDocumentQueue.DocumentQueueStatusTypeCode);
+ return false;
+ }
+ else if (databaseDocumentQueue.DocProcessRetries != externalDocument.DocProcessRetries)
+ {
+ this.Logger.LogError("Requested document retries: {documentQueueStatusTypeCode} does not match current database retries: {documentQueueStatusTypeCode}", externalDocument.DocumentQueueStatusTypeCode, databaseDocumentQueue.DocumentQueueStatusTypeCode);
+ return false;
+ }
+ else if (databaseDocumentQueue.Document == null || databaseDocumentQueue.DocumentId == null)
+ {
+ this.Logger.LogError("Queued document file content is empty, unable to upload.");
+ return false;
+ }
+ return true;
}
}
}
diff --git a/source/backend/api/Services/DocumentService.cs b/source/backend/api/Services/DocumentService.cs
index 333fdb1c2b..2407d796ed 100644
--- a/source/backend/api/Services/DocumentService.cs
+++ b/source/backend/api/Services/DocumentService.cs
@@ -83,7 +83,8 @@ public DocumentService(
IDocumentTypeRepository documentTypeRepository,
IAvService avService,
IMapper mapper,
- IOptionsMonitor options)
+ IOptionsMonitor options,
+ IDocumentQueueRepository queueRepository)
: base(user, logger)
{
this.documentRepository = documentRepository;
@@ -136,9 +137,9 @@ public IList GetPimsDocumentTypes(DocumentRelationType relation
return documentTypeRepository.GetByCategory(categoryType);
}
- public async Task UploadDocumentAsync(DocumentUploadRequest uploadRequest)
+ public async Task UploadDocumentSync(DocumentUploadRequest uploadRequest)
{
- this.Logger.LogInformation("Uploading document");
+ this.Logger.LogInformation("Uploading document and waiting for mayan upload.");
this.User.ThrowIfNotAuthorized(Permissions.DocumentAdd);
ExternalResponse externalResponse = await UploadDocumentAsync(uploadRequest.DocumentTypeMayanId, uploadRequest.File);
@@ -173,6 +174,7 @@ public async Task UploadDocumentAsync(DocumentUploadRequ
{
_ = PrecacheDocumentPreviews(externalDocument.Id, externalDocument.FileLatest.Id);
}
+
// Create metadata of document
if (uploadRequest.DocumentMetadata != null)
{
@@ -209,6 +211,63 @@ public async Task UploadDocumentAsync(DocumentUploadRequ
return response;
}
+ public async Task UploadDocumentAsync(DocumentUploadRequest uploadRequest, bool skipExtensionCheck = false)
+ {
+ this.Logger.LogInformation("Uploading document, do not wait for mayan processing. documentId: {documentId}", uploadRequest.DocumentId);
+ this.User.ThrowIfNotAuthorized(Permissions.DocumentAdd);
+
+ ExternalResponse externalResponse = await UploadDocumentAsync(uploadRequest.DocumentTypeMayanId, uploadRequest.File, skipExtensionCheck);
+ DocumentUploadResponse response = new DocumentUploadResponse()
+ {
+ DocumentExternalResponse = externalResponse,
+ MetadataExternalResponse = new List>(),
+ };
+
+ PimsDocument databaseDocument = documentRepository.TryGet(uploadRequest.DocumentId);
+ response.Document = databaseDocument != null ? mapper.Map(databaseDocument) : null;
+
+ if (response?.DocumentExternalResponse?.Payload?.Id != null && response?.DocumentExternalResponse?.Payload?.Id > 0 && databaseDocument != null)
+ {
+ // Create metadata of document
+ if (uploadRequest.DocumentMetadata != null)
+ {
+ List creates = new List();
+ foreach (var metadata in uploadRequest.DocumentMetadata)
+ {
+ if (!string.IsNullOrEmpty(metadata.Value))
+ {
+ creates.Add(metadata);
+ }
+ }
+
+ response.MetadataExternalResponse = await CreateMetadata(response.DocumentExternalResponse.Payload.Id, creates);
+ }
+
+ databaseDocument.MayanId = response.DocumentExternalResponse.Payload.Id;
+ documentRepository.Update(databaseDocument);
+ documentRepository.CommitTransaction();
+ }
+ else
+ {
+ this.Logger.LogError("Failed to update associated PIMS document with uploaded Mayan Id. documentId: {documentId}", uploadRequest.DocumentId);
+ this.Logger.LogDebug("Mayan response: {response}", response.Serialize());
+ }
+
+ return response;
+ }
+
+ public PimsDocument AddDocument(PimsDocument newPimsDocument)
+ {
+ this.Logger.LogInformation("Adding document uploaded asynchronously.");
+ this.User.ThrowIfNotAuthorized(Permissions.DocumentAdd);
+ newPimsDocument.ThrowIfNull(nameof(newPimsDocument));
+
+ documentRepository.Add(newPimsDocument);
+ documentRepository.CommitTransaction();
+
+ return newPimsDocument;
+ }
+
public async Task UpdateDocumentAsync(DocumentUpdateRequest updateRequest)
{
this.Logger.LogInformation("Updating document {documentId}", updateRequest.DocumentId);
@@ -314,8 +373,8 @@ public async Task UpdateDocumentAsync(DocumentUpdateRequ
public async Task> DeleteDocumentAsync(PimsDocument document)
{
- this.Logger.LogInformation("Deleting document {documentId}", document.Internal_Id);
- this.User.ThrowIfNotAuthorized(Permissions.DocumentDelete);
+ Logger.LogInformation("Deleting document {documentId}", document.Internal_Id);
+ User.ThrowIfNotAuthorized(Permissions.DocumentDelete);
var result = new ExternalResponse() { Status = ExternalResponseStatus.NotExecuted };
if (document.MayanId.HasValue)
@@ -337,6 +396,26 @@ public async Task> DeleteDocumentAsync(PimsDocument doc
{
throw GetMayanResponseError(result.Message);
}
+
+ return result;
+ }
+
+ public async Task> DeleteMayanStorageDocumentAsync(long mayanDocumentId)
+ {
+ Logger.LogInformation("Deleting Mayan document {documentId}", mayanDocumentId);
+ User.ThrowIfNotAuthorized(Permissions.DocumentDelete);
+
+ ExternalResponse result = await documentStorageRepository.TryDeleteDocument(mayanDocumentId);
+ if(result.Status == ExternalResponseStatus.Error && result.HttpStatusCode == HttpStatusCode.NotFound)
+ {
+ return result;
+ }
+
+ if (result.Status == ExternalResponseStatus.Error || result.Status == ExternalResponseStatus.NotExecuted)
+ {
+ throw GetMayanResponseError(result.Message);
+ }
+
return result;
}
@@ -591,13 +670,13 @@ private async Task PrecacheDocumentPreviews(long documentId, long documentFileId
}
}
- private async Task> UploadDocumentAsync(long documentType, IFormFile fileRaw)
+ private async Task> UploadDocumentAsync(long documentType, IFormFile fileRaw, bool skipExtensionCheck = false)
{
this.Logger.LogInformation("Uploading storage document {documentType}", documentType);
this.User.ThrowIfNotAuthorized(Permissions.DocumentAdd);
await this.avService.ScanAsync(fileRaw);
- if (IsValidDocumentExtension(fileRaw.FileName))
+ if (skipExtensionCheck || IsValidDocumentExtension(fileRaw.FileName))
{
ExternalResponse result = await documentStorageRepository.TryUploadDocumentAsync(documentType, fileRaw);
return result;
diff --git a/source/backend/api/Services/FormDocumentService.cs b/source/backend/api/Services/FormDocumentService.cs
index 00cab505bd..b95ece2431 100644
--- a/source/backend/api/Services/FormDocumentService.cs
+++ b/source/backend/api/Services/FormDocumentService.cs
@@ -80,14 +80,14 @@ public async Task UploadFormDocumentTemplate
}
}
- DocumentUploadResponse uploadResult = await _documentService.UploadDocumentAsync(uploadRequest);
+ DocumentUploadResponse uploadResult = await _documentService.UploadDocumentSync(uploadRequest);
DocumentUploadRelationshipResponse relationshipResponse = new DocumentUploadRelationshipResponse()
{
UploadResponse = uploadResult,
};
- if (uploadResult.DocumentExternalResponse.Status == ExternalResponseStatus.Success && uploadResult.Document != null && uploadResult.Document.Id != 0)
+ if (uploadResult.DocumentExternalResponse.Status == ExternalResponseStatus.Success)
{
currentFormType.DocumentId = uploadResult.Document.Id;
var updatedFormType = _formTypeRepository.SetFormTypeDocument(currentFormType);
@@ -138,7 +138,7 @@ public PimsAcquisitionFileForm AddAcquisitionForm(PimsFormType formType, long ac
public IEnumerable GetAcquisitionForms(long acquisitionFileId)
{
- _logger.LogInformation("Getting acquisition forms by acquisition file id ...", acquisitionFileId);
+ _logger.LogInformation("Getting acquisition forms by acquisition file id {acquisitionFileId}", acquisitionFileId);
this.User.ThrowIfNotAuthorized(Permissions.FormView, Permissions.AcquisitionFileView);
var fileForms = _acquisitionFileFormRepository.GetAllByAcquisitionFileId(acquisitionFileId);
@@ -147,7 +147,7 @@ public IEnumerable GetAcquisitionForms(long acquisition
public PimsAcquisitionFileForm GetAcquisitionForm(long fileFormId)
{
- _logger.LogInformation("Getting acquisition form by form file id ...", fileFormId);
+ _logger.LogInformation("Getting acquisition form by form file id {fileFormId}", fileFormId);
this.User.ThrowIfNotAuthorized(Permissions.FormView, Permissions.AcquisitionFileView);
var fileForm = _acquisitionFileFormRepository.GetByAcquisitionFileFormId(fileFormId);
@@ -156,7 +156,7 @@ public PimsAcquisitionFileForm GetAcquisitionForm(long fileFormId)
public bool DeleteAcquisitionFileForm(long fileFormId)
{
- _logger.LogInformation("Deleting acquisition file form id ...", fileFormId);
+ _logger.LogInformation("Deleting acquisition file form id {fileFormId}", fileFormId);
this.User.ThrowIfNotAuthorized(Permissions.FormDelete, Permissions.AcquisitionFileEdit);
var fileFormToDelete = _acquisitionFileFormRepository.TryDelete(fileFormId);
diff --git a/source/backend/api/Services/IDocumentFileService.cs b/source/backend/api/Services/IDocumentFileService.cs
index 987f34bcc2..aa56e119fb 100644
--- a/source/backend/api/Services/IDocumentFileService.cs
+++ b/source/backend/api/Services/IDocumentFileService.cs
@@ -15,17 +15,17 @@ public interface IDocumentFileService
public IList GetFileDocuments(FileType fileType, long fileId)
where T : PimsFileDocument;
- Task UploadResearchDocumentAsync(long researchFileId, DocumentUploadRequest uploadRequest);
+ Task UploadAcquisitionDocument(long acquisitionFileId, DocumentUploadRequest uploadRequest);
- Task UploadAcquisitionDocumentAsync(long acquisitionFileId, DocumentUploadRequest uploadRequest);
+ Task UploadResearchDocument(long researchFileId, DocumentUploadRequest uploadRequest);
- Task UploadLeaseDocumentAsync(long leaseId, DocumentUploadRequest uploadRequest);
+ Task UploadProjectDocument(long projectId, DocumentUploadRequest uploadRequest);
- Task UploadProjectDocumentAsync(long projectId, DocumentUploadRequest uploadRequest);
+ Task UploadLeaseDocument(long leaseId, DocumentUploadRequest uploadRequest);
- Task UploadPropertyActivityDocumentAsync(long propertyActivityId, DocumentUploadRequest uploadRequest);
+ Task UploadPropertyActivityDocument(long propertyActivityId, DocumentUploadRequest uploadRequest);
- Task UploadDispositionDocumentAsync(long dispositionFileId, DocumentUploadRequest uploadRequest);
+ Task UploadDispositionDocument(long dispositionFileId, DocumentUploadRequest uploadRequest);
Task> DeleteResearchDocumentAsync(PimsResearchFileDocument researchFileDocument);
diff --git a/source/backend/api/Services/IDocumentQueueService.cs b/source/backend/api/Services/IDocumentQueueService.cs
index b1f3c09206..2a797d7df8 100644
--- a/source/backend/api/Services/IDocumentQueueService.cs
+++ b/source/backend/api/Services/IDocumentQueueService.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Threading.Tasks;
using Pims.Dal.Entities;
using Pims.Dal.Entities.Models;
@@ -9,6 +10,14 @@ namespace Pims.Api.Services
///
public interface IDocumentQueueService
{
+ public PimsDocumentQueue GetById(long documentQueueId);
+
public IEnumerable SearchDocumentQueue(DocumentQueueFilter filter);
+
+ public PimsDocumentQueue Update(PimsDocumentQueue documentQueue);
+
+ public Task PollForDocument(PimsDocumentQueue documentQueue);
+
+ public Task Upload(PimsDocumentQueue documentQueue);
}
}
diff --git a/source/backend/api/Services/IDocumentService.cs b/source/backend/api/Services/IDocumentService.cs
index ebcd1ded2e..c37d3b6552 100644
--- a/source/backend/api/Services/IDocumentService.cs
+++ b/source/backend/api/Services/IDocumentService.cs
@@ -38,10 +38,14 @@ public interface IDocumentService
IList GetPimsDocumentTypes(DocumentRelationType relationshipType);
- Task UploadDocumentAsync(DocumentUploadRequest uploadRequest);
+ Task UploadDocumentAsync(DocumentUploadRequest uploadRequest, bool skipExtensionCheck = false);
+
+ Task UploadDocumentSync(DocumentUploadRequest uploadRequest);
Task UpdateDocumentAsync(DocumentUpdateRequest updateRequest);
+ Task> DeleteMayanStorageDocumentAsync(long mayanDocumentId);
+
Task> DeleteDocumentAsync(PimsDocument document);
Task> GetStorageDocumentDetail(long mayanDocumentId);
@@ -49,5 +53,7 @@ public interface IDocumentService
Task>> GetDocumentFilePageListAsync(long documentId, long documentFileId);
Task DownloadFilePageImageAsync(long mayanDocumentId, long mayanFileId, long mayanFilePageId);
+
+ PimsDocument AddDocument(PimsDocument newPimsDocument);
}
}
diff --git a/source/backend/api/Services/ResearchFileService.cs b/source/backend/api/Services/ResearchFileService.cs
index f28025f757..81b66e5e67 100644
--- a/source/backend/api/Services/ResearchFileService.cs
+++ b/source/backend/api/Services/ResearchFileService.cs
@@ -5,12 +5,11 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Pims.Core.Extensions;
+using Pims.Core.Security;
using Pims.Dal.Entities;
using Pims.Dal.Entities.Models;
using Pims.Dal.Exceptions;
-using Pims.Dal.Helpers.Extensions;
using Pims.Dal.Repositories;
-using Pims.Core.Security;
namespace Pims.Api.Services
{
@@ -120,6 +119,7 @@ public PimsResearchFile UpdateProperties(PimsResearchFile researchFile, IEnumera
{
incomingResearchProperty.Internal_Id = matchingProperty.Internal_Id;
}
+
// If the property is not new, check if the name has been updated.
if (incomingResearchProperty.Internal_Id != 0)
{
@@ -173,7 +173,7 @@ public Paged GetPage(ResearchFilter filter)
{
_logger.LogInformation("Searching for research files...");
- _logger.LogDebug("Research file search with filter", filter);
+ _logger.LogDebug("Research file search with filter {filter}", filter.Serialize());
_user.ThrowIfNotAuthorized(Permissions.ResearchFileView);
return _researchFileRepository.GetPage(filter);
diff --git a/source/backend/api/Startup.cs b/source/backend/api/Startup.cs
index ba3ab2fb7f..d0e9c5b8c2 100644
--- a/source/backend/api/Startup.cs
+++ b/source/backend/api/Startup.cs
@@ -31,7 +31,6 @@
using Microsoft.OpenApi.Models;
using Pims.Api.Handlers;
using Pims.Api.Helpers;
-using Pims.Core.Api.Exceptions;
using Pims.Api.Helpers.Healthchecks;
using Pims.Api.Helpers.HealthChecks;
using Pims.Api.Helpers.Mapping;
@@ -42,21 +41,23 @@
using Pims.Api.Services;
using Pims.Api.Services.Interfaces;
using Pims.Av;
+using Pims.Core.Api.Exceptions;
using Pims.Core.Api.Helpers;
+using Pims.Core.Api.Middleware;
using Pims.Core.Converters;
using Pims.Core.Http;
using Pims.Core.Json;
using Pims.Dal;
using Pims.Dal.Keycloak;
+using Pims.Dal.Repositories;
using Pims.Geocoder;
using Pims.Ltsa;
using Prometheus;
-using Pims.Core.Api.Middleware;
namespace Pims.Api
{
///
- /// Startup class, provides a way to startup the .netcore RESTful API and configure it.
+ /// Startup class, provides a way to startup the .netcore REST API and configure it.
///
[ExcludeFromCodeCoverage]
public class Startup
@@ -482,6 +483,7 @@ private static void AddPimsApiRepositories(IServiceCollection services)
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddSingleton();
}
@@ -524,6 +526,7 @@ private static void AddPimsApiServices(IServiceCollection services)
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
}
///
diff --git a/source/backend/api/appsettings.Development.json b/source/backend/api/appsettings.Development.json
index 987052db9c..7df068180a 100644
--- a/source/backend/api/appsettings.Development.json
+++ b/source/backend/api/appsettings.Development.json
@@ -31,7 +31,7 @@
}
},
"ConnectionStrings": {
- "PIMS": "Server=localhost,5433;uid=admin;Database=pims;Password=Password12"
+ "PIMS": "Server=localhost,5433;User ID=admin;Database=pims;TrustServerCertificate=True;Encrypt=false;"
},
"Pims": {
"Environment": {
diff --git a/source/backend/apimodels/CodeTypes/DataSourceTypes.cs b/source/backend/apimodels/CodeTypes/DataSourceTypes.cs
new file mode 100644
index 0000000000..d608790cae
--- /dev/null
+++ b/source/backend/apimodels/CodeTypes/DataSourceTypes.cs
@@ -0,0 +1,66 @@
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.CodeTypes
+{
+ [JsonConverter(typeof(JsonStringEnumMemberConverter))]
+ public enum DataSourceTypes
+ {
+ [EnumMember(Value = "BIP")]
+ BIP,
+
+ [EnumMember(Value = "GAZ")]
+ GAZ,
+
+ [EnumMember(Value = "GWP")]
+ GWP,
+
+ [EnumMember(Value = "LIS")]
+ LIS,
+
+ [EnumMember(Value = "LIS_OPSS")]
+ LIS_OPSS,
+
+ [EnumMember(Value = "LIS_OPSS_PAIMS")]
+ LIS_OPSS_PAIMS,
+
+ [EnumMember(Value = "LIS_OPSS_PAIMS_PMBC")]
+ LIS_OPSS_PAIMS_PMBC,
+
+ [EnumMember(Value = "LIS_PAIMS")]
+ LIS_PAIMS,
+
+ [EnumMember(Value = "LIS_PAIMS_PMBC")]
+ LIS_PAIMS_PMBC,
+
+ [EnumMember(Value = "LIS_PMBC")]
+ LIS_PMBC,
+
+ [EnumMember(Value = "OPSS")]
+ OPSS,
+
+ [EnumMember(Value = "OPSS_PAIMS")]
+ OPSS_PAIMS,
+
+ [EnumMember(Value = "PAIMS")]
+ PAIMS,
+
+ [EnumMember(Value = "PAIMS_PMBC")]
+ PAIMS_PMBC,
+
+ [EnumMember(Value = "PAT")]
+ PAT,
+
+ [EnumMember(Value = "PIMS")]
+ PIMS,
+
+ [EnumMember(Value = "PMBC")]
+ PMBC,
+
+ [EnumMember(Value = "SHAREPOINT")]
+ SHAREPOINT,
+
+ [EnumMember(Value = "TAP")]
+ TAP,
+ }
+}
diff --git a/source/backend/apimodels/CodeTypes/DocumentQueueStatusTypes.cs b/source/backend/apimodels/CodeTypes/DocumentQueueStatusTypes.cs
index 3b38b2899d..f13b022817 100644
--- a/source/backend/apimodels/CodeTypes/DocumentQueueStatusTypes.cs
+++ b/source/backend/apimodels/CodeTypes/DocumentQueueStatusTypes.cs
@@ -6,7 +6,6 @@ namespace Pims.Api.Models.CodeTypes
[JsonConverter(typeof(JsonStringEnumMemberConverter))]
public enum DocumentQueueStatusTypes
{
-
[EnumMember(Value = "MAYAN_ERROR")]
MAYAN_ERROR,
diff --git a/source/backend/apimodels/CodeTypes/DocumentStatusTypes.cs b/source/backend/apimodels/CodeTypes/DocumentStatusTypes.cs
new file mode 100644
index 0000000000..95f6372c7e
--- /dev/null
+++ b/source/backend/apimodels/CodeTypes/DocumentStatusTypes.cs
@@ -0,0 +1,40 @@
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+
+namespace Pims.Api.Models.CodeTypes
+{
+ [JsonConverter(typeof(JsonStringEnumMemberConverter))]
+ public enum DocumentStatusTypes
+ {
+
+ [EnumMember(Value = "AMENDD")]
+ AMENDD,
+
+ [EnumMember(Value = "APPROVD")]
+ APPROVD,
+
+ [EnumMember(Value = "CNCLD")]
+ CNCLD,
+
+ [EnumMember(Value = "DRAFT")]
+ DRAFT,
+
+ [EnumMember(Value = "FINAL")]
+ FINAL,
+
+ [EnumMember(Value = "NONE")]
+ NONE,
+
+ [EnumMember(Value = "RGSTRD")]
+ RGSTRD,
+
+ [EnumMember(Value = "SENT")]
+ SENT,
+
+ [EnumMember(Value = "SIGND")]
+ SIGND,
+
+ [EnumMember(Value = "UNREGD")]
+ UNREGD,
+ }
+}
diff --git a/source/backend/apimodels/Models/Base/CodeTypeMap.cs b/source/backend/apimodels/Models/Base/CodeTypeMap.cs
index 1559535948..ed7e2b5720 100644
--- a/source/backend/apimodels/Models/Base/CodeTypeMap.cs
+++ b/source/backend/apimodels/Models/Base/CodeTypeMap.cs
@@ -13,7 +13,6 @@ public void Register(TypeAdapterConfig config)
.Map("IsDisabled", "IsDisabled")
.Map("DisplayOrder", "DisplayOrder");
-
config.ForType(typeof(Entity.ITypeEntity), typeof(CodeTypeModel))
.Map("Id", "Id")
.Map("Description", "Description")
diff --git a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileMap.cs b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileMap.cs
index 28a46032cf..af18fcfc36 100644
--- a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileMap.cs
+++ b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileMap.cs
@@ -30,6 +30,11 @@ public void Register(TypeAdapterConfig config)
.Map(dest => dest.DeliveryDate, src => src.DeliveryDate.ToNullableDateOnly())
.Map(dest => dest.EstimatedCompletionDate, src => src.EstCompletionDt.ToNullableDateOnly())
.Map(dest => dest.PossessionDate, src => src.PossessionDt.ToNullableDateOnly())
+ .Map(dest => dest.AcquisitionFileProgressStatuses, src => src.PimsAcqFileAcqProgresses)
+ .Map(dest => dest.AcquisitionFileAppraisalStatusTypeCode, src => src.AcqFileAppraisalTypeCodeNavigation)
+ .Map(dest => dest.AcquisitionFileLegalSurveyStatusTypeCode, src => src.AcqFileLglSrvyTypeCodeNavigation)
+ .Map(dest => dest.AcquisitionFileTakingStatuses, src => src.PimsAcqFileAcqFlTakeTyps)
+ .Map(dest => dest.AcquisitionFileExpropiationRiskStatusTypeCode, src => src.AcqFileExpropRiskTypeCodeNavigation)
.Map(dest => dest.TotalAllowableCompensation, src => src.TotalAllowableCompensation)
.Map(dest => dest.FileStatusTypeCode, src => src.AcquisitionFileStatusTypeCodeNavigation)
.Map(dest => dest.AcquisitionPhysFileStatusTypeCode, src => src.AcqPhysFileStatusTypeCodeNavigation)
@@ -62,6 +67,11 @@ public void Register(TypeAdapterConfig config)
.Map(dest => dest.DeliveryDate, src => src.DeliveryDate.ToNullableDateTime())
.Map(dest => dest.EstCompletionDt, src => src.EstimatedCompletionDate.ToNullableDateTime())
.Map(dest => dest.PossessionDt, src => src.PossessionDate.ToNullableDateTime())
+ .Map(dest => dest.PimsAcqFileAcqProgresses, src => src.AcquisitionFileProgressStatuses)
+ .Map(dest => dest.AcqFileAppraisalTypeCode, src => src.AcquisitionFileAppraisalStatusTypeCode.Id)
+ .Map(dest => dest.AcqFileLglSrvyTypeCode, src => src.AcquisitionFileLegalSurveyStatusTypeCode.Id)
+ .Map(dest => dest.PimsAcqFileAcqFlTakeTyps, src => src.AcquisitionFileTakingStatuses)
+ .Map(dest => dest.AcqFileExpropRiskTypeCode, src => src.AcquisitionFileExpropiationRiskStatusTypeCode.Id)
.Map(dest => dest.TotalAllowableCompensation, src => src.TotalAllowableCompensation)
.Map(dest => dest.AcquisitionFileStatusTypeCode, src => src.FileStatusTypeCode.Id)
.Map(dest => dest.AcqPhysFileStatusTypeCode, src => src.AcquisitionPhysFileStatusTypeCode.Id)
diff --git a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileModel.cs b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileModel.cs
index aadf35ac51..d321e4497b 100644
--- a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileModel.cs
+++ b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileModel.cs
@@ -53,6 +53,31 @@ public class AcquisitionFileModel : FileWithChecklistModel
///
public DateOnly? PossessionDate { get; set; }
+ ///
+ /// get/set - The list of progress statuses for this file.
+ ///
+ public IList AcquisitionFileProgressStatuses { get; set; }
+
+ ///
+ /// get/set - The acquisition file appraisal status type.
+ ///
+ public CodeTypeModel AcquisitionFileAppraisalStatusTypeCode { get; set; }
+
+ ///
+ /// get/set - The acquisition file Legal Survey status type.
+ ///
+ public CodeTypeModel AcquisitionFileLegalSurveyStatusTypeCode { get; set; }
+
+ ///
+ /// get/set - The list of Taking types statuses for this file.
+ ///
+ public IList AcquisitionFileTakingStatuses { get; set; }
+
+ ///
+ /// get/set - The acquisition file Risk level status type.
+ ///
+ public CodeTypeModel AcquisitionFileExpropiationRiskStatusTypeCode { get; set; }
+
///
/// get/set - The acquisition physical file status type.
///
diff --git a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileProgressStatusesMap.cs b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileProgressStatusesMap.cs
new file mode 100644
index 0000000000..72688242de
--- /dev/null
+++ b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileProgressStatusesMap.cs
@@ -0,0 +1,24 @@
+using Mapster;
+using Pims.Api.Models.Base;
+using Entity = Pims.Dal.Entities;
+
+namespace Pims.Api.Models.Concepts.AcquisitionFile
+{
+ public class AcquisitionFileProgressStatusesMap : IRegister
+ {
+ public void Register(TypeAdapterConfig config)
+ {
+ config.NewConfig()
+ .Map(dest => dest.Id, src => src.AcqFileAcqProgressId)
+ .Map(dest => dest.AcquisitionFileId, src => src.AcquisitionFileId)
+ .Map(dest => dest.ProgressStatusTypeCode, src => src.AcqFileProgessTypeCodeNavigation)
+ .Inherits();
+
+ config.NewConfig()
+ .Map(dest => dest.AcqFileAcqProgressId, src => src.Id)
+ .Map(dest => dest.AcquisitionFileId, src => src.AcquisitionFileId)
+ .Map(dest => dest.AcqFileProgessTypeCode, src => src.ProgressStatusTypeCode.Id)
+ .Inherits();
+ }
+ }
+}
diff --git a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileProgressStatusesModel.cs b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileProgressStatusesModel.cs
new file mode 100644
index 0000000000..3a6b4485af
--- /dev/null
+++ b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileProgressStatusesModel.cs
@@ -0,0 +1,22 @@
+using Pims.Api.Models.Base;
+
+namespace Pims.Api.Models.Concepts.AcquisitionFile
+{
+ public class AcquisitionFileProgressStatusesModel : BaseAuditModel
+ {
+ ///
+ /// get/set - AcquisitionFile Progress id.
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// get/set - Parent Acquisition file id.
+ ///
+ public long AcquisitionFileId { get; set; }
+
+ ///
+ /// get/set - Progress Status type code.
+ ///
+ public virtual CodeTypeModel ProgressStatusTypeCode { get; set; }
+ }
+}
diff --git a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileTakingStatusesMap.cs b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileTakingStatusesMap.cs
new file mode 100644
index 0000000000..6124c5f053
--- /dev/null
+++ b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileTakingStatusesMap.cs
@@ -0,0 +1,24 @@
+using Mapster;
+using Pims.Api.Models.Base;
+using Entity = Pims.Dal.Entities;
+
+namespace Pims.Api.Models.Concepts.AcquisitionFile
+{
+ public class AcquisitionFileTakingStatusesMap : IRegister
+ {
+ public void Register(TypeAdapterConfig config)
+ {
+ config.NewConfig ()
+ .Map(dest => dest.Id, src => src.AcqFileAcqFlTakeTypeId)
+ .Map(dest => dest.AcquisitionFileId, src => src.AcquisitionFileId)
+ .Map(dest => dest.TakingStatusTypeCode, src => src.AcqFileTakeTypeCodeNavigation)
+ .Inherits();
+
+ config.NewConfig()
+ .Map(dest => dest.AcqFileAcqFlTakeTypeId, src => src.Id)
+ .Map(dest => dest.AcquisitionFileId, src => src.AcquisitionFileId)
+ .Map(dest => dest.AcqFileTakeTypeCode, src => src.TakingStatusTypeCode.Id)
+ .Inherits();
+ }
+ }
+}
diff --git a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileTakingStatusesModel.cs b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileTakingStatusesModel.cs
new file mode 100644
index 0000000000..deeacca9ad
--- /dev/null
+++ b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileTakingStatusesModel.cs
@@ -0,0 +1,22 @@
+using Pims.Api.Models.Base;
+
+namespace Pims.Api.Models.Concepts.AcquisitionFile
+{
+ public class AcquisitionFileTakingStatusesModel : BaseAuditModel
+ {
+ ///
+ /// get/set - AcquisitionFile Taking type id.
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// get/set - Parent Acquisition file id.
+ ///
+ public long AcquisitionFileId { get; set; }
+
+ ///
+ /// get/set - Taking type code.
+ ///
+ public virtual CodeTypeModel TakingStatusTypeCode { get; set; }
+ }
+}
diff --git a/source/backend/apimodels/Models/Concepts/Document/DocumentMap.cs b/source/backend/apimodels/Models/Concepts/Document/DocumentMap.cs
index 98c7252660..5fe0e656fe 100644
--- a/source/backend/apimodels/Models/Concepts/Document/DocumentMap.cs
+++ b/source/backend/apimodels/Models/Concepts/Document/DocumentMap.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using Mapster;
using Pims.Api.Models.Base;
using Entity = Pims.Dal.Entities;
@@ -14,6 +15,7 @@ public void Register(TypeAdapterConfig config)
.Map(dest => dest.DocumentType, src => src.DocumentType)
.Map(dest => dest.StatusTypeCode, src => src.DocumentStatusTypeCodeNavigation)
.Map(dest => dest.FileName, src => src.FileName)
+ .Map(dest => dest.DocumentQueueStatusTypeCode, src => src.PimsDocumentQueues.Count > 0 ? src.PimsDocumentQueues.FirstOrDefault().DocumentQueueStatusTypeCodeNavigation : null)
.Inherits();
config.NewConfig()
diff --git a/source/backend/apimodels/Models/Concepts/Document/DocumentModel.cs b/source/backend/apimodels/Models/Concepts/Document/DocumentModel.cs
index d46734b021..6fea721ad2 100644
--- a/source/backend/apimodels/Models/Concepts/Document/DocumentModel.cs
+++ b/source/backend/apimodels/Models/Concepts/Document/DocumentModel.cs
@@ -18,7 +18,7 @@ public class DocumentModel : BaseAuditModel
///
/// get/set - The document id on the external storage.
///
- public int MayanDocumentId { get; set; }
+ public int? MayanDocumentId { get; set; }
///
/// get/set - Document Type.
@@ -34,6 +34,11 @@ public class DocumentModel : BaseAuditModel
/// get/set - Document/File Name.
///
public string FileName { get; set; }
+
+ ///
+ /// get/set - The document queue status type.
+ ///
+ public CodeTypeModel DocumentQueueStatusTypeCode { get; set; }
#endregion
}
}
diff --git a/source/backend/apimodels/Models/Concepts/Document/DocumentTypeModel.cs b/source/backend/apimodels/Models/Concepts/Document/DocumentTypeModel.cs
index 5f558d1d51..46aa23053d 100644
--- a/source/backend/apimodels/Models/Concepts/Document/DocumentTypeModel.cs
+++ b/source/backend/apimodels/Models/Concepts/Document/DocumentTypeModel.cs
@@ -33,7 +33,7 @@ public class DocumentTypeModel : BaseAuditModel
///
/// get/set - The document type id in mayan.
///
- public long MayanId { get; set; }
+ public long? MayanId { get; set; }
///
/// get/set - The document type is disabled and is maintained for reference only.
diff --git a/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueMap.cs b/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueMap.cs
index 1a98ba0d07..22f5018cae 100644
--- a/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueMap.cs
+++ b/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueMap.cs
@@ -1,5 +1,6 @@
using Mapster;
using Pims.Api.Models.Base;
+using Pims.Dal.Entities;
using Entity = Pims.Dal.Entities;
namespace Pims.Api.Models.Concepts.Document
@@ -12,12 +13,14 @@ public void Register(TypeAdapterConfig config)
.Map(dest => dest.Id, src => src.DocumentQueueId)
.Map(dest => dest.DocumentExternalId, src => src.DocumentExternalId)
.Map(dest => dest.DocumentId, src => src.DocumentId)
- .Map(dest => dest.DocumentQueueStatusType, src => src.DocumentQueueStatusTypeCodeNavigation)
+ .Map(dest => dest.DocumentQueueStatusType, src => src.DocumentQueueStatusTypeCodeNavigation == null ? new PimsDocumentQueueStatusType() { Id = src.DocumentQueueStatusTypeCode } : src.DocumentQueueStatusTypeCodeNavigation)
.Map(dest => dest.DataSourceTypeCode, src => src.DataSourceTypeCodeNavigation)
.Map(dest => dest.DocumentProcessStartTimestamp, src => src.DocProcessStartDt)
.Map(dest => dest.DocumentProcessEndTimestamp, src => src.DocProcessEndDt)
.Map(dest => dest.DocumentProcessRetries, src => src.DocProcessRetries)
- .Map(dest => dest.Document, src => src.Document)
+ .Map(dest => dest.PimsDocument, src => src.DocumentNavigation)
+ .Map(dest => dest.MayanError, src => src.MayanError)
+ .Map(dest => dest.DocumentQueueStatusTypeCode, src => src.DocumentQueueStatusTypeCodeNavigation)
.Inherits();
config.NewConfig()
@@ -29,7 +32,9 @@ public void Register(TypeAdapterConfig config)
.Map(dest => dest.DocProcessStartDt, src => src.DocumentProcessStartTimestamp)
.Map(dest => dest.DocProcessEndDt, src => src.DocumentProcessEndTimestamp)
.Map(dest => dest.DocProcessRetries, src => src.DocumentProcessRetries)
+ .Map(dest => dest.DocumentNavigation, src => src.PimsDocument)
.Map(dest => dest.Document, src => src.Document)
+ .Map(dest => dest.MayanError, src => src.MayanError)
.Inherits();
}
}
diff --git a/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueModel.cs b/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueModel.cs
index 73d553f74d..216db81e4a 100644
--- a/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueModel.cs
+++ b/source/backend/apimodels/Models/Concepts/DocumentQueue/DocumentQueueModel.cs
@@ -57,11 +57,19 @@ public class DocumentQueueModel : BaseAuditModel
public string MayanError { get; set; }
///
- /// get/set - The actual document, represented as a byte[].
+ /// get/set - The related pims document.
///
- public byte[] Document { get; set; }
+ public DocumentModel PimsDocument { get; set; }
+ ///
+ /// get/set - The actual document content, as a byte array.
+ ///
+ public byte[] Document { get; set; }
- #endregion
+ ///
+ /// get/set - The queue status type.
+ ///
+ public CodeTypeModel DocumentQueueStatusTypeCode { get; set; }
+ #endregion
}
}
diff --git a/source/backend/apimodels/Models/Concepts/Project/ProjectMap.cs b/source/backend/apimodels/Models/Concepts/Project/ProjectMap.cs
index 0ed5a6ae60..aa213643bf 100644
--- a/source/backend/apimodels/Models/Concepts/Project/ProjectMap.cs
+++ b/source/backend/apimodels/Models/Concepts/Project/ProjectMap.cs
@@ -19,6 +19,7 @@ public void Register(TypeAdapterConfig config)
.Map(dest => dest.Description, src => src.Description)
.Map(dest => dest.Note, src => src.Note)
.Map(dest => dest.ProjectProducts, src => src.PimsProjectProducts)
+ .Map(dest => dest.ProjectPersons, src => src.PimsProjectPeople)
.Map(dest => dest.AppLastUpdateUserid, src => src.AppLastUpdateUserid)
.Map(dest => dest.AppLastUpdateTimestamp, src => src.AppLastUpdateTimestamp)
.Inherits();
@@ -34,6 +35,7 @@ public void Register(TypeAdapterConfig config)
.Map(dest => dest.Description, src => src.Description)
.Map(dest => dest.Note, src => src.Note)
.Map(dest => dest.PimsProjectProducts, src => src.ProjectProducts)
+ .Map(dest => dest.PimsProjectPeople, src => src.ProjectPersons)
.Inherits();
}
}
diff --git a/source/backend/apimodels/Models/Concepts/Project/ProjectModel.cs b/source/backend/apimodels/Models/Concepts/Project/ProjectModel.cs
index ae34f57772..3fd63ac591 100644
--- a/source/backend/apimodels/Models/Concepts/Project/ProjectModel.cs
+++ b/source/backend/apimodels/Models/Concepts/Project/ProjectModel.cs
@@ -61,6 +61,11 @@ public class ProjectModel : BaseAuditModel
/// get/set - Project products.
///
public List ProjectProducts { get; set; }
+
+ ///
+ /// get/set - Project persons.
+ ///
+ public List ProjectPersons { get; set; }
#endregion
}
}
diff --git a/source/backend/apimodels/Models/Concepts/Project/ProjectPersonMap.cs b/source/backend/apimodels/Models/Concepts/Project/ProjectPersonMap.cs
new file mode 100644
index 0000000000..cbe29185d2
--- /dev/null
+++ b/source/backend/apimodels/Models/Concepts/Project/ProjectPersonMap.cs
@@ -0,0 +1,26 @@
+using Mapster;
+using Pims.Api.Models.Base;
+using Entity = Pims.Dal.Entities;
+
+namespace Pims.Api.Models.Concepts.Project
+{
+ public class ProjectPersonMap : IRegister
+ {
+ public void Register(TypeAdapterConfig config)
+ {
+ config.NewConfig()
+ .Map(dest => dest.Id, src => src.ProjectPersonId)
+ .Map(dest => dest.ProjectId, src => src.ProjectId)
+ .Map(dest => dest.Person, src => src.Person)
+ .Map(dest => dest.PersonId, src => src.PersonId)
+ .Map(dest => dest.Project, src => src.Project)
+ .Inherits();
+
+ config.NewConfig()
+ .Map(dest => dest.ProjectPersonId, src => src.Id)
+ .Map(dest => dest.ProjectId, src => src.ProjectId)
+ .Map(dest => dest.PersonId, src => src.PersonId)
+ .Inherits();
+ }
+ }
+}
diff --git a/source/backend/apimodels/Models/Concepts/Project/ProjectPersonModel.cs b/source/backend/apimodels/Models/Concepts/Project/ProjectPersonModel.cs
new file mode 100644
index 0000000000..759292b317
--- /dev/null
+++ b/source/backend/apimodels/Models/Concepts/Project/ProjectPersonModel.cs
@@ -0,0 +1,22 @@
+using Pims.Api.Models.Base;
+using Pims.Api.Models.Concepts.Person;
+
+namespace Pims.Api.Models.Concepts.Project
+{
+ public class ProjectPersonModel : BaseAuditModel
+ {
+ #region Properties
+
+ public long? Id { get; set; }
+
+ public long? ProjectId { get; set; }
+
+ public ProjectModel Project { get; set; }
+
+ public long PersonId { get; set; }
+
+ public PersonModel Person { get; set; }
+
+ #endregion
+ }
+}
diff --git a/source/backend/apimodels/Models/Requests/Document/Upload/DocumentUploadRequest.cs b/source/backend/apimodels/Models/Requests/Document/Upload/DocumentUploadRequest.cs
index 83102292e3..7d08d437e5 100644
--- a/source/backend/apimodels/Models/Requests/Document/Upload/DocumentUploadRequest.cs
+++ b/source/backend/apimodels/Models/Requests/Document/Upload/DocumentUploadRequest.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.IO;
using Microsoft.AspNetCore.Http;
using Pims.Api.Models.Concepts.Document;
@@ -21,6 +22,11 @@ public class DocumentUploadRequest
///
public long DocumentTypeId { get; set; }
+ ///
+ /// get/set - The id of the document to be uploaded (in PIMS).
+ ///
+ public long DocumentId { get; set; }
+
///
/// get/set - Initial status code of the document.
///
diff --git a/source/backend/core.api/Pims.Core.Api.csproj b/source/backend/core.api/Pims.Core.Api.csproj
index 70c947b9d1..51d218ea46 100644
--- a/source/backend/core.api/Pims.Core.Api.csproj
+++ b/source/backend/core.api/Pims.Core.Api.csproj
@@ -20,6 +20,7 @@
+
diff --git a/source/backend/core.api/Repositories/RestCommon/BaseRestRepository.cs b/source/backend/core.api/Repositories/RestCommon/BaseRestRepository.cs
index 214d359f9b..d0aaa708d2 100644
--- a/source/backend/core.api/Repositories/RestCommon/BaseRestRepository.cs
+++ b/source/backend/core.api/Repositories/RestCommon/BaseRestRepository.cs
@@ -10,6 +10,7 @@
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Pims.Api.Models;
using Pims.Api.Models.CodeTypes;
using Pims.Api.Models.Requests.Http;
@@ -23,6 +24,7 @@ public abstract class BaseRestRepository : IRestRespository
{
protected readonly IHttpClientFactory _httpClientFactory;
protected readonly ILogger _logger;
+ protected readonly IOptions _jsonOptions;
///
/// Initializes a new instance of the class.
@@ -31,10 +33,12 @@ public abstract class BaseRestRepository : IRestRespository
/// Injected Httpclient factory.
protected BaseRestRepository(
ILogger logger,
- IHttpClientFactory httpClientFactory)
+ IHttpClientFactory httpClientFactory,
+ IOptions jsonOptions)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
+ _jsonOptions = jsonOptions;
}
public abstract void AddAuthentication(HttpClient client, string authenticationToken = null);
@@ -305,7 +309,7 @@ private async Task> ProcessResponse(HttpResponseMessage r
};
_logger.LogTrace("Response: {response}", response);
- string payload = await response.Content.ReadAsStringAsync().ConfigureAwait(true);
+ var payload = await response.Content.ReadAsStreamAsync().ConfigureAwait(true);
result.HttpStatusCode = response.StatusCode;
switch (response.StatusCode)
@@ -321,7 +325,7 @@ private async Task> ProcessResponse(HttpResponseMessage r
result.Payload = (T)Convert.ChangeType(payload, typeof(T), CultureInfo.InvariantCulture);
break;
default:
- T requestTokenResult = JsonSerializer.Deserialize(payload);
+ T requestTokenResult = JsonSerializer.Deserialize(payload, _jsonOptions.Value);
result.Payload = requestTokenResult;
break;
}
@@ -342,7 +346,7 @@ private async Task> ProcessResponse(HttpResponseMessage r
case HttpStatusCode.BadRequest:
case HttpStatusCode.MethodNotAllowed:
result.Status = ExternalResponseStatus.Error;
- result.Message = payload;
+ result.Message = await response.Content.ReadAsStringAsync();
break;
default:
result.Status = ExternalResponseStatus.Error;
diff --git a/source/backend/dal/IRepository.cs b/source/backend/dal/IRepository.cs
index 247773ffde..4188616194 100644
--- a/source/backend/dal/IRepository.cs
+++ b/source/backend/dal/IRepository.cs
@@ -1,9 +1,15 @@
+using Microsoft.EntityFrameworkCore.Storage;
+
namespace Pims.Dal
{
public interface IRepository
{
#region Methods
+ IDbContextTransaction BeginTransaction();
+
+ void SaveChanges();
+
void CommitTransaction();
#endregion
}
diff --git a/source/backend/dal/PIMSContextFactory.cs b/source/backend/dal/PIMSContextFactory.cs
index e0b626aca6..3f42940261 100644
--- a/source/backend/dal/PIMSContextFactory.cs
+++ b/source/backend/dal/PIMSContextFactory.cs
@@ -87,6 +87,7 @@ public PimsContext CreateDbContext(string[] args)
{
options.CommandTimeout((int)TimeSpan.FromMinutes(10).TotalSeconds);
options.UseNetTopologySuite();
+ options.EnableRetryOnFailure();
});
var serializerOptions = new JsonSerializerOptions()
diff --git a/source/backend/dal/Pims.Dal.csproj b/source/backend/dal/Pims.Dal.csproj
index d1f653dc4f..453a8d2c64 100644
--- a/source/backend/dal/Pims.Dal.csproj
+++ b/source/backend/dal/Pims.Dal.csproj
@@ -36,7 +36,7 @@
-
+
diff --git a/source/backend/dal/Repositories/AcquisitionFileDocumentRepository.cs b/source/backend/dal/Repositories/AcquisitionFileDocumentRepository.cs
index 56b967aa60..f0636616d9 100644
--- a/source/backend/dal/Repositories/AcquisitionFileDocumentRepository.cs
+++ b/source/backend/dal/Repositories/AcquisitionFileDocumentRepository.cs
@@ -41,6 +41,9 @@ public IList GetAllByAcquisitionFile(long fileId)
.ThenInclude(d => d.DocumentStatusTypeCodeNavigation)
.Include(ad => ad.Document)
.ThenInclude(d => d.DocumentType)
+ .Include(ad => ad.Document)
+ .ThenInclude(q => q.PimsDocumentQueues)
+ .ThenInclude(s => s.DocumentQueueStatusTypeCodeNavigation)
.Where(ad => ad.AcquisitionFileId == fileId)
.AsNoTracking()
.ToList();
diff --git a/source/backend/dal/Repositories/AcquisitionFileRepository.cs b/source/backend/dal/Repositories/AcquisitionFileRepository.cs
index fbac91b873..96f5d3d881 100644
--- a/source/backend/dal/Repositories/AcquisitionFileRepository.cs
+++ b/source/backend/dal/Repositories/AcquisitionFileRepository.cs
@@ -99,12 +99,21 @@ public PimsAcquisitionFile GetById(long id)
.Include(r => r.RegionCodeNavigation)
.Include(r => r.AcquisitionFundingTypeCodeNavigation)
.Include(r => r.SubfileInterestTypeCodeNavigation)
+ .Include(s => s.PimsAcqFileAcqProgresses)
+ .ThenInclude(p => p.AcqFileProgessTypeCodeNavigation)
+ .Include(s => s.AcqFileAppraisalTypeCodeNavigation)
+ .Include(s => s.AcqFileLglSrvyTypeCodeNavigation)
+ .Include(s => s.PimsAcqFileAcqFlTakeTyps)
+ .ThenInclude(t => t.AcqFileTakeTypeCodeNavigation)
+ .Include(s => s.AcqFileExpropRiskTypeCodeNavigation)
.Include(r => r.Project)
.ThenInclude(x => x.WorkActivityCode)
.Include(r => r.Project)
.ThenInclude(x => x.CostTypeCode)
.Include(r => r.Project)
.ThenInclude(x => x.BusinessFunctionCode)
+ .Include(r => r.Project)
+ .ThenInclude(x => x.PimsProjectPeople)
.Include(r => r.Product)
.Include(r => r.PimsPropertyAcquisitionFiles)
.Include(r => r.PimsAcquisitionFileTeams)
@@ -730,6 +739,8 @@ public PimsAcquisitionFile Update(PimsAcquisitionFile acquisitionFile)
Context.UpdateChild(p => p.PimsAcquisitionFileTeams, acquisitionFile.Internal_Id, acquisitionFile.PimsAcquisitionFileTeams.ToArray());
Context.UpdateChild(p => p.PimsInterestHolders, acquisitionFile.Internal_Id, acquisitionFile.PimsInterestHolders.ToArray());
Context.UpdateGrandchild(o => o.PimsAcquisitionOwners, oa => oa.Address, acquisitionFile.Internal_Id, acquisitionFile.PimsAcquisitionOwners.ToArray());
+ Context.UpdateChild(p => p.PimsAcqFileAcqProgresses, acquisitionFile.AcquisitionFileId, acquisitionFile.PimsAcqFileAcqProgresses.ToArray());
+ Context.UpdateChild(p => p.PimsAcqFileAcqFlTakeTyps, acquisitionFile.AcquisitionFileId, acquisitionFile.PimsAcqFileAcqFlTakeTyps.ToArray());
return acquisitionFile;
}
@@ -887,7 +898,7 @@ private IQueryable GetCommonAcquisitionFileQueryDeep(Acquis
if (contractorPersonId is not null)
{
- predicate = predicate.And(acq => acq.PimsAcquisitionFileTeams.Any(x => x.PersonId == contractorPersonId));
+ predicate = predicate.And(acq => acq.PimsAcquisitionFileTeams.Any(x => x.PersonId == contractorPersonId) || (acq.Project != null && acq.Project.PimsProjectPeople.Any(x => x.PersonId == contractorPersonId)));
}
if (!string.IsNullOrWhiteSpace(filter.AcquisitionTeamMemberPersonId))
@@ -903,6 +914,7 @@ private IQueryable GetCommonAcquisitionFileQueryDeep(Acquis
var query = Context.PimsAcquisitionFiles.AsNoTracking()
.Include(r => r.RegionCodeNavigation)
.Include(p => p.Project)
+ .ThenInclude(p => p.PimsProjectPeople)
.Include(s => s.AcquisitionFileStatusTypeCodeNavigation)
.Include(f => f.AcquisitionFundingTypeCodeNavigation)
.Include(ph => ph.AcqPhysFileStatusTypeCodeNavigation)
@@ -926,7 +938,18 @@ private IQueryable GetCommonAcquisitionFileQueryDeep(Acquis
.ThenInclude(fp => fp.AlternateProject)
.Where(predicate);
- query = (filter.Sort?.Any() == true) ? query.OrderByProperty(true, filter.Sort) : query.OrderBy(acq => acq.AcquisitionFileId);
+ if (filter.Sort?.Length > 0 && filter.Sort[0].Contains("FileNumber"))
+ {
+ query = filter.Sort[0].Contains("asc") ? query.OrderBy(acq => acq.RegionCode)
+ .ThenBy(acq => acq.FileNo)
+ .ThenBy(acq => acq.FileNoSuffix) : query.OrderByDescending(acq => acq.RegionCode)
+ .ThenByDescending(acq => acq.FileNo)
+ .ThenByDescending(acq => acq.FileNoSuffix);
+ }
+ else
+ {
+ query = (filter.Sort?.Length > 0) ? query.OrderByProperty(true, filter.Sort) : query.OrderBy(acq => acq.AcquisitionFileId);
+ }
return query;
}
diff --git a/source/backend/dal/Repositories/BaseRepository.cs b/source/backend/dal/Repositories/BaseRepository.cs
index 1261a87011..f8486b76a6 100644
--- a/source/backend/dal/Repositories/BaseRepository.cs
+++ b/source/backend/dal/Repositories/BaseRepository.cs
@@ -1,4 +1,5 @@
using System.Security.Claims;
+using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.Logging;
namespace Pims.Dal.Repositories
@@ -44,6 +45,23 @@ protected BaseRepository(PimsContext dbContext, ClaimsPrincipal user, ILogger
+ /// Begin a DB transaction.
+ ///
+ ///
+ public IDbContextTransaction BeginTransaction()
+ {
+ return this.Context.Database.BeginTransaction();
+ }
+
+ ///
+ /// Save changes for a DB action.
+ ///
+ public void SaveChanges()
+ {
+ Context.SaveChanges();
+ }
+
///
/// Commit all saved changes as a single transaction.
///
diff --git a/source/backend/dal/Repositories/BaseRepository{T_Entity}.cs b/source/backend/dal/Repositories/BaseRepository{T_Entity}.cs
index 2781d0d459..6173347ee4 100644
--- a/source/backend/dal/Repositories/BaseRepository{T_Entity}.cs
+++ b/source/backend/dal/Repositories/BaseRepository{T_Entity}.cs
@@ -41,11 +41,6 @@ public T_Entity Find(params object[] keyValues)
{
return this.Context.Find(keyValues);
}
-
- public void SaveChanges()
- {
- this.Context.SaveChanges();
- }
#endregion
}
}
diff --git a/source/backend/dal/Repositories/CompReqFinancialRepository.cs b/source/backend/dal/Repositories/CompReqFinancialRepository.cs
index a80bd59684..9099c5820d 100644
--- a/source/backend/dal/Repositories/CompReqFinancialRepository.cs
+++ b/source/backend/dal/Repositories/CompReqFinancialRepository.cs
@@ -5,10 +5,10 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Pims.Core.Exceptions;
+using Pims.Core.Extensions;
using Pims.Core.Security;
using Pims.Dal.Entities;
using Pims.Dal.Entities.Models;
-using Pims.Core.Extensions;
using Pims.Dal.Helpers.Extensions;
namespace Pims.Dal.Repositories
diff --git a/source/backend/dal/Repositories/CompensationRequisitionRepository.cs b/source/backend/dal/Repositories/CompensationRequisitionRepository.cs
index eadd62f192..0ed6a06ffa 100644
--- a/source/backend/dal/Repositories/CompensationRequisitionRepository.cs
+++ b/source/backend/dal/Repositories/CompensationRequisitionRepository.cs
@@ -142,7 +142,7 @@ public List GetAcquisitionCompReqPropertiesById(lon
{
return Context.PimsPropAcqFlCompReqs
.Where(x => x.CompensationRequisitionId == compensationRequisitionId)
- .Include(pa => pa.PropertyAcquisitionFile)
+ .Include(pa => pa.PropertyAcquisitionFile)
.ThenInclude(p => p.Property)
.ThenInclude(rp => rp.RegionCodeNavigation)
.Include(pa => pa.PropertyAcquisitionFile)
@@ -165,7 +165,7 @@ public List GetLeaseCompReqPropertiesById(long compensationRe
{
return Context.PimsPropLeaseCompReqs
.Where(x => x.CompensationRequisitionId == compensationRequisitionId)
- .Include(l => l.PropertyLease)
+ .Include(l => l.PropertyLease)
.ThenInclude(p => p.Property)
.ThenInclude(rp => rp.RegionCodeNavigation)
.Include(pa => pa.PropertyLease)
diff --git a/source/backend/dal/Repositories/DispositionFileDocumentRepository.cs b/source/backend/dal/Repositories/DispositionFileDocumentRepository.cs
index 7855458d55..82d5a77032 100644
--- a/source/backend/dal/Repositories/DispositionFileDocumentRepository.cs
+++ b/source/backend/dal/Repositories/DispositionFileDocumentRepository.cs
@@ -42,6 +42,9 @@ public IList GetAllByDispositionFile(long fileId)
.ThenInclude(d => d.DocumentStatusTypeCodeNavigation)
.Include(fd => fd.Document)
.ThenInclude(d => d.DocumentType)
+ .Include(x => x.Document)
+ .ThenInclude(q => q.PimsDocumentQueues)
+ .ThenInclude(s => s.DocumentQueueStatusTypeCodeNavigation)
.Where(fd => fd.DispositionFileId == fileId)
.AsNoTracking()
.ToList();
diff --git a/source/backend/dal/Repositories/DocumentQueueRepository.cs b/source/backend/dal/Repositories/DocumentQueueRepository.cs
index fa2472427a..7ef007241a 100644
--- a/source/backend/dal/Repositories/DocumentQueueRepository.cs
+++ b/source/backend/dal/Repositories/DocumentQueueRepository.cs
@@ -1,7 +1,10 @@
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
+using Pims.Api.Models.CodeTypes;
using Pims.Core.Extensions;
using Pims.Dal.Entities;
using Pims.Dal.Entities.Models;
@@ -32,16 +35,72 @@ public DocumentQueueRepository(
#region Methods
+ ///
+ /// Attempts to find a queued document via the documentQueueId. Returns null if not found.
+ ///
+ ///
+ ///
+ public PimsDocumentQueue TryGetById(long documentQueueId)
+ {
+
+ return Context.PimsDocumentQueues
+ .AsNoTracking()
+ .FirstOrDefault(dq => dq.DocumentQueueId == documentQueueId);
+ }
+
+ ///
+ /// Add Document to Queue.
+ ///
+ ///
+ ///
+ public PimsDocumentQueue Add(PimsDocumentQueue queuedDocument)
+ {
+ queuedDocument.ThrowIfNull(nameof(queuedDocument));
+
+ // Default values for new queue items.
+ queuedDocument.DocumentQueueStatusTypeCode = DocumentQueueStatusTypes.PENDING.ToString();
+ queuedDocument.DataSourceTypeCode = DataSourceTypes.PIMS.ToString();
+ queuedDocument.MayanError = null;
+
+ // Add
+ Context.PimsDocumentQueues.Add(queuedDocument);
+
+ return queuedDocument;
+ }
+
+ ///
+ /// Find Queue Item for a Document.
+ ///
+ ///
+ ///
+ public PimsDocumentQueue GetByDocumentId(long documentId)
+ {
+ return Context.PimsDocumentQueues.Where(x => x.DocumentId == documentId).FirstOrDefault();
+ }
+
///
/// Updates the queued document in the database.
///
///
///
- public PimsDocumentQueue Update(PimsDocumentQueue queuedDocument)
+ public PimsDocumentQueue Update(PimsDocumentQueue queuedDocument, bool removeDocument = false)
{
queuedDocument.ThrowIfNull(nameof(queuedDocument));
+ var existingQueuedDocument = TryGetById(queuedDocument.DocumentQueueId) ?? throw new KeyNotFoundException($"DocumentQueueId {queuedDocument.DocumentQueueId} not found.");
+ if (existingQueuedDocument?.DocumentQueueStatusTypeCode == DocumentQueueStatusTypes.SUCCESS.ToString() && queuedDocument.DocumentQueueStatusTypeCode != DocumentQueueStatusTypes.SUCCESS.ToString())
+ {
+ throw new InvalidOperationException($"DocumentQueueId {queuedDocument.DocumentQueueId} is already completed.");
+ }
+ if (!removeDocument)
+ {
+ queuedDocument.Document = existingQueuedDocument.Document;
+ }
+ queuedDocument.MayanError = queuedDocument.MayanError?.Truncate(4000);
+ queuedDocument.DataSourceTypeCode = existingQueuedDocument.DataSourceTypeCode; // Do not allow the data source to be updated.
+ Context.Entry(existingQueuedDocument).CurrentValues.SetValues(queuedDocument);
queuedDocument = Context.Update(queuedDocument).Entity;
+
return queuedDocument;
}
@@ -53,8 +112,8 @@ public PimsDocumentQueue Update(PimsDocumentQueue queuedDocument)
public bool Delete(PimsDocumentQueue queuedDocument)
{
queuedDocument.ThrowIfNull(nameof(queuedDocument));
-
Context.Remove(queuedDocument);
+
return true;
}
@@ -63,27 +122,65 @@ public bool Delete(PimsDocumentQueue queuedDocument)
///
///
///
- public IEnumerable GetAllByFilter(DocumentQueueFilter filter)
+ public IEnumerable GetAllByFilter(DocumentQueueFilter filter)
{
- var query = Context.PimsDocumentQueues.Where(q => true);
+ var query = Context.PimsDocumentQueues
+ .Include(dq => dq.DocumentNavigation)
+ .ThenInclude(d => d.DocumentType)
+ .Include(dq => dq.DocumentQueueStatusTypeCodeNavigation)
+ .Include(dq => dq.DataSourceTypeCodeNavigation)
+ .Where(q => true).AsNoTracking();
if (filter.DataSourceTypeCode != null)
{
- query.Where(d => d.DataSourceTypeCode == filter.DataSourceTypeCode);
+ query = query.Where(d => d.DataSourceTypeCode == filter.DataSourceTypeCode);
}
- if (filter.DocumentQueueStatusTypeCode != null)
+ if (filter.DocumentQueueStatusTypeCodes != null && filter.DocumentQueueStatusTypeCodes.Length > 0)
{
- query.Where(d => d.DocumentQueueStatusTypeCode == filter.DocumentQueueStatusTypeCode);
+ query = query.Where(d => filter.DocumentQueueStatusTypeCodes.Any(filterStatus => d.DocumentQueueStatusTypeCode == filterStatus));
}
if (filter.DocProcessStartDate != null)
{
- query.Where(d => d.DocProcessStartDt >= filter.DocProcessStartDate);
+ query = query.Where(d => d.DocProcessStartDt >= filter.DocProcessStartDate);
}
if (filter.DocProcessEndDate != null)
{
- query.Where(d => d.DocProcessEndDt <= filter.DocProcessEndDate);
+ query = query.Where(d => d.DocProcessEndDt <= filter.DocProcessEndDate);
}
- return query.ToList();
+ if (filter.MaxDocProcessRetries != null)
+ {
+ query = query.Where(d => d.DocProcessRetries == null || d.DocProcessRetries < filter.MaxDocProcessRetries);
+ }
+
+ // Return the PimsDocumentQueue search results without the file contents - to avoid memory issues.
+ return query.Take(filter.Quantity).Select(dq => new DocumentQueueSearchResult()
+ {
+ DocumentQueueId = dq.DocumentQueueId,
+ DocumentId = dq.DocumentId,
+ DocumentQueueStatusTypeCode = dq.DocumentQueueStatusTypeCode,
+ DocumentQueueStatusTypeCodeNavigation = dq.DocumentQueueStatusTypeCodeNavigation,
+ DataSourceTypeCode = dq.DataSourceTypeCode,
+ DataSourceTypeCodeNavigation = dq.DataSourceTypeCodeNavigation,
+ DocumentExternalId = dq.DocumentExternalId,
+ DocProcessStartDt = dq.DocProcessStartDt,
+ DocProcessEndDt = dq.DocProcessEndDt,
+ DocProcessRetries = dq.DocProcessRetries,
+ MayanError = dq.MayanError,
+ AppCreateTimestamp = dq.AppCreateTimestamp,
+ AppCreateUserDirectory = dq.AppCreateUserDirectory,
+ AppCreateUserGuid = dq.AppCreateUserGuid,
+ AppCreateUserid = dq.AppCreateUserid,
+ AppLastUpdateTimestamp = dq.AppLastUpdateTimestamp,
+ AppLastUpdateUserDirectory = dq.AppLastUpdateUserDirectory,
+ AppLastUpdateUserGuid = dq.AppLastUpdateUserGuid,
+ AppLastUpdateUserid = dq.AppLastUpdateUserid,
+ DbCreateTimestamp = dq.DbCreateTimestamp,
+ DbCreateUserid = dq.DbCreateUserid,
+ DbLastUpdateTimestamp = dq.DbLastUpdateTimestamp,
+ DbLastUpdateUserid = dq.DbLastUpdateUserid,
+ ConcurrencyControlNumber = dq.ConcurrencyControlNumber,
+ DocumentSize = dq.Document != null ? dq.Document.Length : 0,
+ }).ToList();
}
public int DocumentQueueCount(PimsDocumentQueueStatusType pimsDocumentQueueStatusType)
@@ -92,6 +189,7 @@ public int DocumentQueueCount(PimsDocumentQueueStatusType pimsDocumentQueueStatu
{
Context.PimsDocumentQueues.Count();
}
+
return Context.PimsDocumentQueues.Count(d => d.DocumentQueueStatusTypeCode == pimsDocumentQueueStatusType.DocumentQueueStatusTypeCode);
}
diff --git a/source/backend/dal/Repositories/DocumentRepository.cs b/source/backend/dal/Repositories/DocumentRepository.cs
index c9a9716baa..2cdf09989b 100644
--- a/source/backend/dal/Repositories/DocumentRepository.cs
+++ b/source/backend/dal/Repositories/DocumentRepository.cs
@@ -4,9 +4,8 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Pims.Core.Extensions;
-using Pims.Dal.Entities;
-using Pims.Dal.Helpers.Extensions;
using Pims.Core.Security;
+using Pims.Dal.Entities;
namespace Pims.Dal.Repositories
{
@@ -44,6 +43,23 @@ public PimsDocument TryGet(long documentId)
return this.Context.PimsDocuments.AsNoTracking().FirstOrDefault(x => x.DocumentId == documentId);
}
+ public PimsDocument TryGetDocumentRelationships(long documentId)
+ {
+ var documentRelationships = Context.PimsDocuments.AsNoTracking()
+ .Include(d => d.PimsResearchFileDocuments)
+ .Include(d => d.PimsAcquisitionFileDocuments)
+ .Include(d => d.PimsProjectDocuments)
+ .Include(d => d.PimsFormTypes)
+ .Include(d => d.PimsLeaseDocuments)
+ .Include(d => d.PimsPropertyActivityDocuments)
+ .Include(d => d.PimsDispositionFileDocuments)
+ .Where(d => d.DocumentId == documentId)
+ .AsNoTracking()
+ .FirstOrDefault();
+
+ return documentRelationships;
+ }
+
///
/// Adds the passed document to the database.
///
@@ -72,9 +88,10 @@ public PimsDocument Add(PimsDocument document)
public PimsDocument Update(PimsDocument document, bool commitTransaction = true)
{
document.ThrowIfNull(nameof(document));
+ User.ThrowIfNotAuthorized(Permissions.DocumentEdit);
- this.User.ThrowIfNotAuthorized(Permissions.DocumentEdit);
document = Context.Update(document).Entity;
+
return document;
}
@@ -83,7 +100,7 @@ public PimsDocument Update(PimsDocument document, bool commitTransaction = true)
///
///
///
- public bool Delete(PimsDocument document)
+ public bool Delete(PimsDocument document, bool commitTransaction = true)
{
document.ThrowIfNull(nameof(document));
@@ -137,9 +154,27 @@ public bool Delete(PimsDocument document)
Context.Entry(pimsFormTypeDocument).Property(x => x.DocumentId).IsModified = true;
}
- Context.CommitTransaction(); // TODO: required to enforce delete order. Can be removed when cascade deletes are implemented.
+ if (commitTransaction)
+ {
+ Context.CommitTransaction(); // TODO: required to enforce delete order. Can be removed when cascade deletes are implemented.
+ }
Context.PimsDocuments.Remove(new PimsDocument() { Internal_Id = document.Internal_Id });
+
+ return true;
+ }
+
+ ///
+ /// Deletes the passed document from the database.
+ ///
+ ///
+ ///
+ public bool DeleteDocument(PimsDocument document)
+ {
+ document.ThrowIfNull(nameof(document));
+
+ Context.PimsDocuments.Remove(document);
+
return true;
}
diff --git a/source/backend/dal/Repositories/Interfaces/IDocumentQueueRepository.cs b/source/backend/dal/Repositories/Interfaces/IDocumentQueueRepository.cs
index 45c14d19ac..5e57420673 100644
--- a/source/backend/dal/Repositories/Interfaces/IDocumentQueueRepository.cs
+++ b/source/backend/dal/Repositories/Interfaces/IDocumentQueueRepository.cs
@@ -9,9 +9,16 @@ namespace Pims.Dal.Repositories
///
public interface IDocumentQueueRepository : IRepository
{
- IEnumerable GetAllByFilter(DocumentQueueFilter filter);
- PimsDocumentQueue Update(PimsDocumentQueue queuedDocument);
+ PimsDocumentQueue TryGetById(long documentQueueId);
+
+ PimsDocumentQueue Add(PimsDocumentQueue queuedDocument);
+
+ PimsDocumentQueue GetByDocumentId(long documentId);
+
+ IEnumerable GetAllByFilter(DocumentQueueFilter filter);
+
+ PimsDocumentQueue Update(PimsDocumentQueue queuedDocument, bool removeDocument = false);
bool Delete(PimsDocumentQueue queuedDocument);
diff --git a/source/backend/dal/Repositories/Interfaces/IDocumentRepository.cs b/source/backend/dal/Repositories/Interfaces/IDocumentRepository.cs
index 3be4e2b302..dd53b48872 100644
--- a/source/backend/dal/Repositories/Interfaces/IDocumentRepository.cs
+++ b/source/backend/dal/Repositories/Interfaces/IDocumentRepository.cs
@@ -13,8 +13,12 @@ public interface IDocumentRepository : IRepository
PimsDocument Update(PimsDocument document, bool commitTransaction = true);
- bool Delete(PimsDocument document);
+ bool Delete(PimsDocument document, bool commitTransaction = true);
+
+ bool DeleteDocument(PimsDocument document);
int DocumentRelationshipCount(long documentId);
+
+ PimsDocument TryGetDocumentRelationships(long documentId);
}
}
diff --git a/source/backend/dal/Repositories/Interfaces/ILookupRepository.cs b/source/backend/dal/Repositories/Interfaces/ILookupRepository.cs
index ba895f0898..f8b1b245c9 100644
--- a/source/backend/dal/Repositories/Interfaces/ILookupRepository.cs
+++ b/source/backend/dal/Repositories/Interfaces/ILookupRepository.cs
@@ -159,5 +159,15 @@ public interface ILookupRepository : IRepository
IEnumerable GetAllConsultationOutcomeTypes();
IEnumerable GetAllSubfileInterestTypes();
+
+ IEnumerable