diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 993b5ad1..a1e8f565 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -9,7 +9,7 @@ services: - '8888:80' environment: - ASPNETCORE_ENVIRONMENT=Testing - - PROFILE_DatabaseSettings__ConnectionString=Server=database;Port=5432;Database=mytestdb;User ID=profiletester;Password=supasupasecured; + - PROFILE_DatabaseSettings__ConnectionString=Server=database;Port=5432;Database=mytestdb;User ID=profiletester;Password=supasupasecured;Include Error Detail=true depends_on: database: condition: service_started diff --git a/src/Api/ConfigureServices.cs b/src/Api/ConfigureServices.cs index 39dabb10..4c4f747f 100644 --- a/src/Api/ConfigureServices.cs +++ b/src/Api/ConfigureServices.cs @@ -1,6 +1,8 @@ using System.Reflection; using Api.Middlewares; using Api.Policies; +using Api.Services; +using Application.Common.Interfaces; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.OpenApi.Models; @@ -53,6 +55,7 @@ private static IServiceCollection AddServices(this IServiceCollection services) { // In order for ExceptionMiddleware to work services.AddScoped(); + services.AddScoped(); return services; } diff --git a/src/Api/Controllers/BorrowsController.cs b/src/Api/Controllers/BorrowsController.cs new file mode 100644 index 00000000..0c7b34c1 --- /dev/null +++ b/src/Api/Controllers/BorrowsController.cs @@ -0,0 +1,264 @@ +using Api.Controllers.Payload.Requests.Borrows; +using Application.Borrows.Commands; +using Application.Borrows.Queries; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Physical; +using Application.Identity; +using Infrastructure.Identity.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers; + +public class BorrowsController : ApiControllerBase +{ + private readonly ICurrentUserService _currentUserService; + public BorrowsController(ICurrentUserService currentUserService) + { + _currentUserService = currentUserService; + } + /// + /// Borrow a document request + /// + /// Borrow document details + /// A BorrowDto of the requested borrow + [RequiresRole(IdentityData.Roles.Employee)] + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> BorrowDocument([FromBody] BorrowDocumentRequest request) + { + var borrowerId = _currentUserService.GetCurrentUser().Id; + var command = new BorrowDocument.Command() + { + BorrowerId = borrowerId, + DocumentId = request.DocumentId, + BorrowFrom = request.BorrowFrom, + BorrowTo = request.BorrowTo, + Reason = request.Reason, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + [RequiresRole(IdentityData.Roles.Staff)] + [HttpGet("staffs")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>>> GetAllRequestsAsStaffPaginated( + [FromQuery] GetAllBorrowRequestsPaginatedAsStaffQueryParameters queryParameters) + { + var departmentId = _currentUserService.GetCurrentDepartmentForStaff(); + var command = new GetAllBorrowRequestsPaginated.Query() + { + DepartmentId = departmentId, + EmployeeId = queryParameters.EmployeeId, + DocumentId = queryParameters.DocumentId, + Page = queryParameters.Page, + Size = queryParameters.Size, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder, + }; + var result = await Mediator.Send(command); + return Ok(Result>.Succeed(result)); + } + + /// + /// Get all borrow requests as admin paginated + /// + /// + /// + [RequiresRole(IdentityData.Roles.Admin)] + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>>> GetAllRequestsAsAdminPaginated( + [FromQuery] GetAllBorrowRequestsPaginatedAsAdminQueryParameters queryParameters) + { + var command = new GetAllBorrowRequestsPaginated.Query() + { + DepartmentId = queryParameters.DepartmentId, + EmployeeId = queryParameters.EmployeeId, + DocumentId = queryParameters.DocumentId, + Page = queryParameters.Page, + Size = queryParameters.Size, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder, + }; + var result = await Mediator.Send(command); + return Ok(Result>.Succeed(result)); + } + + /// + /// Get all borrow requests as employee paginated + /// + /// + /// + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("employees")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>>> GetAllRequestsAsEmployeePaginated( + [FromQuery] GetAllBorrowRequestsPaginatedAsEmployeeQueryParameters queryParameters) + { + var userId = _currentUserService.GetCurrentUser().Id; + var command = new GetAllBorrowRequestsPaginated.Query() + { + EmployeeId = userId, + DocumentId = queryParameters.DocumentId, + Page = queryParameters.Page, + Size = queryParameters.Size, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder, + }; + var result = await Mediator.Send(command); + return Ok(Result>.Succeed(result)); + } + + /// + /// Get all borrow requests for a document paginated + /// + /// + /// + /// + [RequiresRole(IdentityData.Roles.Employee)] + [HttpGet("documents/{documentId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>>> GetAllRequestsForDocumentPaginated( + [FromRoute] Guid documentId, + [FromQuery] GetAllBorrowRequestsPaginatedForDocumentQueryParameters queryParameters) + { + var command = new GetAllBorrowRequestsPaginated.Query() + { + DocumentId = documentId, + Page = queryParameters.Page, + Size = queryParameters.Size, + SortBy = queryParameters.SortBy, + SortOrder = queryParameters.SortOrder, + }; + var result = await Mediator.Send(command); + return Ok(Result>.Succeed(result)); + } + + /// + /// Approve a borrow request + /// + /// Id of the borrow request to be approved + /// A BorrowDto of the approved borrow request + [RequiresRole(IdentityData.Roles.Staff)] + [HttpPost("approve/{borrowId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> ApproveRequest([FromRoute] Guid borrowId) + { + var command = new ApproveBorrowRequest.Command() + { + BorrowId = borrowId, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Reject a borrow request + /// + /// Id of the borrow request to be rejected + /// A BorrowDto of the rejected borrow request + [RequiresRole(IdentityData.Roles.Staff)] + [HttpPost("reject/{borrowId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> RejectRequest([FromRoute] Guid borrowId) + { + var command = new RejectBorrowRequest.Command() + { + BorrowId = borrowId, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Check out a borrow request + /// + /// Id of the borrow request to be checked out + /// A BorrowDto of the checked out borrow request + [RequiresRole(IdentityData.Roles.Staff)] + [HttpPost("checkout/{borrowId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> Checkout([FromRoute] Guid borrowId) + { + var command = new CheckoutDocument.Command() + { + BorrowId = borrowId, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Return document + /// + /// Id of the document to be returned + /// A BorrowDto of the returned document borrow request + [RequiresRole(IdentityData.Roles.Staff)] + [HttpPost("return/{documentId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> Return([FromRoute] Guid documentId) + { + var command = new ReturnDocument.Command() + { + DocumentId = documentId, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } + + /// + /// Update borrow request + /// + /// Id of the borrow request to be updated + /// Update borrow details + /// A BorrowDto of the updated borrow request + [RequiresRole(IdentityData.Roles.Employee)] + [HttpPut("{borrowId:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + public async Task>> Update( + [FromRoute] Guid borrowId, + [FromBody] UpdateBorrowRequest request) + { + var command = new UpdateBorrow.Command() + { + BorrowId = borrowId, + BorrowFrom = request.BorrowFrom, + BorrowTo = request.BorrowTo, + Reason = request.Reason, + }; + var result = await Mediator.Send(command); + return Ok(Result.Succeed(result)); + } +} \ No newline at end of file diff --git a/src/Api/Controllers/DocumentsController.cs b/src/Api/Controllers/DocumentsController.cs index 5c56986b..8d817d70 100644 --- a/src/Api/Controllers/DocumentsController.cs +++ b/src/Api/Controllers/DocumentsController.cs @@ -105,7 +105,7 @@ public async Task>> Import([FromBody] ImportDoc /// Id of the document to be updated /// Update document details /// A DocumentDto of the updated document - [RequiresRole(IdentityData.Roles.Admin)] + [RequiresRole(IdentityData.Roles.Admin, IdentityData.Roles.Staff)] [HttpPut("{documentId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] @@ -128,7 +128,7 @@ public async Task>> Update([FromRoute] Guid doc /// Delete a document /// /// Id of the document to be deleted - /// A DocumentDto of the deleted document + /// A DocumentDto of the deleted document [HttpDelete("{documentId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] diff --git a/src/Api/Controllers/Payload/Requests/Borrows/BorrowDocumentRequest.cs b/src/Api/Controllers/Payload/Requests/Borrows/BorrowDocumentRequest.cs new file mode 100644 index 00000000..f3a0085d --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Borrows/BorrowDocumentRequest.cs @@ -0,0 +1,24 @@ +namespace Api.Controllers.Payload.Requests.Borrows; + +/// +/// Request details to borrow a document +/// +public class BorrowDocumentRequest +{ + /// + /// Id of the document to be borrowed + /// + public Guid DocumentId { get; set; } + /// + /// Borrow from + /// + public DateTime BorrowFrom { get; set; } + /// + /// Borrow to + /// + public DateTime BorrowTo { get; set; } + /// + /// Reason of borrowing + /// + public string Reason { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsAdminQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsAdminQueryParameters.cs new file mode 100644 index 00000000..8ea8eca1 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsAdminQueryParameters.cs @@ -0,0 +1,14 @@ +namespace Api.Controllers.Payload.Requests.Borrows; + +/// +/// Query parameters for getting all borrow requests with pagination as admin +/// +public class GetAllBorrowRequestsPaginatedAsAdminQueryParameters : PaginatedQueryParameters +{ + /// + /// Id of the department to get borrow requests in + /// + public Guid? DepartmentId { get; set; } + public Guid? DocumentId { get; set; } + public Guid? EmployeeId { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsEmployeeQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsEmployeeQueryParameters.cs new file mode 100644 index 00000000..922651ed --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsEmployeeQueryParameters.cs @@ -0,0 +1,9 @@ +namespace Api.Controllers.Payload.Requests.Borrows; + +/// +/// Query parameters for getting all borrow requests with pagination as employee +/// +public class GetAllBorrowRequestsPaginatedAsEmployeeQueryParameters : PaginatedQueryParameters +{ + public Guid? DocumentId { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsStaffQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsStaffQueryParameters.cs new file mode 100644 index 00000000..d9656766 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedAsStaffQueryParameters.cs @@ -0,0 +1,10 @@ +namespace Api.Controllers.Payload.Requests.Borrows; + +/// +/// Query parameters for getting all borrow requests with pagination as staff +/// +public class GetAllBorrowRequestsPaginatedAsStaffQueryParameters : PaginatedQueryParameters +{ + public Guid? DocumentId { get; set; } + public Guid? EmployeeId { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedForDocumentQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedForDocumentQueryParameters.cs new file mode 100644 index 00000000..a44619fe --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Borrows/GetAllBorrowRequestsPaginatedForDocumentQueryParameters.cs @@ -0,0 +1,6 @@ +namespace Api.Controllers.Payload.Requests.Borrows; + +public class GetAllBorrowRequestsPaginatedForDocumentQueryParameters : PaginatedQueryParameters +{ + +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Borrows/UpdateBorrowRequest.cs b/src/Api/Controllers/Payload/Requests/Borrows/UpdateBorrowRequest.cs new file mode 100644 index 00000000..dbbef730 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/Borrows/UpdateBorrowRequest.cs @@ -0,0 +1,8 @@ +namespace Api.Controllers.Payload.Requests.Borrows; + +public class UpdateBorrowRequest +{ + public DateTime BorrowFrom { get; init; } + public DateTime BorrowTo { get; init; } + public string Reason { get; init; } = null!; +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsPaginatedQueryParameters.cs index 7f134c04..7b97fc8f 100644 --- a/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsPaginatedQueryParameters.cs +++ b/src/Api/Controllers/Payload/Requests/Documents/GetAllDocumentsPaginatedQueryParameters.cs @@ -3,7 +3,7 @@ namespace Api.Controllers.Payload.Requests.Documents; /// /// Query parameters for getting all documents with pagination /// -public class GetAllDocumentsPaginatedQueryParameters +public class GetAllDocumentsPaginatedQueryParameters : PaginatedQueryParameters { /// /// Id of the room to find documents in @@ -21,20 +21,4 @@ public class GetAllDocumentsPaginatedQueryParameters /// Search term /// public string? SearchTerm { get; set; } - /// - /// Page number - /// - public int? Page { get; set; } - /// - /// Size number - /// - public int? Size { get; set; } - /// - /// Sort criteria - /// - public string? SortBy { get; set; } - /// - /// Sort direction - /// - public string? SortOrder { get; set; } } \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Folders/GetAllFoldersPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Folders/GetAllFoldersPaginatedQueryParameters.cs index ae6399ab..298a30b0 100644 --- a/src/Api/Controllers/Payload/Requests/Folders/GetAllFoldersPaginatedQueryParameters.cs +++ b/src/Api/Controllers/Payload/Requests/Folders/GetAllFoldersPaginatedQueryParameters.cs @@ -3,8 +3,12 @@ namespace Api.Controllers.Payload.Requests.Folders; /// /// Query parameters for getting all folders with pagination /// -public class GetAllFoldersPaginatedQueryParameters +public class GetAllFoldersPaginatedQueryParameters : PaginatedQueryParameters { + /// + /// Search term + /// + public string? SearchTerm { get; set; } /// /// Id of the room to find folders in /// @@ -13,24 +17,4 @@ public class GetAllFoldersPaginatedQueryParameters /// Id of the locker to find folders in /// public Guid? LockerId { get; set; } - /// - /// Search term - /// - public string? SearchTerm { get; init; } - /// - /// Page number - /// - public int? Page { get; set; } - /// - /// Size number - /// - public int? Size { get; set; } - /// - /// Sort criteria - /// - public string? SortBy { get; set; } - /// - /// Sort direction - /// - public string? SortOrder { get; set; } } \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Lockers/GetAllLockersPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Lockers/GetAllLockersPaginatedQueryParameters.cs index 9f701ed4..25372261 100644 --- a/src/Api/Controllers/Payload/Requests/Lockers/GetAllLockersPaginatedQueryParameters.cs +++ b/src/Api/Controllers/Payload/Requests/Lockers/GetAllLockersPaginatedQueryParameters.cs @@ -5,7 +5,7 @@ namespace Api.Controllers.Payload.Requests.Lockers; /// /// Query parameters for getting all lockers with pagination /// -public class GetAllLockersPaginatedQueryParameters +public class GetAllLockersPaginatedQueryParameters : PaginatedQueryParameters { /// /// Id of the room to find lockers in @@ -15,20 +15,4 @@ public class GetAllLockersPaginatedQueryParameters /// Search term /// public string? SearchTerm { get; set; } - /// - /// Page number - /// - public int? Page { get; set; } - /// - /// Size number - /// - public int? Size { get; set; } - /// - /// Sort criteria - /// - public string? SortBy { get; set; } - /// - /// Sort direction - /// - public string? SortOrder { get; set; } } \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/PaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/PaginatedQueryParameters.cs new file mode 100644 index 00000000..d29c3838 --- /dev/null +++ b/src/Api/Controllers/Payload/Requests/PaginatedQueryParameters.cs @@ -0,0 +1,21 @@ +namespace Api.Controllers.Payload.Requests; + +public class PaginatedQueryParameters +{ + /// + /// Page number + /// + public int? Page { get; set; } + /// + /// Size number + /// + public int? Size { get; set; } + /// + /// Sort criteria + /// + public string? SortBy { get; set; } + /// + /// Sort direction + /// + public string? SortOrder { get; set; } +} \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Rooms/GetAllRoomsPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Rooms/GetAllRoomsPaginatedQueryParameters.cs index 89a7bbb6..390ae4be 100644 --- a/src/Api/Controllers/Payload/Requests/Rooms/GetAllRoomsPaginatedQueryParameters.cs +++ b/src/Api/Controllers/Payload/Requests/Rooms/GetAllRoomsPaginatedQueryParameters.cs @@ -3,26 +3,10 @@ namespace Api.Controllers.Payload.Requests.Rooms; /// /// Query parameters for getting all rooms with pagination /// -public class GetAllRoomsPaginatedQueryParameters +public class GetAllRoomsPaginatedQueryParameters : PaginatedQueryParameters { /// /// Search term /// public string? SearchTerm { get; set; } - /// - /// Page number - /// - public int? Page { get; set; } - /// - /// Size number - /// - public int? Size { get; set; } - /// - /// Sort criteria - /// - public string? SortBy { get; set; } - /// - /// Sort direction - /// - public string? SortOrder { get; set; } } \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Staffs/GetAllStaffsPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Staffs/GetAllStaffsPaginatedQueryParameters.cs index 53fcbb87..323ec479 100644 --- a/src/Api/Controllers/Payload/Requests/Staffs/GetAllStaffsPaginatedQueryParameters.cs +++ b/src/Api/Controllers/Payload/Requests/Staffs/GetAllStaffsPaginatedQueryParameters.cs @@ -3,26 +3,10 @@ namespace Api.Controllers.Payload.Requests.Staffs; /// /// Query parameters for getting all staffs with pagination /// -public class GetAllStaffsPaginatedQueryParameters +public class GetAllStaffsPaginatedQueryParameters : PaginatedQueryParameters { /// /// Search term /// public string? SearchTerm { get; set; } - /// - /// Page number - /// - public int? Page { get; set; } - /// - /// Size number - /// - public int? Size { get; set; } - /// - /// Sort criteria - /// - public string? SortBy { get; set; } - /// - /// Sort direction - /// - public string? SortOrder { get; set; } } \ No newline at end of file diff --git a/src/Api/Controllers/Payload/Requests/Users/GetAllUsersPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Users/GetAllUsersPaginatedQueryParameters.cs index 9f47d022..47db40c8 100644 --- a/src/Api/Controllers/Payload/Requests/Users/GetAllUsersPaginatedQueryParameters.cs +++ b/src/Api/Controllers/Payload/Requests/Users/GetAllUsersPaginatedQueryParameters.cs @@ -3,7 +3,7 @@ namespace Api.Controllers.Payload.Requests.Users; /// /// Query parameters for getting all users with pagination /// -public class GetAllUsersPaginatedQueryParameters +public class GetAllUsersPaginatedQueryParameters : PaginatedQueryParameters { /// /// Id of the department to find users in @@ -13,20 +13,4 @@ public class GetAllUsersPaginatedQueryParameters /// Search term /// public string? SearchTerm { get; set; } - /// - /// Page number - /// - public int? Page { get; set; } - /// - /// Size number - /// - public int? Size { get; set; } - /// - /// Sort criteria - /// - public string? SortBy { get; set; } - /// - /// Sort direction - /// - public string? SortOrder { get; set; } } \ No newline at end of file diff --git a/src/Api/Controllers/RoomsController.cs b/src/Api/Controllers/RoomsController.cs index 5ecb848d..fa5198ee 100644 --- a/src/Api/Controllers/RoomsController.cs +++ b/src/Api/Controllers/RoomsController.cs @@ -61,7 +61,7 @@ public async Task>>> GetAllPaginated( /// Get empty containers paginated details /// A paginated list of EmptyLockerDto [RequiresRole(IdentityData.Roles.Staff)] - [HttpPost("empty-containers")] + [HttpPost("empty-containers/{roomId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/src/Api/Program.cs b/src/Api/Program.cs index 5bd6b849..2a739649 100644 --- a/src/Api/Program.cs +++ b/src/Api/Program.cs @@ -17,7 +17,6 @@ builder.Services.AddApplicationServices(); builder.Services.AddInfrastructureServices(builder.Configuration); builder.Services.AddApiServices(); - var app = builder.Build(); app.UseInfrastructure(builder.Configuration); diff --git a/src/Api/Services/CurrentUserService.cs b/src/Api/Services/CurrentUserService.cs index d8b6c481..0075bedb 100644 --- a/src/Api/Services/CurrentUserService.cs +++ b/src/Api/Services/CurrentUserService.cs @@ -1,5 +1,7 @@ using System.IdentityModel.Tokens.Jwt; using Application.Common.Interfaces; +using Domain.Entities; +using Microsoft.EntityFrameworkCore; namespace Api.Services; @@ -17,7 +19,7 @@ public CurrentUserService(IHttpContextAccessor httpContextAccessor, IApplication public string GetRole() { var userName = _httpContextAccessor.HttpContext!.User.Claims - .FirstOrDefault(x => x.Type.Equals(JwtRegisteredClaimNames.Sub)); + .FirstOrDefault(x => x.Type.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"))!.Value; if (userName is null) { throw new UnauthorizedAccessException(); @@ -51,4 +53,72 @@ public string GetRole() return user.Department?.Name; } + + public User GetCurrentUser() + { + var userName = _httpContextAccessor.HttpContext!.User.Claims + .FirstOrDefault(x => x.Type.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"))!.Value; + if (userName is null) + { + throw new UnauthorizedAccessException(); + } + + var user = _context.Users.FirstOrDefault(x => x.Username.Equals(userName)); + + if (user is null) + { + throw new UnauthorizedAccessException(); + } + + return user; + } + + public Guid? GetCurrentRoomForStaff() + { + var userName = _httpContextAccessor.HttpContext!.User.Claims + .FirstOrDefault(x => x.Type.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"))!.Value; + if (userName is null) + { + throw new UnauthorizedAccessException(); + } + + var staff = _context.Staffs + .Include(x => x.User) + .Include(x => x.Room) + .FirstOrDefault(x => x.User.Username.Equals(userName)); + + if (staff is null) + { + throw new UnauthorizedAccessException(); + } + + return staff.Room!.Id; + } + + public Guid? GetCurrentDepartmentForStaff() + { + var userName = _httpContextAccessor.HttpContext!.User.Claims + .FirstOrDefault(x => x.Type.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"))!.Value; + if (userName is null) + { + throw new UnauthorizedAccessException(); + } + + var staff = _context.Staffs + .Include(x => x.User) + .Include(x => x.Room) + .FirstOrDefault(x => x.User.Username.Equals(userName)); + + if (staff is null) + { + throw new UnauthorizedAccessException(); + } + + if (staff.Room is null) + { + throw new UnauthorizedAccessException(); + } + + return staff.Room!.DepartmentId; + } } \ No newline at end of file diff --git a/src/Application/Borrows/Commands/ApproveBorrowRequest.cs b/src/Application/Borrows/Commands/ApproveBorrowRequest.cs new file mode 100644 index 00000000..9d7b345d --- /dev/null +++ b/src/Application/Borrows/Commands/ApproveBorrowRequest.cs @@ -0,0 +1,80 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Borrows.Commands; + +public class ApproveBorrowRequest +{ + public record Command : IRequest + { + public Guid BorrowId { 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 borrowRequest = await _context.Borrows + .Include(x => x.Borrower) + .Include(x => x.Document) + .FirstOrDefaultAsync(x => x.Id == request.BorrowId, cancellationToken); + if (borrowRequest is null) + { + throw new KeyNotFoundException("Borrow request does not exist."); + } + + if (borrowRequest.Document.Status is DocumentStatus.Lost) + { + borrowRequest.Status = BorrowRequestStatus.NotProcessable; + _context.Borrows.Update(borrowRequest); + await _context.SaveChangesAsync(cancellationToken); + throw new ConflictException("Document is lost. Request is unprocessable."); + } + + if (borrowRequest.Status is not BorrowRequestStatus.Pending + && borrowRequest.Status is not BorrowRequestStatus.Rejected) + { + throw new ConflictException("Request cannot be approved."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(DateTime.Now); + var existedBorrow = await _context.Borrows + .FirstOrDefaultAsync(x => + x.Document.Id == borrowRequest.Document.Id + && x.Id != borrowRequest.Id + && ((x.DueTime > localDateTimeNow) + || x.Status == BorrowRequestStatus.Overdue), cancellationToken); + + if (existedBorrow is not null) + { + if (existedBorrow?.Status + is BorrowRequestStatus.Approved + or BorrowRequestStatus.CheckedOut + && borrowRequest.BorrowTime < existedBorrow.DueTime) + { + throw new ConflictException("This document cannot be borrowed."); + } + } + + borrowRequest.Status = BorrowRequestStatus.Approved; + var result = _context.Borrows.Update(borrowRequest); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Commands/BorrowDocument.cs b/src/Application/Borrows/Commands/BorrowDocument.cs new file mode 100644 index 00000000..602003bc --- /dev/null +++ b/src/Application/Borrows/Commands/BorrowDocument.cs @@ -0,0 +1,138 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities.Physical; +using Domain.Statuses; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Borrows.Commands; + +public class BorrowDocument +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Reason) + .MaximumLength(512).WithMessage("Reason cannot exceed 512 characters."); + + RuleFor(x => x.BorrowFrom) + .GreaterThan(DateTime.Now).WithMessage("Borrow date cannot be in the past.") + .Must((command, borrowTime) => borrowTime < command.BorrowTo).WithMessage("Due date cannot be before borrow date."); + + RuleFor(x => x.BorrowTo) + .GreaterThan(DateTime.Now).WithMessage("Due date cannot be in the past."); + } + } + + public record Command : IRequest + { + public Guid DocumentId { get; init; } + public Guid BorrowerId { get; init; } + public DateTime BorrowFrom { get; init; } + public DateTime BorrowTo { 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 user = await _context.Users + .Include(x => x.Department) + .FirstOrDefaultAsync(x => x.Id == request.BorrowerId, cancellationToken); + if (user is null) + { + throw new KeyNotFoundException("User does not exist."); + } + + if (user.IsActive is false) + { + throw new ConflictException("User is not active."); + } + + if (user.IsActivated is false) + { + throw new ConflictException("User is not activated."); + } + + var document = await _context.Documents + .Include(x => x.Department) + .FirstOrDefaultAsync(x => x.Id == request.DocumentId, cancellationToken); + if (document is null) + { + throw new KeyNotFoundException("Document does not exist."); + } + + if (document.Status is not DocumentStatus.Available) + { + throw new ConflictException("Document is not available."); + } + + if (document.Department!.Id != user.Department!.Id) + { + throw new ConflictException("User is not allowed to borrow this document."); + } + + // getting out a request of that document which is either not due or overdue + // if the request is in time, meaning not overdue, + // then check if its due date is less than the borrow request date, if not then check + // if it's already been approved, checked out or lost, meaning + var localDateTimeNow = LocalDateTime.FromDateTime(DateTime.Now); + var existedBorrow = await _context.Borrows + .Include(x => x.Borrower) + .FirstOrDefaultAsync(x => + x.Document.Id == request.DocumentId + && ((x.DueTime > localDateTimeNow) + || x.Status == BorrowRequestStatus.Overdue), cancellationToken); + + if (existedBorrow is not null) + { + // Does not make sense if the same person go up and want to borrow the same document again + // even if the borrow day will be after the due day + if (existedBorrow.Borrower.Id == request.BorrowerId) + { + throw new ConflictException("This document is already requested borrow from the same user."); + } + + if (existedBorrow.Status + is BorrowRequestStatus.Approved + or BorrowRequestStatus.CheckedOut + && LocalDateTime.FromDateTime(request.BorrowFrom) < existedBorrow.DueTime) + { + throw new ConflictException("This document cannot be borrowed."); + } + } + + var entity = new Borrow() + { + Borrower = user, + Document = document, + BorrowTime = LocalDateTime.FromDateTime(request.BorrowFrom), + DueTime = LocalDateTime.FromDateTime(request.BorrowTo), + Reason = request.Reason, + Status = BorrowRequestStatus.Pending, + }; + + var result = await _context.Borrows.AddAsync(entity, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Commands/CancelBorrowRequest.cs b/src/Application/Borrows/Commands/CancelBorrowRequest.cs new file mode 100644 index 00000000..97f111d4 --- /dev/null +++ b/src/Application/Borrows/Commands/CancelBorrowRequest.cs @@ -0,0 +1,51 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Borrows.Commands; + +public class CancelBorrowRequest +{ + public record Command : IRequest + { + public Guid BorrowId { 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 borrowRequest = await _context.Borrows + .Include(x => x.Borrower) + .Include(x => x.Document) + .FirstOrDefaultAsync(x => x.Id == request.BorrowId, cancellationToken); + if (borrowRequest is null) + { + throw new KeyNotFoundException("Borrow request does not exist."); + } + + if (borrowRequest.Status is not (BorrowRequestStatus.Approved or BorrowRequestStatus.Pending)) + { + throw new ConflictException("Request cannot be cancelled."); + } + + borrowRequest.Status = BorrowRequestStatus.Cancelled; + var result = _context.Borrows.Update(borrowRequest); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Commands/CheckoutDocument.cs b/src/Application/Borrows/Commands/CheckoutDocument.cs new file mode 100644 index 00000000..6578225f --- /dev/null +++ b/src/Application/Borrows/Commands/CheckoutDocument.cs @@ -0,0 +1,58 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Borrows.Commands; + +public class CheckoutDocument +{ + public record Command : IRequest + { + public Guid BorrowId { 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 borrowRequest = await _context.Borrows + .Include(x => x.Borrower) + .Include(x => x.Document) + .FirstOrDefaultAsync(x => x.Id == request.BorrowId, cancellationToken); + if (borrowRequest is null) + { + throw new KeyNotFoundException("Borrow request does not exist."); + } + + if (borrowRequest.Document.Status is not DocumentStatus.Available) + { + throw new ConflictException("Document is not available."); + } + + if (borrowRequest.Status is not BorrowRequestStatus.Approved) + { + throw new ConflictException("Request cannot be checked out."); + } + + borrowRequest.Status = BorrowRequestStatus.CheckedOut; + borrowRequest.Document.Status = DocumentStatus.Borrowed; + var result = _context.Borrows.Update(borrowRequest); + _context.Documents.Update(borrowRequest.Document); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Commands/RejectBorrowRequest.cs b/src/Application/Borrows/Commands/RejectBorrowRequest.cs new file mode 100644 index 00000000..e1290e83 --- /dev/null +++ b/src/Application/Borrows/Commands/RejectBorrowRequest.cs @@ -0,0 +1,50 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Borrows.Commands; + +public class RejectBorrowRequest +{ + public record Command : IRequest + { + public Guid BorrowId { 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 borrowRequest = await _context.Borrows + .Include(x => x.Borrower) + .Include(x => x.Document) + .FirstOrDefaultAsync(x => x.Id == request.BorrowId, cancellationToken); + if (borrowRequest is null) + { + throw new KeyNotFoundException("Borrow request does not exist."); + } + + if (borrowRequest.Status is not BorrowRequestStatus.Pending) + { + throw new ConflictException("Request cannot be rejected."); + } + + borrowRequest.Status = BorrowRequestStatus.Rejected; + var result = _context.Borrows.Update(borrowRequest); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Commands/ReturnDocument.cs b/src/Application/Borrows/Commands/ReturnDocument.cs new file mode 100644 index 00000000..918a979d --- /dev/null +++ b/src/Application/Borrows/Commands/ReturnDocument.cs @@ -0,0 +1,61 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Statuses; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Borrows.Commands; + +public class ReturnDocument +{ + public record Command : IRequest + { + 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 borrowRequest = await _context.Borrows + .Include(x => x.Borrower) + .Include(x => x.Document) + .FirstOrDefaultAsync(x => x.Document.Id == request.DocumentId + && x.Status == BorrowRequestStatus.CheckedOut, cancellationToken); + if (borrowRequest is null) + { + throw new KeyNotFoundException("Borrow request does not exist."); + } + + if (borrowRequest.Document.Status is not DocumentStatus.Borrowed) + { + throw new ConflictException("Document is not borrowed."); + } + + if (borrowRequest.Status is not BorrowRequestStatus.CheckedOut) + { + throw new ConflictException("Request cannot be made."); + } + + borrowRequest.Status = BorrowRequestStatus.Returned; + borrowRequest.Document.Status = DocumentStatus.Available; + borrowRequest.ActualReturnTime = LocalDateTime.FromDateTime(DateTime.Now); + var result = _context.Borrows.Update(borrowRequest); + _context.Documents.Update(borrowRequest.Document); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Commands/UpdateBorrow.cs b/src/Application/Borrows/Commands/UpdateBorrow.cs new file mode 100644 index 00000000..fe39c471 --- /dev/null +++ b/src/Application/Borrows/Commands/UpdateBorrow.cs @@ -0,0 +1,104 @@ +using Application.Common.Exceptions; +using Application.Common.Interfaces; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using Domain.Entities.Physical; +using Domain.Statuses; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; +using NodaTime; + +namespace Application.Borrows.Commands; + +public class UpdateBorrow +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Reason) + .MaximumLength(512).WithMessage("Reason cannot exceed 512 characters."); + + RuleFor(x => x.BorrowFrom) + .GreaterThan(DateTime.Now).WithMessage("Borrow date cannot be in the past.") + .Must((command, borrowTime) => borrowTime < command.BorrowTo).WithMessage("Due date cannot be before borrow date."); + + RuleFor(x => x.BorrowTo) + .GreaterThan(DateTime.Now).WithMessage("Due date cannot be in the past."); + } + } + + public record Command : IRequest + { + public Guid BorrowId { get; init; } + public DateTime BorrowFrom { get; init; } + public DateTime BorrowTo { 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 borrowRequest = await _context.Borrows + .Include(x => x.Borrower) + .Include(x => x.Document) + .FirstOrDefaultAsync(x => x.Id == request.BorrowId, cancellationToken); + if (borrowRequest is null) + { + throw new KeyNotFoundException("Borrow request does not exist."); + } + + if (borrowRequest.Status is not BorrowRequestStatus.Pending) + { + throw new ConflictException("Cannot update borrow request."); + } + + if (borrowRequest.Document.Status is DocumentStatus.Lost) + { + throw new ConflictException("Document is lost."); + } + + var localDateTimeNow = LocalDateTime.FromDateTime(DateTime.Now); + var existedBorrow = await _context.Borrows + .Include(x => x.Borrower) + .FirstOrDefaultAsync(x => + x.Document.Id == borrowRequest.Document.Id + && x.Id != borrowRequest.Id + && ((x.DueTime > localDateTimeNow) + || x.Status == BorrowRequestStatus.Overdue), cancellationToken); + + if (existedBorrow is not null) + { + if (existedBorrow.Status + is BorrowRequestStatus.Approved + or BorrowRequestStatus.CheckedOut + && LocalDateTime.FromDateTime(request.BorrowFrom) < existedBorrow.DueTime) + { + throw new ConflictException("This document cannot be borrowed."); + } + } + + borrowRequest.BorrowTime = LocalDateTime.FromDateTime(request.BorrowFrom); + borrowRequest.DueTime = LocalDateTime.FromDateTime(request.BorrowTo); + borrowRequest.Reason = request.Reason; + + var result = _context.Borrows.Update(borrowRequest); + await _context.SaveChangesAsync(cancellationToken); + + return _mapper.Map(result.Entity); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Queries/GetAllBorrowRequestsPaginated.cs b/src/Application/Borrows/Queries/GetAllBorrowRequestsPaginated.cs new file mode 100644 index 00000000..bc5c138e --- /dev/null +++ b/src/Application/Borrows/Queries/GetAllBorrowRequestsPaginated.cs @@ -0,0 +1,94 @@ +using Application.Common.Exceptions; +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using FluentValidation; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Borrows.Queries; + +public class GetAllBorrowRequestsPaginated +{ + public class Validator : AbstractValidator + { + public Validator() + { + RuleLevelCascadeMode = CascadeMode.Stop; + } + } + + public record Query : IRequest> + { + public Guid? DepartmentId { get; init; } + public Guid? DocumentId { get; init; } + public Guid? EmployeeId { 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 borrows = _context.Borrows.AsQueryable(); + + borrows = borrows + .Include(x => x.Borrower) + .Include(x => x.Document) + .ThenInclude(y => y.Department) + .Include(x => x.Document) + .ThenInclude(y => y.Folder) + .ThenInclude(z => z!.Locker) + .ThenInclude(t => t.Room) + .ThenInclude(s => s.Department); + + if (request.DepartmentId is not null) + { + borrows = borrows.Where(x => x.Document.Department!.Id == request.DepartmentId); + } + + if (request.EmployeeId is not null) + { + borrows = borrows.Where(x => x.Borrower.Id == request.EmployeeId); + } + + if (request.DocumentId is not null) + { + borrows = borrows.Where(x => x.Document.Id == request.DocumentId); + } + + var sortBy = request.SortBy; + if (sortBy is null || !sortBy.MatchesPropertyName()) + { + sortBy = nameof(BorrowDto.Status); + } + 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 list = await borrows + .OrderByCustom(sortBy, sortOrder) + .Paginate(pageNumber.Value, sizeNumber.Value) + .ToListAsync(cancellationToken); + + var result = _mapper.Map>(list); + + return new PaginatedList(result, result.Count, pageNumber.Value, sizeNumber.Value); + } + } +} \ No newline at end of file diff --git a/src/Application/Borrows/Queries/GetAllBorrowRequestsSpecificPaginated.cs b/src/Application/Borrows/Queries/GetAllBorrowRequestsSpecificPaginated.cs new file mode 100644 index 00000000..a645079f --- /dev/null +++ b/src/Application/Borrows/Queries/GetAllBorrowRequestsSpecificPaginated.cs @@ -0,0 +1,79 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Models; +using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Application.Borrows.Queries; + +[Obsolete] +public class GetAllBorrowRequestsSpecificPaginated +{ + public record Query : IRequest> + { + public Guid? DocumentId { get; init; } + public Guid? EmployeeId { 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 borrows = _context.Borrows.AsQueryable(); + + borrows = borrows + .Include(x => x.Borrower) + .Include(x => x.Document) + .ThenInclude(y => y.Department) + .Include(x => x.Document) + .ThenInclude(y => y.Folder) + .ThenInclude(z => z!.Locker) + .ThenInclude(t => t.Room) + .ThenInclude(s => s.Department); + + if (request.EmployeeId is not null) + { + borrows = borrows.Where(x => x.Borrower.Id == request.EmployeeId); + } + + if (request.DocumentId is not null) + { + borrows = borrows.Where(x => x.Document.Id == request.DocumentId); + } + + var sortBy = request.SortBy; + if (sortBy is null || !sortBy.MatchesPropertyName()) + { + sortBy = nameof(BorrowDto.Status); + } + 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 list = await borrows + .Paginate(pageNumber.Value, sizeNumber.Value) + .OrderByCustom(sortBy, sortOrder) + .ToListAsync(cancellationToken); + + var result = _mapper.Map>(list); + + return new PaginatedList(result, result.Count, pageNumber.Value, sizeNumber.Value); + } + } +} \ No newline at end of file diff --git a/src/Application/Common/Extensions/QueryableExtensions.cs b/src/Application/Common/Extensions/QueryableExtensions.cs index 2272a400..a670cca7 100644 --- a/src/Application/Common/Extensions/QueryableExtensions.cs +++ b/src/Application/Common/Extensions/QueryableExtensions.cs @@ -23,6 +23,6 @@ public static IQueryable OrderByCustom(this IQueryable Paginate(this IQueryable items, int page, int size) { - return items.Skip((page - 1) * page).Take(size); + return items.Skip((page - 1) * size).Take(size); } } \ No newline at end of file diff --git a/src/Application/Common/Interfaces/ICurrentUserService.cs b/src/Application/Common/Interfaces/ICurrentUserService.cs index ae870b98..0c4aed4b 100644 --- a/src/Application/Common/Interfaces/ICurrentUserService.cs +++ b/src/Application/Common/Interfaces/ICurrentUserService.cs @@ -1,7 +1,12 @@ +using Domain.Entities; + namespace Application.Common.Interfaces; public interface ICurrentUserService { string GetRole(); string? GetDepartment(); + User GetCurrentUser(); + Guid? GetCurrentRoomForStaff(); + Guid? GetCurrentDepartmentForStaff(); } \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Physical/BorrowDto.cs b/src/Application/Common/Models/Dtos/Physical/BorrowDto.cs index bb171be9..49afc176 100644 --- a/src/Application/Common/Models/Dtos/Physical/BorrowDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/BorrowDto.cs @@ -8,18 +8,28 @@ namespace Application.Common.Models.Dtos.Physical; public class BorrowDto : IMapFrom { public Guid Id { get; set; } - public UserDto Borrower { get; set; } - public DocumentDto Document { get; set; } + public Guid BorrowerId { get; set; } + public Guid DocumentId { get; set; } public DateTime BorrowTime { get; set; } public DateTime DueTime { get; set; } - public string Reason { get; set; } + public DateTime ActualReturnTime { get; set; } + public string Reason { get; set; } = null!; + public string Status { get; set; } = null!; public void Mapping(Profile profile) { profile.CreateMap() + .ForMember(dest => dest.BorrowerId, + opt => opt.MapFrom(src => src.Borrower.Id)) + .ForMember(dest => dest.DocumentId, + opt => opt.MapFrom(src => src.Document.Id)) + .ForMember(dest => dest.Status, + opt => opt.MapFrom(src => src.Status.ToString())) .ForMember(dest => dest.BorrowTime, opt => opt.MapFrom(src => src.BorrowTime.ToDateTimeUnspecified())) .ForMember(dest => dest.DueTime, - opt => opt.MapFrom(src => src.DueTime.ToDateTimeUnspecified())); + opt => opt.MapFrom(src => src.DueTime.ToDateTimeUnspecified())) + .ForMember(dest => dest.ActualReturnTime, + opt => opt.MapFrom(src => src.ActualReturnTime.ToDateTimeUnspecified())); } } \ No newline at end of file diff --git a/src/Application/Common/Models/Dtos/Physical/DocumentDto.cs b/src/Application/Common/Models/Dtos/Physical/DocumentDto.cs index 23f0f4ec..994b3ac8 100644 --- a/src/Application/Common/Models/Dtos/Physical/DocumentDto.cs +++ b/src/Application/Common/Models/Dtos/Physical/DocumentDto.cs @@ -1,5 +1,6 @@ using Application.Common.Mappings; using Application.Users.Queries; +using AutoMapper; using Domain.Entities.Physical; namespace Application.Common.Models.Dtos.Physical; @@ -13,4 +14,12 @@ public class DocumentDto : IMapFrom public DepartmentDto? Department { get; set; } public UserDto? Importer { get; set; } public FolderDto? Folder { get; set; } + public string Status { get; set; } + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(dest => dest.Status, + opt => opt.MapFrom(src => src.Status.ToString())); + } } \ No newline at end of file diff --git a/src/Application/Documents/Commands/ImportDocument.cs b/src/Application/Documents/Commands/ImportDocument.cs index 16e9c6a7..d33d4942 100644 --- a/src/Application/Documents/Commands/ImportDocument.cs +++ b/src/Application/Documents/Commands/ImportDocument.cs @@ -3,6 +3,7 @@ using Application.Common.Models.Dtos.Physical; using AutoMapper; using Domain.Entities.Physical; +using Domain.Statuses; using MediatR; using Microsoft.EntityFrameworkCore; @@ -68,7 +69,8 @@ public async Task Handle(Command request, CancellationToken cancell DocumentType = request.DocumentType.Trim(), Importer = importer, Department = importer.Department, - Folder = folder + Folder = folder, + Status = DocumentStatus.Issued, }; var result = await _context.Documents.AddAsync(entity, cancellationToken); diff --git a/src/Application/Documents/Queries/GetAllDocumentsPaginated.cs b/src/Application/Documents/Queries/GetAllDocumentsPaginated.cs index d2d4f38b..de7c13cf 100644 --- a/src/Application/Documents/Queries/GetAllDocumentsPaginated.cs +++ b/src/Application/Documents/Queries/GetAllDocumentsPaginated.cs @@ -124,17 +124,17 @@ public async Task> Handle(Query request, } var sortBy = request.SortBy; - if (sortBy is null || !sortBy.MatchesPropertyName()) + if (sortBy is null || !sortBy.MatchesPropertyName()) { - sortBy = nameof(LockerDto.Id); + sortBy = nameof(DocumentDto.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 list = await documents - .Paginate(pageNumber.Value, sizeNumber.Value) .OrderByCustom(sortBy, sortOrder) + .Paginate(pageNumber.Value, sizeNumber.Value) .ToListAsync(cancellationToken); var result = _mapper.Map>(list); diff --git a/src/Application/Folders/Queries/GetAllFoldersPaginated.cs b/src/Application/Folders/Queries/GetAllFoldersPaginated.cs index 9a4b149f..f98ce633 100644 --- a/src/Application/Folders/Queries/GetAllFoldersPaginated.cs +++ b/src/Application/Folders/Queries/GetAllFoldersPaginated.cs @@ -23,7 +23,7 @@ public Validator() RuleFor(x => x.RoomId) .Must((query, roomId) => roomId is not null || query.LockerId is null); - } + } } public record Query : IRequest> @@ -105,8 +105,8 @@ public async Task> Handle(Query request, CancellationTo var sizeNumber = request.Size is null or <= 0 ? 5 : request.Size; var list = await folders - .Paginate(pageNumber.Value, sizeNumber.Value) .OrderByCustom(sortBy, sortOrder) + .Paginate(pageNumber.Value, sizeNumber.Value) .ToListAsync(cancellationToken); var result = _mapper.Map>(list); diff --git a/src/Application/Lockers/Queries/GetAllLockersPaginated.cs b/src/Application/Lockers/Queries/GetAllLockersPaginated.cs index 8485fffe..5e2a93ff 100644 --- a/src/Application/Lockers/Queries/GetAllLockersPaginated.cs +++ b/src/Application/Lockers/Queries/GetAllLockersPaginated.cs @@ -61,8 +61,8 @@ public async Task> Handle(Query request, CancellationTo var sizeNumber = request.Size is null or <= 0 ? 5 : request.Size; var list = await lockers - .Paginate(pageNumber.Value, sizeNumber.Value) .OrderByCustom(sortBy, sortOrder) + .Paginate(pageNumber.Value, sizeNumber.Value) .ToListAsync(cancellationToken); var result = _mapper.Map>(list); diff --git a/src/Application/Rooms/Queries/GetAllRoomsPaginated.cs b/src/Application/Rooms/Queries/GetAllRoomsPaginated.cs index e261e6b7..31fddbb8 100644 --- a/src/Application/Rooms/Queries/GetAllRoomsPaginated.cs +++ b/src/Application/Rooms/Queries/GetAllRoomsPaginated.cs @@ -54,8 +54,8 @@ public async Task> Handle(Query request, CancellationToke var sizeNumber = request.Size is null or <= 0 ? 5 : request.Size; var list = await rooms - .Paginate(pageNumber.Value, sizeNumber.Value) .OrderByCustom(sortBy, sortOrder) + .Paginate(pageNumber.Value, sizeNumber.Value) .ToListAsync(cancellationToken); var result = _mapper.Map>(list); diff --git a/src/Application/Staffs/Commands/AddStaff.cs b/src/Application/Staffs/Commands/AddStaff.cs index c3415e00..0d78536f 100644 --- a/src/Application/Staffs/Commands/AddStaff.cs +++ b/src/Application/Staffs/Commands/AddStaff.cs @@ -1,3 +1,4 @@ +using Application.Common.Exceptions; using Application.Common.Interfaces; using Application.Common.Models.Dtos.Physical; using AutoMapper; @@ -19,13 +20,13 @@ 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 user = await _context.Users.FirstOrDefaultAsync(x => x.Id == request.UserId, cancellationToken); @@ -36,16 +37,40 @@ public async Task Handle(Command request, CancellationToken cancellati var room = await _context.Rooms.FirstOrDefaultAsync(x => x.Id == request.RoomId, cancellationToken); - var staff = new Staff + if (room is null) + { + throw new KeyNotFoundException("Room does not exist."); + } + + var existedStaff = await _context.Staffs + .Include(x => x.Room) + .Include(x => x.User) + .FirstOrDefaultAsync(x => x.Id == user.Id, cancellationToken); + if (existedStaff is not null) { - Id = user.Id, - User = user, - Room = room - }; - - var result = await _context.Staffs.AddAsync(staff, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - return _mapper.Map(result.Entity); + if (existedStaff.Room is not null) + { + throw new ConflictException("This user is already a staff."); + } + + existedStaff.Room = room; + var result = _context.Staffs.Update(existedStaff); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } + else + { + var staff = new Staff + { + Id = user.Id, + User = user, + Room = room + }; + + var result = await _context.Staffs.AddAsync(staff, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + return _mapper.Map(result.Entity); + } } } } \ No newline at end of file diff --git a/src/Domain/Entities/Physical/Borrow.cs b/src/Domain/Entities/Physical/Borrow.cs index a6f90f9a..8b3076ac 100644 --- a/src/Domain/Entities/Physical/Borrow.cs +++ b/src/Domain/Entities/Physical/Borrow.cs @@ -1,4 +1,5 @@ using Domain.Common; +using Domain.Statuses; using NodaTime; namespace Domain.Entities.Physical; @@ -9,5 +10,7 @@ public class Borrow : BaseEntity public Document Document { get; set; } = null!; public LocalDateTime BorrowTime { get; set; } public LocalDateTime DueTime { get; set; } + public LocalDateTime ActualReturnTime { get; set; } public string Reason { get; set; } = null!; + public BorrowRequestStatus Status { get; set; } } \ No newline at end of file diff --git a/src/Domain/Entities/Physical/Document.cs b/src/Domain/Entities/Physical/Document.cs index 2729d8ea..81313b56 100644 --- a/src/Domain/Entities/Physical/Document.cs +++ b/src/Domain/Entities/Physical/Document.cs @@ -1,4 +1,5 @@ using Domain.Common; +using Domain.Statuses; namespace Domain.Entities.Physical; @@ -10,4 +11,5 @@ public class Document : BaseEntity public Department? Department { get; set; } public User? Importer { get; set; } public Folder? Folder { get; set; } + public DocumentStatus Status { get; set; } } \ No newline at end of file diff --git a/src/Domain/Statuses/BorrowRequestStatus.cs b/src/Domain/Statuses/BorrowRequestStatus.cs new file mode 100644 index 00000000..15c19a0b --- /dev/null +++ b/src/Domain/Statuses/BorrowRequestStatus.cs @@ -0,0 +1,14 @@ +namespace Domain.Statuses; + +public enum BorrowRequestStatus +{ + Approved, + Pending, + Rejected, + Overdue, + Cancelled, + CheckedOut, + Returned, + Lost, + NotProcessable, +} \ No newline at end of file diff --git a/src/Domain/Statuses/DocumentStatus.cs b/src/Domain/Statuses/DocumentStatus.cs new file mode 100644 index 00000000..133c1cd1 --- /dev/null +++ b/src/Domain/Statuses/DocumentStatus.cs @@ -0,0 +1,9 @@ +namespace Domain.Statuses; + +public enum DocumentStatus +{ + Issued, + Available, + Borrowed, + Lost, +} \ No newline at end of file diff --git a/src/Infrastructure/Identity/IdentityService.cs b/src/Infrastructure/Identity/IdentityService.cs index 7f879fdd..1947979e 100644 --- a/src/Infrastructure/Identity/IdentityService.cs +++ b/src/Infrastructure/Identity/IdentityService.cs @@ -185,7 +185,9 @@ public async Task RefreshTokenAsync(string token, string r public async Task<(AuthenticationResult, UserDto)> LoginAsync(string email, string password) { - var user = _context.Users.FirstOrDefault(x => x.Email!.Equals(email)); + var user = _context.Users + .Include(x => x.Department) + .FirstOrDefault(x => x.Email!.Equals(email)); if (user is null || !user.PasswordHash.Equals(SecurityUtil.Hash(password))) { diff --git a/src/Infrastructure/Persistence/Configurations/BorrowConfiguration.cs b/src/Infrastructure/Persistence/Configurations/BorrowConfiguration.cs index 88bd292f..78697f26 100644 --- a/src/Infrastructure/Persistence/Configurations/BorrowConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/BorrowConfiguration.cs @@ -30,5 +30,8 @@ public void Configure(EntityTypeBuilder builder) builder.Property(x => x.Reason) .IsRequired(); + + builder.Property(x => x.Status) + .IsRequired(); } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Configurations/DocumentConfiguration.cs b/src/Infrastructure/Persistence/Configurations/DocumentConfiguration.cs index 79e66f71..4c7033c5 100644 --- a/src/Infrastructure/Persistence/Configurations/DocumentConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/DocumentConfiguration.cs @@ -38,5 +38,8 @@ public void Configure(EntityTypeBuilder builder) .WithMany() .HasForeignKey("ImporterId") .IsRequired(false); + + builder.Property(x => x.Status) + .IsRequired(); } } \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Migrations/00000000000009_RoomMustHaveADepartment.cs b/src/Infrastructure/Persistence/Migrations/00000000000009_RoomMustHaveADepartment.cs deleted file mode 100644 index a9fa4a85..00000000 --- a/src/Infrastructure/Persistence/Migrations/00000000000009_RoomMustHaveADepartment.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Infrastructure.Persistence.Migrations -{ - /// - public partial class RoomMustHaveADepartment : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/src/Infrastructure/Persistence/Migrations/00000000000009_RoomMustHaveADepartment.Designer.cs b/src/Infrastructure/Persistence/Migrations/00000000000010_UpdateBorrow.Designer.cs similarity index 97% rename from src/Infrastructure/Persistence/Migrations/00000000000009_RoomMustHaveADepartment.Designer.cs rename to src/Infrastructure/Persistence/Migrations/00000000000010_UpdateBorrow.Designer.cs index a20e242e..39077769 100644 --- a/src/Infrastructure/Persistence/Migrations/00000000000009_RoomMustHaveADepartment.Designer.cs +++ b/src/Infrastructure/Persistence/Migrations/00000000000010_UpdateBorrow.Designer.cs @@ -13,8 +13,8 @@ namespace Infrastructure.Persistence.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20230528182741_RoomMustHaveADepartment")] - partial class RoomMustHaveADepartment + [Migration("20230603132557_UpdateBorrow")] + partial class UpdateBorrow { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -50,6 +50,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("uuid"); + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + b.Property("BorrowTime") .HasColumnType("timestamp without time zone"); @@ -66,6 +69,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); + b.Property("Status") + .HasColumnType("integer"); + b.HasKey("Id"); b.HasIndex("BorrowerId"); @@ -99,6 +105,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ImporterId") .HasColumnType("uuid"); + b.Property("Status") + .HasColumnType("integer"); + b.Property("Title") .IsRequired() .HasMaxLength(64) diff --git a/src/Infrastructure/Persistence/Migrations/00000000000010_UpdateBorrow.cs b/src/Infrastructure/Persistence/Migrations/00000000000010_UpdateBorrow.cs new file mode 100644 index 00000000..1cf4d397 --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/00000000000010_UpdateBorrow.cs @@ -0,0 +1,124 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace Infrastructure.Persistence.Migrations +{ + /// + public partial class UpdateBorrow : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Departments_Rooms_RoomId", + table: "Departments"); + + migrationBuilder.DropIndex( + name: "IX_Departments_RoomId", + table: "Departments"); + + migrationBuilder.DropColumn( + name: "RoomId", + table: "Departments"); + + migrationBuilder.AlterColumn( + name: "DepartmentId", + table: "Rooms", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "Status", + table: "Documents", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "ActualReturnTime", + table: "Borrows", + type: "timestamp without time zone", + nullable: false, + defaultValue: new NodaTime.LocalDateTime(1, 1, 1, 0, 0)); + + migrationBuilder.AddColumn( + name: "Status", + table: "Borrows", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateIndex( + name: "IX_Rooms_DepartmentId", + table: "Rooms", + column: "DepartmentId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_Rooms_Departments_DepartmentId", + table: "Rooms", + column: "DepartmentId", + principalTable: "Departments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Rooms_Departments_DepartmentId", + table: "Rooms"); + + migrationBuilder.DropIndex( + name: "IX_Rooms_DepartmentId", + table: "Rooms"); + + migrationBuilder.DropColumn( + name: "Status", + table: "Documents"); + + migrationBuilder.DropColumn( + name: "ActualReturnTime", + table: "Borrows"); + + migrationBuilder.DropColumn( + name: "Status", + table: "Borrows"); + + migrationBuilder.AlterColumn( + name: "DepartmentId", + table: "Rooms", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddColumn( + name: "RoomId", + table: "Departments", + type: "uuid", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Departments_RoomId", + table: "Departments", + column: "RoomId", + unique: true); + + migrationBuilder.AddForeignKey( + name: "FK_Departments_Rooms_RoomId", + table: "Departments", + column: "RoomId", + principalTable: "Rooms", + principalColumn: "Id"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index 49740741..26a343b3 100644 --- a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -47,6 +47,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("uuid"); + b.Property("ActualReturnTime") + .HasColumnType("timestamp without time zone"); + b.Property("BorrowTime") .HasColumnType("timestamp without time zone"); @@ -63,6 +66,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); + b.Property("Status") + .HasColumnType("integer"); + b.HasKey("Id"); b.HasIndex("BorrowerId"); @@ -96,6 +102,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ImporterId") .HasColumnType("uuid"); + b.Property("Status") + .HasColumnType("integer"); + b.Property("Title") .IsRequired() .HasMaxLength(64) diff --git a/src/Infrastructure/Services/MailService.cs b/src/Infrastructure/Services/MailService.cs index 8357655f..32feba59 100644 --- a/src/Infrastructure/Services/MailService.cs +++ b/src/Infrastructure/Services/MailService.cs @@ -51,7 +51,6 @@ public bool SendResetPasswordHtmlMail(string userEmail, string password) request.AddHeader("Content-Type", "application/json"); request.AddParameter("application/json", json, ParameterType.RequestBody); var response = client.Execute(request); - return response.IsSuccessStatusCode; } } \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Folders/Commands/DisableFolderTests.cs b/tests/Application.Tests.Integration/Folders/Commands/DisableFolderTests.cs index 02a9e97b..cf2628d7 100644 --- a/tests/Application.Tests.Integration/Folders/Commands/DisableFolderTests.cs +++ b/tests/Application.Tests.Integration/Folders/Commands/DisableFolderTests.cs @@ -1,5 +1,6 @@ using Application.Common.Exceptions; using Application.Folders.Commands; +using Domain.Entities; using Domain.Entities.Physical; using FluentAssertions; using Xunit; @@ -16,36 +17,12 @@ public DisableFolderTests(CustomApiFactory apiFactory) : base(apiFactory) public async Task ShouldDisableFolder_WhenFolderHaveNoDocument() { // Arrange - var room = new Room() - { - Id = Guid.NewGuid(), - Capacity = 24, - IsAvailable = true, - Name = "Kamito's room", - NumberOfLockers = 1, - }; - - var locker = new Locker() - { - Id = Guid.NewGuid(), - Capacity = 47, - Name = "fqwlkjdb sajdbqwk", - IsAvailable = true, - Room = room, - NumberOfFolders = 1, - }; - - var folder = new Folder() - { - Id = Guid.NewGuid(), - Capacity = 12, - IsAvailable = true, - Name = "A Random name", - NumberOfDocuments = 0, - Locker = locker - }; + var department = CreateDepartment(); + var folder = CreateFolder(); + var locker = CreateLocker(folder); + var room = CreateRoom(department, locker); + await AddAsync(room); - await AddAsync(folder); var disableFolderCommand = new DisableFolder.Command() { FolderId = folder.Id @@ -61,6 +38,7 @@ public async Task ShouldDisableFolder_WhenFolderHaveNoDocument() Remove(folder); Remove(locker); Remove(room); + Remove(await FindAsync(department.Id)); } [Fact] @@ -84,35 +62,12 @@ await result.Should().ThrowAsync() public async Task ShouldThrowInvalidOperationException_WhenFolderIsAlreadyDisabled() { // Arrange - var room = new Room() - { - Id = Guid.NewGuid(), - Capacity = 24, - IsAvailable = true, - Name = "Kamito's room!", - NumberOfLockers = 1, - }; - - var locker = new Locker() - { - Id = Guid.NewGuid(), - Capacity = 47, - Name = "fqwlkjdb sajdbqwk!", - IsAvailable = true, - Room = room, - NumberOfFolders = 1, - }; - - var folder = new Folder() - { - Id = Guid.NewGuid(), - Capacity = 12, - IsAvailable = false, - Name = "A Random name!", - NumberOfDocuments = 0, - Locker = locker - }; - await AddAsync(folder); + var department = CreateDepartment(); + var folder = CreateFolder(); + folder.IsAvailable = false; + var locker = CreateLocker(folder); + var room = CreateRoom(department, locker); + await AddAsync(room); var disableFolderCommand = new DisableFolder.Command() { FolderId = folder.Id @@ -129,50 +84,19 @@ await result.Should().ThrowAsync() Remove(folder); Remove(locker); Remove(room); + Remove(await FindAsync(department.Id)); } [Fact] public async Task ShouldThrowInvalidOperationException_WhenFolderHasDocuments() { // Arrange - var room = new Room() - { - Id = Guid.NewGuid(), - Capacity = 24, - IsAvailable = true, - Name = "Kamito's room!!safqwf!!", - NumberOfLockers = 1, - }; - - var locker = new Locker() - { - Id = Guid.NewGuid(), - Capacity = 47, - Name = "fqwlkjdb sawfqfw wfqwfjdbqwk!", - IsAvailable = true, - Room = room, - NumberOfFolders = 1, - }; - - var folder = new Folder() - { - Id = Guid.NewGuid(), - Capacity = 12, - IsAvailable = true, - Name = "A Randasfwqfawfqom name!", - NumberOfDocuments = 1, - Locker = locker - }; - - var document = new Document() - { - DocumentType = "fqwkfwqbfk", - Id = Guid.NewGuid(), - Title = "wjqk ljfqwjlf qwkhjf ;qikwf", - Folder = folder - }; - - await AddAsync(document); + var department = CreateDepartment(); + var document = CreateNDocuments(1).First(); + var folder = CreateFolder(document); + var locker = CreateLocker(folder); + var room = CreateRoom(department, locker); + await AddAsync(room); var disableFolderCommand = new DisableFolder.Command() { FolderId = folder.Id @@ -190,5 +114,6 @@ await result.Should().ThrowAsync() Remove(folder); Remove(locker); Remove(room); + Remove(await FindAsync(department.Id)); } } \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Lockers/Commands/AddLockerTests.cs b/tests/Application.Tests.Integration/Lockers/Commands/AddLockerTests.cs index 2da1413f..b3ff5025 100644 --- a/tests/Application.Tests.Integration/Lockers/Commands/AddLockerTests.cs +++ b/tests/Application.Tests.Integration/Lockers/Commands/AddLockerTests.cs @@ -1,6 +1,7 @@ using Application.Common.Exceptions; using Application.Lockers.Commands; using Bogus; +using Domain.Entities; using Domain.Entities.Physical; using Domain.Exceptions; using FluentAssertions; @@ -18,15 +19,8 @@ public AddLockerTests(CustomApiFactory apiFactory) : base(apiFactory) public async Task ShouldReturnLocker_WhenCreateDetailsAreValid() { // Arrange - var room = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Name.JobArea(), - Description = new Faker().Lorem.Sentence(), - Capacity = 3, - IsAvailable = true, - NumberOfLockers = 0, - }; + var department = CreateDepartment(); + var room = CreateRoom(department); await AddAsync(room); @@ -56,21 +50,15 @@ public async Task ShouldReturnLocker_WhenCreateDetailsAreValid() // Cleanup var roomEntity = await FindAsync(room.Id); Remove(roomEntity); + Remove(await FindAsync(department.Id)); } [Fact] public async Task ShouldThrowConflictException_WhenLockerAlreadyExistsInTheSameRoom() { // Arrange - var room = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Name.JobArea(), - Description = new Faker().Lorem.Sentence(), - Capacity = 3, - IsAvailable = true, - NumberOfLockers = 0, - }; + var department = CreateDepartment(); + var room = CreateRoom(department); await AddAsync(room); @@ -93,33 +81,20 @@ public async Task ShouldThrowConflictException_WhenLockerAlreadyExistsInTheSameR // Cleanup var roomEntity = await FindAsync(room.Id); Remove(roomEntity); + Remove(await FindAsync(department.Id)); } [Fact] public async Task ShouldReturnLocker_WhenLockersHasSameNameButInDifferentRooms() { // Arrange - var room1 = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Name.JobArea(), - Description = new Faker().Lorem.Sentence(), - Capacity = 3, - IsAvailable = true, - NumberOfLockers = 0, - }; + var department1 = CreateDepartment(); + var room1 = CreateRoom(department1); await AddAsync(room1); - var room2 = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Name.JobArea(), - Description = new Faker().Lorem.Sentence(), - Capacity = 3, - IsAvailable = true, - NumberOfLockers = 0, - }; + var department2 = CreateDepartment(); + var room2 = CreateRoom(department2); await AddAsync(room2); @@ -160,22 +135,17 @@ public async Task ShouldReturnLocker_WhenLockersHasSameNameButInDifferentRooms() var room2Entity = await FindAsync(room2.Id); Remove(room1Entity); Remove(room2Entity); + Remove(await FindAsync(department1.Id)); + Remove(await FindAsync(department2.Id)); } [Fact] public async Task ShouldThrowLimitExceededException_WhenGoingOverCapacity() { // Arrange - var room = new Room() - { - Id = Guid.NewGuid(), - Name = new Faker().Name.JobArea(), - Description = new Faker().Lorem.Sentence(), - Capacity = 1, - IsAvailable = true, - NumberOfLockers = 0, - }; - + var department = CreateDepartment(); + var room = CreateRoom(department); + room.Capacity = 1; await AddAsync(room); var addLockerCommand = new AddLocker.Command() @@ -205,5 +175,6 @@ public async Task ShouldThrowLimitExceededException_WhenGoingOverCapacity() // Cleanup var roomEntity = await FindAsync(room.Id); Remove(roomEntity); + Remove(await FindAsync(department.Id)); } } \ No newline at end of file