diff --git a/lib/Common/Extensions/ExpressionExtension.cs b/lib/Common/Extensions/ExpressionExtension.cs new file mode 100644 index 0000000..c915c09 --- /dev/null +++ b/lib/Common/Extensions/ExpressionExtension.cs @@ -0,0 +1,12 @@ +using System; + +namespace Common.Extensions +{ + public static class ExpressionExtension + { + public static bool Between(this DateTime @this, DateTime left, DateTime right) + { + return left <= @this && @this <= right; + } + } +} diff --git a/lib/Common/Extensions/NullAwareExtension.cs b/lib/Common/Extensions/NullAwareExtension.cs deleted file mode 100644 index 79f8a25..0000000 --- a/lib/Common/Extensions/NullAwareExtension.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; - -namespace Common.Extensions -{ - public static class NullAwareExtension - { - /// - /// Если object null, то выполнит void-action, и пойдет дальше. В action можно бросать исключения. - /// - public static void IfIsNull(this object @object, Action action) - { - if (null == @object) action.Invoke(); - } - - /// - /// Если object НЕ null, то выполнит void-action, и пойдет дальше. В action можно бросать исключения. - /// - public static void IfNotNull(this object @object, Action action) - { - if (null != @object) action.Invoke(); - } - - /// - /// Если object null, то выполнится action, который должен бросить исключение. Если action не бросает исключение, то - /// вручную бросится NullReferenceException. - /// - /// - public static TResult AssertNull(this TResult @object, Action action) - { - if (null == @object) - { - action.Invoke(); - throw new NullReferenceException(); - } - - return @object; - } - } -} \ No newline at end of file diff --git a/src/Application/Application.csproj b/src/Application/Application.csproj index 00bf2f0..c304cf2 100644 --- a/src/Application/Application.csproj +++ b/src/Application/Application.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Application/CQS/Reservation/Command/CreateReservationCommand.cs b/src/Application/CQS/Reservation/Command/CreateReservationCommand.cs new file mode 100644 index 0000000..8b8a59f --- /dev/null +++ b/src/Application/CQS/Reservation/Command/CreateReservationCommand.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Application.CQS.Reservation.Exception; +using Common.Extensions; +using Domain; +using Domain.Entities; +using JetBrains.Annotations; +using NHibernate.Linq; + +namespace Application.CQS.Reservation.Command +{ + public class CreateReservationCommand : IUserAware + { + public UserEntity? CurrentUser { get; set; } + private IRepository ReservationRepository { get; } + private IRepository RoomRepository { get; } + private IRepository ServiceRepository { get; } + + public CreateReservationCommand( + IRepository reservationRepository, + IRepository roomRepository, + IRepository serviceRepository + ) + { + ReservationRepository = reservationRepository; + RoomRepository = roomRepository; + ServiceRepository = serviceRepository; + } + + public async Task ExecuteAsync(ReservationInput input) + { + AssertRentDatesValid(input); + await AssertRentDatesNotBusyAsync(input); + + var services = await ServiceRepository.FindAll() + .Where(service => input.ServiceIds.Contains(service.Id)) + .ToListAsync(); + + var room = await RoomRepository.GetAsync(input.RoomId); + + var reservation = new ReservationEntity(CurrentUser!, room, input.ReservedFrom, input.ReservedTo, services); + + await ReservationRepository.SaveAndFlushAsync(reservation); + } + + [AssertionMethod] + private void AssertRentDatesValid(ReservationInput input) + { + if (DateTime.Now > input.ReservedTo) + { + throw CreateReservationException.DateToCantBeInPast(); + } + + if (input.ReservedFrom <= input.ReservedTo) + { + throw CreateReservationException.InvalidDateValues(); + } + } + + [AssertionMethod] + private async Task AssertRentDatesNotBusyAsync(ReservationInput input) + { + var reservations = ReservationRepository + .FindAll() + .Where(r => input.RoomId == r.Room.Id + && (input.ReservedFrom.Between(r.ReservedFrom, r.ReservedTo) + || input.ReservedTo.Between(r.ReservedFrom, r.ReservedTo))); + + if (0 != await reservations.CountAsync()) + { + throw CreateReservationException.DatesAreBusy(); + } + } + } +} diff --git a/src/Application/CQS/Reservation/Command/DeleteReservationCommand.cs b/src/Application/CQS/Reservation/Command/DeleteReservationCommand.cs index 1834eac..90d4890 100644 --- a/src/Application/CQS/Reservation/Command/DeleteReservationCommand.cs +++ b/src/Application/CQS/Reservation/Command/DeleteReservationCommand.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Application.CQS.Reservation.Exception; using Domain; using Domain.Entities; @@ -29,7 +30,7 @@ public async Task ExecuteAsync(Guid reservationId) throw ReservationException.AlreadyExpired(); } - await ReservationRepository.DeleteAsync(reservation); + await ReservationRepository.DeleteAndFlushAsync(reservation); } } } diff --git a/src/Application/CQS/Reservation/Exception/CreateReservationException.cs b/src/Application/CQS/Reservation/Exception/CreateReservationException.cs new file mode 100644 index 0000000..5387b65 --- /dev/null +++ b/src/Application/CQS/Reservation/Exception/CreateReservationException.cs @@ -0,0 +1,24 @@ +namespace Application.CQS.Reservation.Exception +{ + public class CreateReservationException : System.Exception + { + public CreateReservationException(string message): base(message) + { + } + + public static CreateReservationException DateToCantBeInPast() + { + return new CreateReservationException("Reservatoin date 'From' can't be in past."); + } + + public static CreateReservationException InvalidDateValues() + { + return new CreateReservationException("Reservation date 'From' must me smaller than date 'To'."); + } + + public static CreateReservationException DatesAreBusy() + { + return new CreateReservationException("Specified rent dates are busy."); + } + } +} diff --git a/src/Application/CQS/Reservation/ReservationException.cs b/src/Application/CQS/Reservation/Exception/DeleteReservationException.cs similarity index 90% rename from src/Application/CQS/Reservation/ReservationException.cs rename to src/Application/CQS/Reservation/Exception/DeleteReservationException.cs index dd15420..ae730f9 100644 --- a/src/Application/CQS/Reservation/ReservationException.cs +++ b/src/Application/CQS/Reservation/Exception/DeleteReservationException.cs @@ -1,6 +1,4 @@ -using System; - -namespace Application.CQS.Reservation +namespace Application.CQS.Reservation.Exception { public class ReservationException : System.Exception { diff --git a/src/Application/CQS/Reservation/ReservationInput.cs b/src/Application/CQS/Reservation/ReservationInput.cs new file mode 100644 index 0000000..92fbec4 --- /dev/null +++ b/src/Application/CQS/Reservation/ReservationInput.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace Application.CQS.Reservation +{ + public class ReservationInput + { + public Guid RoomId { get; set; } + public DateTime ReservedFrom { get; set; } + public DateTime ReservedTo { get; set; } + public IEnumerable ServiceIds { get; set; } + + public ReservationInput(Guid roomId, DateTime reservedFrom, DateTime reservedTo, IEnumerable serviceIds) + { + RoomId = roomId; + ReservedFrom = reservedFrom; + ReservedTo = reservedTo; + ServiceIds = serviceIds; + } + } +} diff --git a/src/Domain/Exceptions/ReservationException.cs b/src/Domain/Exceptions/ReservationException.cs index cc95003..af879c4 100644 --- a/src/Domain/Exceptions/ReservationException.cs +++ b/src/Domain/Exceptions/ReservationException.cs @@ -10,9 +10,14 @@ public ReservationException(string message): base(message) public static void AssertDatesValid(DateTime from, DateTime to) { + if (to < DateTime.Now) + { + throw new ReservationException("Reservation date 'from' can't be in past."); + } + if (from >= to) { - throw new ReservationException($"Reservation date 'from' must be greater than date 'to'."); + throw new ReservationException("Reservation date 'from' must be greater than date 'to'."); } } } diff --git a/src/Domain/IRepository.cs b/src/Domain/IRepository.cs index cb39eb8..1f02aab 100644 --- a/src/Domain/IRepository.cs +++ b/src/Domain/IRepository.cs @@ -11,8 +11,8 @@ public interface IRepository where TEntity : class TEntity Get(object id); Task GetAsync(object id); - void Delete(object id); - Task DeleteAsync(object id); + void DeleteAndFlush(object id); + Task DeleteAndFlushAsync(object id); IQueryable FindAll(); } diff --git a/src/Infrastructure/NHibernate/Repository/Repository.cs b/src/Infrastructure/NHibernate/Repository/Repository.cs index 8230377..662ebc3 100644 --- a/src/Infrastructure/NHibernate/Repository/Repository.cs +++ b/src/Infrastructure/NHibernate/Repository/Repository.cs @@ -48,16 +48,23 @@ public TEntity Get(object id) public async Task GetAsync(object id) { - return await Session.GetAsync(id); + var entity = await Session.GetAsync(id); + + if (null == entity) + { + throw new EntityNotFoundException(id); + } + + return entity; } - public void Delete(object id) + public void DeleteAndFlush(object id) { Session.Delete(id); Session.Flush(); } - public async Task DeleteAsync(object id) + public async Task DeleteAndFlushAsync(object id) { await Session.DeleteAsync(id); await Session.FlushAsync();