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();