diff --git a/src/Api/Controllers/DocumentsController.cs b/src/Api/Controllers/DocumentsController.cs index 9b7931ca..2a6b6036 100644 --- a/src/Api/Controllers/DocumentsController.cs +++ b/src/Api/Controllers/DocumentsController.cs @@ -1,10 +1,13 @@ using Api.Controllers.Payload.Requests.Documents; using Application.Common.Interfaces; using Application.Common.Models; +using Application.Common.Models.Dtos; +using Application.Common.Models.Dtos.ImportDocument; using Application.Common.Models.Dtos.Physical; using Application.Documents.Commands; using Application.Documents.Queries; using Application.Identity; +using Domain.Enums; using Infrastructure.Identity.Authorization; using Microsoft.AspNetCore.Mvc; @@ -43,12 +46,45 @@ public async Task>> GetById([FromRoute] Guid do /// /// Get all documents query parameters /// A paginated list of DocumentDto + [RequiresRole(IdentityData.Roles.Staff)] + [HttpGet("issued")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>>> GetAllIssuedPaginated( + [FromQuery] GetAllIssuedPaginatedQueryParameters queryParameters) + { + var departmentId = _currentUserService.GetCurrentDepartmentForStaff(); + if (departmentId is null) + { + return Result>.Fail(new Exception("Staff does not have a room")); + } + var query = new GetAllIssuedDocumentsPaginated.Query() + { + DepartmentId = departmentId.Value, + SearchTerm = queryParameters.SearchTerm, + Page = queryParameters.Page, + Size = queryParameters.Size, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } + + /// + /// Get all documents paginated + /// + /// Get all documents query parameters + /// A paginated list of DocumentDto + [RequiresRole(IdentityData.Roles.Admin)] [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>>> GetAllPaginated( + public async Task>>> GetAllForAdminPaginated( [FromQuery] GetAllDocumentsPaginatedQueryParameters queryParameters) { var query = new GetAllDocumentsPaginated.Query() @@ -66,6 +102,36 @@ public async Task>>> GetAllPagina return Ok(Result>.Succeed(result)); } + /// + /// Get all documents for staff paginated + /// + /// Get all documents for staff query parameters + /// A paginated list of DocumentDto + [RequiresRole(IdentityData.Roles.Staff)] + [HttpGet("staff")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>>> GetAllForStaffPaginated( + [FromQuery] GetAllDocumentsForStaffPaginatedQueryParameters queryParameters) + { + var roomId = _currentUserService.GetCurrentRoomForStaff(); + var query = new GetAllDocumentsPaginated.Query() + { + RoomId = roomId, + LockerId = queryParameters.LockerId, + FolderId = queryParameters.FolderId, + SearchTerm = queryParameters.SearchTerm, + Page = queryParameters.Page, + Size = queryParameters.Size, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder, + }; + var result = await Mediator.Send(query); + return Ok(Result>.Succeed(result)); + } + /// /// Get all document types /// @@ -120,7 +186,7 @@ public async Task>> Import([FromBody] ImportDoc [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task>> RequestImport([FromBody] RequestImportDocumentRequest request) + public async Task>> RequestImport([FromBody] RequestImportDocumentRequest request) { var performingUserId = _currentUserService.GetId(); var command = new RequestImportDocument.Command() @@ -128,9 +194,35 @@ public async Task>> RequestImport([FromBody] Re Title = request.Title, Description = request.Description, DocumentType = request.DocumentType, + IsPrivate = request.IsPrivate, IssuerId = performingUserId, }; var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Checkin a document + /// + /// + /// A DocumentDto of the imported document + [RequiresRole(IdentityData.Roles.Staff)] + [HttpPost("checkin{documentId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> Checkin( + [FromRoute] Guid documentId) + { + var performingUserId = _currentUserService.GetId(); + var command = new CheckinDocument.Command() + { + PerformingUserId = performingUserId, + DocumentId = documentId, + }; + var result = await Mediator.Send(command); return Ok(Result.Succeed(result)); } @@ -177,4 +269,101 @@ public async Task>> Delete([FromRoute] Guid doc var result = await Mediator.Send(query); return Ok(Result.Succeed(result)); } + + /// + /// Approve a document request + /// + /// Id of the document to be approved + /// + /// A DocumentDto of the approved document + [HttpPost("approve/{documentId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> Approve( + [FromRoute] Guid documentId, + [FromBody] ApproveImportRequest request) + { + var performingUserId = _currentUserService.GetId(); + var query = new ApproveDocument.Command() + { + PerformingUserId = performingUserId, + DocumentId = documentId, + Reason = request.Reason, + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + + /// + /// Reject a document request + /// + /// Id of the document to be rejected + /// + /// A DocumentDto of the rejected document + [HttpPost("reject/{documentId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> Reject( + [FromRoute] Guid documentId, + [FromBody] RejectImportRequest request) + { + var performingUserId = _currentUserService.GetId(); + var query = new RejectDocument.Command() + { + PerformingUserId = performingUserId, + DocumentId = documentId, + Reason = request.Reason, + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + + /// + /// Get a document request reason + /// + /// Id of the document to be rejected + /// A DocumentDto of the rejected document + [RequiresRole(IdentityData.Roles.Staff)] + [HttpPost("reason/{documentId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> Reason( + [FromRoute] Guid documentId) + { + var query = new GetDocumentReason.Query() + { + DocumentId = documentId, + Type = RequestType.Import, + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } + + /// + /// Assign a document to + /// + /// Id of the document to be rejected + /// + /// A DocumentDto of the rejected document + [HttpPost("{documentId:guid}/assign")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> Assign( + [FromRoute] Guid documentId, + [FromBody] AssignDocumentToFolderRequest request) + { + var performingUserId = _currentUserService.GetId(); + var query = new AssignDocument.Command() + { + PerformingUserId = performingUserId, + DocumentId = documentId, + FolderId = request.FolderId, + }; + var result = await Mediator.Send(query); + return Ok(Result.Succeed(result)); + } } \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/ApproveImportRequest.cs b/src/Api/Controllers/Payload/Requests/Documents/ApproveImportRequest.cs new file mode 100644 index 00000000..d617bb8b --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/ApproveImportRequest.cs @@ -0,0 +1,6 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +public class ApproveImportRequest +{ + public string Reason { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/AssignDocumentToFolderRequest.cs b/src/Api/Controllers/Payload/Requests/Documents/AssignDocumentToFolderRequest.cs new file mode 100644 index 00000000..bcca52f5 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/AssignDocumentToFolderRequest.cs @@ -0,0 +1,6 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +public class AssignDocumentToFolderRequest +{ + public Guid FolderId { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsForStaffPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsForStaffPaginatedQueryParameters.cs new file mode 100644 index 00000000..7860021d --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsForStaffPaginatedQueryParameters.cs @@ -0,0 +1,21 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +public class GetAllDocumentsForStaffPaginatedQueryParameters : PaginatedQueryParameters +{ + /// + /// Id of the room to find documents in + /// + public Guid? RoomId { get; set; } + /// + /// Id of the locker to find documents in + /// + public Guid? LockerId { get; set; } + /// + /// Id of the folder to find documents in + /// + public Guid? FolderId { get; set; } + /// + /// Search term + /// + public string? SearchTerm { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/GetAllIssuedPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Documents/GetAllIssuedPaginatedQueryParameters.cs new file mode 100644 index 00000000..ed060421 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/GetAllIssuedPaginatedQueryParameters.cs @@ -0,0 +1,6 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +public class GetAllIssuedPaginatedQueryParameters : PaginatedQueryParameters +{ + public string? SearchTerm { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/RejectImportRequest.cs b/src/Api/Controllers/Payload/Requests/Documents/RejectImportRequest.cs new file mode 100644 index 00000000..1e9c4b80 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Documents/RejectImportRequest.cs @@ -0,0 +1,6 @@ +namespace Api.Controllers.Payload.Requests.Documents; + +public class RejectImportRequest +{ + public string Reason { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/RequestImportDocumentRequest.cs b/src/Api/Controllers/Payload/Requests/Documents/RequestImportDocumentRequest.cs index 4c8fb941..ecdc79a5 100644 --- a/src/Api/Controllers/Payload/Requests/Documents/RequestImportDocumentRequest.cs +++ b/src/Api/Controllers/Payload/Requests/Documents/RequestImportDocumentRequest.cs @@ -17,4 +17,5 @@ public class RequestImportDocumentRequest /// Document type of the document to be imported /// public string DocumentType { get; set; } = null!; + public bool IsPrivate { get; set; } } \ No newline at end of file diff --git a/src/Application/Common/Messages/DocumentLogMessages.cs b/src/Application/Common/Messages/DocumentLogMessages.cs index b6a62cae..5fb37c1f 100644 --- a/src/Application/Common/Messages/DocumentLogMessages.cs +++ b/src/Application/Common/Messages/DocumentLogMessages.cs @@ -6,5 +6,9 @@ public static class Import { public const string NewImport = "Imported new document"; public const string NewImportRequest = "Created new import request"; + public const string Checkin = "Checked in document"; + public const string Approve = "Approved import request"; + public const string Reject = "Rejected import request"; + public const string Assign = "Assigned to a folder"; } } \ No newline at end of file diff --git a/src/Application/Common/Messages/FolderLogMessage.cs b/src/Application/Common/Messages/FolderLogMessage.cs index b6922a83..97f97818 100644 --- a/src/Application/Common/Messages/FolderLogMessage.cs +++ b/src/Application/Common/Messages/FolderLogMessage.cs @@ -4,4 +4,5 @@ public static class FolderLogMessage { public const string Add = "Added folder"; public const string Update = "Updated folder"; + public const string AssignDocument = "Assigned document to folder"; } \ No newline at end of file diff --git a/src/Application/Common/Messages/RequestLogMessages.cs b/src/Application/Common/Messages/RequestLogMessages.cs new file mode 100644 index 00000000..ae9bab77 --- /dev/null +++ b/src/Application/Common/Messages/RequestLogMessages.cs @@ -0,0 +1,7 @@ +namespace Application.Common.Messages; + +public static class RequestLogMessages +{ + public const string ApproveImport = "Approved import request"; + public const string RejectImport = "Rejected import request"; +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/ImportDocument/IssuedDocumentDto.cs b/src/Application/Common/Models/Dtos/ImportDocument/IssuedDocumentDto.cs new file mode 100644 index 00000000..1c573d00 --- /dev/null +++ b/src/Application/Common/Models/Dtos/ImportDocument/IssuedDocumentDto.cs @@ -0,0 +1,25 @@ +using Application.Common.Mappings; +using AutoMapper; +using Domain.Entities.Physical; + +namespace Application.Common.Models.Dtos.ImportDocument; + +public class IssuedDocumentDto : IMapFrom +{ + public Guid Id { get; set; } + public string Title { get; set; } = null!; + public string? Description { get; set; } + public string DocumentType { get; set; } = null!; + public IssuerDto? Issuer { get; set; } + public string Status { get; set; } = null!; + public bool IsPrivate { get; set; } + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(dest => dest.Status, + opt => opt.MapFrom(src => src.Status.ToString())) + .ForMember(dest => dest.Issuer, + opt => opt.MapFrom(x => x.Importer)); + } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/ImportDocument/IssuerDto.cs b/src/Application/Common/Models/Dtos/ImportDocument/IssuerDto.cs new file mode 100644 index 00000000..3818dc77 --- /dev/null +++ b/src/Application/Common/Models/Dtos/ImportDocument/IssuerDto.cs @@ -0,0 +1,17 @@ +using Application.Common.Mappings; +using Domain.Entities; + +namespace Application.Common.Models.Dtos.ImportDocument; + +public class IssuerDto : IMapFrom +{ + public Guid Id { get; set; } + public string Username { get; set; } + public string Email { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Role { get; set; } + public string Position { get; set; } + public bool IsActive { get; set; } + public bool IsActivated { get; set; } +} \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/ReasonDto.cs b/src/Application/Common/Models/Dtos/ReasonDto.cs new file mode 100644 index 00000000..8e82c755 --- /dev/null +++ b/src/Application/Common/Models/Dtos/ReasonDto.cs @@ -0,0 +1,11 @@ +using Application.Common.Mappings; +using Domain.Entities.Logging; +using Domain.Enums; + +namespace Application.Common.Models.Dtos; + +public class ReasonDto : IMapFrom +{ + public RequestType Type { get; set; } + public string Reason { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Application/Documents/Commands/ApproveDocument.cs b/src/Application/Documents/Commands/ApproveDocument.cs new file mode 100644 index 00000000..15bb65cd --- /dev/null +++ b/src/Application/Documents/Commands/ApproveDocument.cs @@ -0,0 +1,78 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Messages; +using Application.Common.Models.Dtos.ImportDocument; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities.Logging; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Application.Documents.Commands; + +public class ApproveDocument +{ + public record Command : IRequest + { + public Guid PerformingUserId { get; init; } + public Guid DocumentId { get; init; } + public string Reason { get; init; } = null!; + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public CommandHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var document = await _context.Documents + .Include(x => x.Department) + .FirstOrDefaultAsync(x => + x.Id == request.DocumentId, cancellationToken); + if (document is null) + { + throw new ConflictException("Document does not exist."); + } + + if (document.Status is not DocumentStatus.Issued) + { + throw new ConflictException("Request cannot be approved."); + } + + document.Status = DocumentStatus.Approved; + var performingUser = await _context.Users.FirstOrDefaultAsync(x => x.Id == request.PerformingUserId, cancellationToken); + var log = new DocumentLog() + { + Object = document, + Time = LocalDateTime.FromDateTime(DateTime.Now), + User = performingUser!, + UserId = performingUser!.Id, + Action = DocumentLogMessages.Import.Approve, + }; + var requestLog = new RequestLog() + { + Object = document, + Time = LocalDateTime.FromDateTime(DateTime.Now), + User = performingUser, + UserId = performingUser.Id, + Action = RequestLogMessages.ApproveImport, + Reason = request.Reason, + }; + var result = _context.Documents.Update(document); + await _context.DocumentLogs.AddAsync(log, cancellationToken); + await _context.RequestLogs.AddAsync(requestLog, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Commands/AssignDocument.cs b/src/Application/Documents/Commands/AssignDocument.cs new file mode 100644 index 00000000..80096146 --- /dev/null +++ b/src/Application/Documents/Commands/AssignDocument.cs @@ -0,0 +1,90 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Messages; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities.Logging; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Documents.Commands; + +public class AssignDocument +{ + public record Command : IRequest + { + public Guid PerformingUserId { get; init; } + public Guid DocumentId { get; init; } + public Guid FolderId { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public CommandHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var document = await _context.Documents + .Include(x => x.Folder) + .FirstOrDefaultAsync(x => + x.Id == request.DocumentId, cancellationToken); + if (document is null) + { + throw new ConflictException("Document does not exist."); + } + + if (document.Status is not DocumentStatus.Approved) + { + throw new ConflictException("Document cannot be assigned."); + } + + var folder = await _context.Folders + .FirstOrDefaultAsync(x => x.Id == request.FolderId, cancellationToken); + + if (folder is null) + { + throw new ConflictException("Folder does not exist."); + } + + if (folder.NumberOfDocuments >= folder.Capacity) + { + throw new ConflictException("This folder cannot accept more documents."); + } + + var performingUser = await _context.Users.FirstOrDefaultAsync(x => x.Id == request.PerformingUserId, cancellationToken); + document.Folder = folder; + document.LastModified = LocalDateTime.FromDateTime(DateTime.Now); + document.LastModifiedBy = performingUser!.Id; + var log = new DocumentLog() + { + Object = document, + Time = LocalDateTime.FromDateTime(DateTime.Now), + User = performingUser!, + UserId = performingUser!.Id, + Action = DocumentLogMessages.Import.Assign, + }; + var folderLog = new FolderLog() + { + Object = folder, + Time = LocalDateTime.FromDateTime(DateTime.Now), + User = performingUser!, + UserId = performingUser!.Id, + Action = FolderLogMessage.AssignDocument, + }; + var result = _context.Documents.Update(document); + await _context.DocumentLogs.AddAsync(log, cancellationToken); + await _context.FolderLogs.AddAsync(folderLog, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Commands/CheckinDocument.cs b/src/Application/Documents/Commands/CheckinDocument.cs new file mode 100644 index 00000000..73351023 --- /dev/null +++ b/src/Application/Documents/Commands/CheckinDocument.cs @@ -0,0 +1,75 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Messages; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities.Logging; +using Domain.Entities.Physical; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Documents.Commands; + +public class CheckinDocument +{ + public record Command : IRequest + { + public Guid PerformingUserId { get; init; } + public Guid DocumentId { get; init; } + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public CommandHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var document = await _context.Documents + .Include(x => x.Department) + .Include(x => x.Folder) + .FirstOrDefaultAsync(x => x.Id == request.DocumentId, cancellationToken); + + if (document is null) + { + throw new KeyNotFoundException("Document does not exist."); + } + + if (document.Status is not DocumentStatus.Approved) + { + throw new ConflictException("Request cannot be checked in."); + } + + if (document.Folder is null) + { + throw new ConflictException("Request cannot be checked in."); + } + + var performingUser = await _context.Users.FirstOrDefaultAsync(x => x.Id == request.PerformingUserId, cancellationToken); + document.Status = DocumentStatus.Available; + document.LastModified = LocalDateTime.FromDateTime(DateTime.Now); + document.LastModifiedBy = performingUser!.Id; + var log = new DocumentLog() + { + User = performingUser, + UserId = performingUser.Id, + Object = document, + Time = LocalDateTime.FromDateTime(DateTime.Now), + Action = DocumentLogMessages.Import.Checkin, + }; + + var result = _context.Documents.Update(document); + await _context.DocumentLogs.AddAsync(log, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Commands/ImportDocument.cs b/src/Application/Documents/Commands/ImportDocument.cs index 6f1d441e..eaf17236 100644 --- a/src/Application/Documents/Commands/ImportDocument.cs +++ b/src/Application/Documents/Commands/ImportDocument.cs @@ -1,6 +1,7 @@ using Application.Common.Exceptions; using Application.Common.Interfaces; using Application.Common.Messages; +using Application.Common.Models.Dtos.ImportDocument; using Application.Common.Models.Dtos.Physical; using AutoMapper; using Domain.Entities.Logging; @@ -22,6 +23,7 @@ public record Command : IRequest public string DocumentType { get; init; } = null!; public Guid ImporterId { get; init; } public Guid FolderId { get; init; } + public bool IsPrivate { get; init; } } public class CommandHandler : IRequestHandler @@ -75,7 +77,8 @@ public async Task Handle(Command request, CancellationToken cancell Importer = importer, Department = importer.Department, Folder = folder, - Status = DocumentStatus.Issued, + Status = DocumentStatus.Available, + IsPrivate = request.IsPrivate, Created = LocalDateTime.FromDateTime(DateTime.Now), CreatedBy = performingUser!.Id, }; diff --git a/src/Application/Documents/Commands/RejectDocument.cs b/src/Application/Documents/Commands/RejectDocument.cs new file mode 100644 index 00000000..d58bb281 --- /dev/null +++ b/src/Application/Documents/Commands/RejectDocument.cs @@ -0,0 +1,75 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Messages; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities.Logging; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Documents.Commands; + +public class RejectDocument +{ + public record Command : IRequest + { + public Guid PerformingUserId { get; init; } + public Guid DocumentId { get; init; } + public string Reason { get; init; } = null!; + } + + public class CommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public CommandHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(Command request, CancellationToken cancellationToken) + { + var document = await _context.Documents + .Include(x => x.Department) + .FirstOrDefaultAsync(x => + x.Id == request.DocumentId, cancellationToken); + if (document is null) + { + throw new ConflictException("Document does not exist."); + } + + if (document.Status is not DocumentStatus.Issued) + { + throw new ConflictException("Request cannot be rejected."); + } + + document.Status = DocumentStatus.Rejected; + var performingUser = await _context.Users.FirstOrDefaultAsync(x => x.Id == request.PerformingUserId, cancellationToken); + var log = new DocumentLog() + { + Object = document, + Time = LocalDateTime.FromDateTime(DateTime.Now), + User = performingUser!, + UserId = performingUser!.Id, + Action = DocumentLogMessages.Import.Reject, + }; + var requestLog = new RequestLog() + { + Object = document, + Time = LocalDateTime.FromDateTime(DateTime.Now), + User = performingUser, + UserId = performingUser.Id, + Action = RequestLogMessages.RejectImport, + Reason = request.Reason, + }; + var result = _context.Documents.Update(document); + await _context.DocumentLogs.AddAsync(log, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Commands/RequestImportDocument.cs b/src/Application/Documents/Commands/RequestImportDocument.cs index acc3df27..4efe3ca1 100644 --- a/src/Application/Documents/Commands/RequestImportDocument.cs +++ b/src/Application/Documents/Commands/RequestImportDocument.cs @@ -1,6 +1,7 @@ using Application.Common.Exceptions; using Application.Common.Interfaces; using Application.Common.Messages; +using Application.Common.Models.Dtos.ImportDocument; using Application.Common.Models.Dtos.Physical; using AutoMapper; using Domain.Entities.Logging; @@ -15,29 +16,27 @@ namespace Application.Documents.Commands; public class RequestImportDocument { - public record Command : IRequest + public record Command : IRequest { public string Title { get; init; } = null!; public string? Description { get; init; } public string DocumentType { get; init; } = null!; public Guid IssuerId { get; init; } + public bool IsPrivate { get; set; } } - public class CommandHandler : IRequestHandler + public class CommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; - private readonly ILogger _logger; - - public CommandHandler(IApplicationDbContext context, IMapper mapper, ILogger logger) + public CommandHandler(IApplicationDbContext context, IMapper mapper) { _context = context; _mapper = mapper; - _logger = logger; } - public async Task Handle(Command request, CancellationToken cancellationToken) + public async Task Handle(Command request, CancellationToken cancellationToken) { var issuer = await _context.Users .Include(x => x.Department) @@ -64,6 +63,7 @@ public async Task Handle(Command request, CancellationToken cancell Importer = issuer, Department = issuer.Department, Status = DocumentStatus.Issued, + IsPrivate = request.IsPrivate, Created = LocalDateTime.FromDateTime(DateTime.Now), CreatedBy = issuer.Id, }; @@ -73,13 +73,13 @@ public async Task Handle(Command request, CancellationToken cancell Time = LocalDateTime.FromDateTime(DateTime.Now), User = issuer, UserId = issuer.Id, - Action = DocumentLogMessages.Import.NewImportRequest + Action = DocumentLogMessages.Import.NewImportRequest, }; var result = await _context.Documents.AddAsync(entity, cancellationToken); await _context.DocumentLogs.AddAsync(log, cancellationToken); await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); + return _mapper.Map(result.Entity); } } } \ No newline at end of file diff --git a/src/Application/Documents/Commands/UpdateDocument.cs b/src/Application/Documents/Commands/UpdateDocument.cs index d670ed86..442da5ce 100644 --- a/src/Application/Documents/Commands/UpdateDocument.cs +++ b/src/Application/Documents/Commands/UpdateDocument.cs @@ -51,6 +51,7 @@ public CommandHandler(IApplicationDbContext context, IMapper mapper) public async Task Handle(Command request, CancellationToken cancellationToken) { var document = await _context.Documents + .Include(x => x.Department) .Include( x => x.Importer) .FirstOrDefaultAsync( x => x.Id.Equals(request.DocumentId), cancellationToken); diff --git a/src/Application/Documents/Queries/GetAllIssuedDocumentsPaginated.cs b/src/Application/Documents/Queries/GetAllIssuedDocumentsPaginated.cs new file mode 100644 index 00000000..60a1cd78 --- /dev/null +++ b/src/Application/Documents/Queries/GetAllIssuedDocumentsPaginated.cs @@ -0,0 +1,74 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Mappings; +using Application.Common.Models; +using Application.Common.Models.Dtos.ImportDocument; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities; +using Domain.Entities.Physical; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Documents.Queries; + +public class GetAllIssuedDocumentsPaginated +{ + public record Query : IRequest> + { + public Guid DepartmentId { get; init; } + public string? SearchTerm { get; init; } + public int? Page { get; init; } + public int? Size { get; init; } + public string? SortBy { get; init; } + public string? SortOrder { get; init; } + } + + public class QueryHandler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task> Handle(Query request, + CancellationToken cancellationToken) + { + var documents = _context.Documents + .Include(x => x.Importer) + .Where(x => x.Status == DocumentStatus.Issued); + + documents = documents.Where(x => x.Department!.Id == request.DepartmentId); + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + documents = documents.Where(x => + x.Title.ToLower().Contains(request.SearchTerm.ToLower())); + } + + var sortBy = request.SortBy; + if (sortBy is null || !sortBy.MatchesPropertyName()) + { + sortBy = nameof(IssuedDocumentDto.Id); + } + var sortOrder = request.SortOrder ?? "asc"; + var pageNumber = request.Page is null or <= 0 ? 1 : request.Page; + var sizeNumber = request.Size is null or <= 0 ? 5 : request.Size; + + var count = await documents.CountAsync(cancellationToken); + var list = await documents + .OrderByCustom(sortBy, sortOrder) + .Paginate(pageNumber.Value, sizeNumber.Value) + .ToListAsync(cancellationToken); + + var result = _mapper.Map>(list); + + return new PaginatedList(result, count, pageNumber.Value, sizeNumber.Value); + } + } +} \ No newline at end of file diff --git a/src/Application/Documents/Queries/GetDocumentReason.cs b/src/Application/Documents/Queries/GetDocumentReason.cs new file mode 100644 index 00000000..9088f785 --- /dev/null +++ b/src/Application/Documents/Queries/GetDocumentReason.cs @@ -0,0 +1,43 @@ +using Application.Common.Interfaces; +using Application.Common.Models.Dtos; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Documents.Queries; + +public class GetDocumentReason +{ + public record Query : IRequest + { + public Guid DocumentId { get; init; } + public RequestType Type { get; set; } + } + + public class QueryHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + public async Task Handle(Query request, CancellationToken cancellationToken) + { + var log = await _context.RequestLogs + .FirstOrDefaultAsync(x => x.Object!.Id == request.DocumentId + && x.Type == request.Type, cancellationToken); + + if (log is null) + { + throw new KeyNotFoundException("Document does not have a request."); + } + + return _mapper.Map(log); + } + } +} \ No newline at end of file diff --git a/src/Application/Staffs/Commands/AddStaff.cs b/src/Application/Staffs/Commands/AddStaff.cs index cb381aae..343fb367 100644 --- a/src/Application/Staffs/Commands/AddStaff.cs +++ b/src/Application/Staffs/Commands/AddStaff.cs @@ -39,13 +39,20 @@ public async Task Handle(Command request, CancellationToken cancellati throw new KeyNotFoundException("User does not exist."); } - var room = await _context.Rooms.FirstOrDefaultAsync(x => x.Id == request.RoomId, cancellationToken); + var room = await _context.Rooms + .Include(x => x.Staff) + .FirstOrDefaultAsync(x => x.Id == request.RoomId, cancellationToken); if (room is null) { throw new KeyNotFoundException("Room does not exist."); } + if (room.Staff is not null) + { + throw new ConflictException("Room already has a staff."); + } + var existedStaff = await _context.Staffs .Include(x => x.Room) .Include(x => x.User) diff --git a/src/Domain/Entities/Logging/RequestLog.cs b/src/Domain/Entities/Logging/RequestLog.cs index fbf7d568..e9605ed3 100644 --- a/src/Domain/Entities/Logging/RequestLog.cs +++ b/src/Domain/Entities/Logging/RequestLog.cs @@ -7,4 +7,5 @@ namespace Domain.Entities.Logging; public class RequestLog : BaseLoggingEntity { public RequestType Type { get; set; } + public string Reason { get; set; } = null!; } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Migrations/20230613140433_RequestLogReason.Designer.cs b/src/Infrastructure/Persistence/Migrations/20230613140433_RequestLogReason.Designer.cs new file mode 100644 index 00000000..710cd220 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230613140433_RequestLogReason.Designer.cs @@ -0,0 +1,1019 @@ +// +using System; +using Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230613140433_RequestLogReason")] + partial class RequestLogReason + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.FileEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("FileType") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("DocumentLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("FolderLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("LockerLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("RoomLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("ObjectId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ObjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogs"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowTime") + .HasColumnType("timestamp without time zone"); + + b.Property("BorrowerId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DueTime") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BorrowerId"); + + b.HasIndex("DocumentId"); + + b.ToTable("Borrows"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("EntryId") + .HasColumnType("uuid"); + + b.Property("FolderId") + .HasColumnType("uuid"); + + b.Property("ImporterId") + .HasColumnType("uuid"); + + b.Property("IsPrivate") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.HasIndex("EntryId") + .IsUnique(); + + b.HasIndex("FolderId"); + + b.HasIndex("ImporterId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LockerId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfDocuments") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("LockerId"); + + b.ToTable("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfFolders") + .HasColumnType("integer"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.ToTable("Lockers"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("AllowedOperations") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("DocumentId", "EmployeeId"); + + b.HasIndex("EmployeeId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Capacity") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAvailable") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NumberOfLockers") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name"); + + b.HasIndex("DepartmentId") + .IsUnique(); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("UserId"); + + b.Property("RoomId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoomId") + .IsUnique(); + + b.ToTable("Staffs"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("ExpiryDateTime") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("IsUsed") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("JwtId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Token"); + + b.HasIndex("UserId"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.Property("TokenHash") + .HasColumnType("text"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("IsInvalidated") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("ResetPasswordTokens"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DepartmentId") + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("FirstName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActivated") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone"); + + b.Property("LastModifiedBy") + .HasColumnType("uuid"); + + b.Property("LastName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PasswordSalt") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Position") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("UserGroupId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("UserGroupId"); + + b.ToTable("Memberships"); + }); + + modelBuilder.Entity("Domain.Entities.Digital.Entry", b => + { + b.HasOne("Domain.Entities.Digital.FileEntity", "File") + .WithOne() + .HasForeignKey("Domain.Entities.Digital.Entry", "FileId"); + + b.Navigation("File"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.DocumentLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.FolderLog", b => + { + b.HasOne("Domain.Entities.Physical.Folder", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.LockerLog", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RequestLog", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.RoomLog", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Object") + .WithMany() + .HasForeignKey("ObjectId"); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Logging.UserLog", b => + { + b.HasOne("Domain.Entities.User", "Object") + .WithMany() + .HasForeignKey("ObjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Object"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Borrow", b => + { + b.HasOne("Domain.Entities.User", "Borrower") + .WithMany() + .HasForeignKey("BorrowerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borrower"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Document", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.HasOne("Domain.Entities.Digital.Entry", "Entry") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Document", "EntryId"); + + b.HasOne("Domain.Entities.Physical.Folder", "Folder") + .WithMany("Documents") + .HasForeignKey("FolderId"); + + b.HasOne("Domain.Entities.User", "Importer") + .WithMany() + .HasForeignKey("ImporterId"); + + b.Navigation("Department"); + + b.Navigation("Entry"); + + b.Navigation("Folder"); + + b.Navigation("Importer"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.HasOne("Domain.Entities.Physical.Locker", "Locker") + .WithMany("Folders") + .HasForeignKey("LockerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Locker"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithMany("Lockers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Permission", b => + { + b.HasOne("Domain.Entities.Physical.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithOne("Room") + .HasForeignKey("Domain.Entities.Physical.Room", "DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Staff", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithOne() + .HasForeignKey("Domain.Entities.Physical.Staff", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.Physical.Room", "Room") + .WithOne("Staff") + .HasForeignKey("Domain.Entities.Physical.Staff", "RoomId"); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.RefreshToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.ResetPasswordToken", b => + { + b.HasOne("Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Domain.Entities.User", b => + { + b.HasOne("Domain.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId"); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Memberships", b => + { + b.HasOne("Domain.Entities.Digital.UserGroup", null) + .WithMany() + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Domain.Entities.Department", b => + { + b.Navigation("Room"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Folder", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Locker", b => + { + b.Navigation("Folders"); + }); + + modelBuilder.Entity("Domain.Entities.Physical.Room", b => + { + b.Navigation("Lockers"); + + b.Navigation("Staff"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20230613140433_RequestLogReason.cs b/src/Infrastructure/Persistence/Migrations/20230613140433_RequestLogReason.cs new file mode 100644 index 00000000..5ca28aa1 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20230613140433_RequestLogReason.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class RequestLogReason : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Reason", + table: "RequestLogs", + type: "text", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Reason", + table: "RequestLogs"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index 679625fe..ac299d19 100644 --- a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -201,6 +201,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ObjectId") .HasColumnType("uuid"); + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + b.Property("Time") .HasColumnType("timestamp without time zone");