public async Task <PaidTimeOffRequestValidationResult> Handle(ValidateRequestedPaidTimeOffHoursQuery request, CancellationToken cancellationToken) { var viewModel = request.ValidationRequest; var tentativeRequest = mapper.Map(viewModel); var today = DateTime.Today; var parms = new { request.AspNetUsersId, viewModel.EndDate, viewModel.StartDate, viewModel.ForEmployeeId, viewModel.HoursRequested, viewModel.TenantId }; // TODO: add domain validation/authorization if submitting on behalf of another employee (i.e. manager is submitting). var paidTimeOffPolicy = await facade.QueryFirstOrDefaultAsync <PaidTimeOffPolicy>(@" SELECT TOP 1 p.[Id] ,p.[AllowsUnlimitedPto] ,p.[EmployeeLevel] ,p.[IsDefaultForEmployeeLevel] ,p.[MaxPtoHours] ,p.[Name] ,p.[PtoAccrualRate] FROM PaidTimeOffPolicies p WITH(NOLOCK) JOIN Employees e WITH(NOLOCK) ON e.PaidTimeOffPolicyId = p.Id AND e.TenantId = p.TenantId WHERE ((@ForEmployeeId IS NULL AND e.AspNetUsersId = @AspNetUsersId) OR e.Id = @ForEmployeeId) AND e.TenantId = @TenantId ", parms, cancellationToken : cancellationToken); if (paidTimeOffPolicy == null) { throw new NotFoundException("PTO policy not found or invalid."); } var existingRequests = await facade.QueryAsync <PaidTimeOffRequest>(@" SELECT p.[Id] ,p.[ApprovalStatus] ,p.[EndDate] ,p.[ForEmployeeId] ,p.[HoursRequested] ,p.[StartDate] ,p.[Paid] ,p.[SubmittedById] FROM [PaidTimeOffRequests] p WITH(NOLOCK) JOIN Employees e WITH(NOLOCK) ON e.Id = p.ForEmployeeId AND e.TenantId = p.TenantId WHERE ((@ForEmployeeId IS NULL AND e.AspNetUsersId = @AspNetUsersId) OR e.Id = @ForEmployeeId) AND e.TenantId = @TenantId ", parms, cancellationToken : cancellationToken); return(paidTimeOffRequestService.ValidatePaidTimeOffRequest(tentativeRequest, existingRequests, paidTimeOffPolicy, today)); }
public async Task <PaidTimeOffRequestValidationResult> Handle(ValidateRequestedPaidTimeOffHoursQuery request, CancellationToken cancellationToken) { var tentativeRequest = mapper.Map(request.ValidationRequest); var today = DateTime.Today; var paidTimeOffPolicy = await facade.QueryFirstOrDefaultAsync <PaidTimeOffPolicy>(@" SELECT TOP 1 p.[Id] ,p.[AllowsUnlimitedPto] ,p.[EmployeeLevel] ,p.[IsDefaultForEmployeeLevel] ,p.[MaxPtoHours] ,p.[Name] ,p.[PtoAccrualRate] FROM PaidTimeOffPolicies p WITH(NOLOCK) JOIN Employees e WITH(NOLOCK) ON e.PaidTimeOffPolicyId = p.Id AND e.TenantId = p.TenantId WHERE e.Id = @ForEmployeeId AND e.TenantId = @TenantId ", request.ValidationRequest, cancellationToken : cancellationToken); if (paidTimeOffPolicy == null) { throw new NotFoundException("PTO policy not found or invalid."); } var existingRequests = await facade.QueryAsync <PaidTimeOffRequest>(@" SELECT [Id] ,[ApprovalStatus] ,[EndDate] ,[ForEmployeeId] ,[HoursRequested] ,[StartDate] ,[Paid] ,[SubmittedById] FROM [PaidTimeOffRequests] WITH(NOLOCK) WHERE @ForEmployeeId = @ForEmployeeId AND TenantId = @TenantId ", request.ValidationRequest, cancellationToken : cancellationToken); return(paidTimeOffRequestService.ValidatePaidTimeOffRequest(tentativeRequest, existingRequests, paidTimeOffPolicy, today)); }
public async Task <SubmitNewPaidTimeOffRequestViewModel> Handle(SubmitNewPaidTimeOffRequestCommand request, CancellationToken cancellationToken) { var today = DateTime.Today; // PRESENTATION/APPLICATION LAYER var timeOffRequestViewModel = request.PaidTimeOffRequest; timeOffRequestViewModel.StartDate = timeOffRequestViewModel.StartDate.Date; timeOffRequestViewModel.EndDate = timeOffRequestViewModel.EndDate.Date; // PERSISTENCE LAYER var submittedByEntity = await facade.QueryFirstOrDefaultAsync <EmployeeEntity>(@"SELECT TOP 1 * FROM Employees WITH(NOLOCK) WHERE AspNetUsersId = @AspNetUsersId AND TenantId = @TenantId", new { request.AspNetUsersId, request.PaidTimeOffRequest.TenantId }); var forEmployeeEntity = timeOffRequestViewModel.ForEmployeeId == null? context.Employees.Include(e => e.PaidTimeOffPolicy).Include(e => e.ForPaidTimeOffRequests).FirstOrDefault(e => e.AspNetUsersId == request.AspNetUsersId && e.TenantId == request.PaidTimeOffRequest.TenantId) : context.Employees.Include(e => e.PaidTimeOffPolicy).Include(e => e.ForPaidTimeOffRequests).FirstOrDefault(e => e.Id == timeOffRequestViewModel.ForEmployeeId && e.TenantId == request.PaidTimeOffRequest.TenantId) ; // TODO: if submitting on behalf of another employee, use the domain layer to validate that they have the privileges to do so. if ( forEmployeeEntity == null || submittedByEntity == null) { throw new ApplicationLayerException(@"Unable to query employees while trying to create time off request aggregate."); } // DOMAIN LAYER var forEmployee = mapper.MapDbEntityToDomainEntity <EmployeeEntity, Employee>(forEmployeeEntity); var submittedByEmployee = mapper.MapDbEntityToDomainEntity <EmployeeEntity, Employee>(submittedByEntity); var paidTimeOffPolicy = mapper.MapDbEntityToDomainEntity <PaidTimeOffPolicyEntity, PaidTimeOffPolicy>(forEmployeeEntity.PaidTimeOffPolicy); var existingRequests = (from req in forEmployeeEntity.ForPaidTimeOffRequests select mapper.MapDbEntityToDomainEntity <PaidTimeOffRequestEntity, PaidTimeOffRequest>(req)).ToList(); // Build up the Domain aggregate entity so that complex business logic can be executed against it. In an enterprise (non-demo) solution this // would likely involve special rules involving accrued hours, whether the company allows going negative in PTO hours, managerial overrides, // etc. The point is that the entities, and the logic which operates against them, are separate from view models and database persistence models. var submittedRequest = mapper.MapViewModelToDomainEntity <SubmitNewPaidTimeOffRequestViewModel, PaidTimeOffRequest>(request.PaidTimeOffRequest) .WithForEmployee(forEmployee) .WithSubmittedBy(submittedByEmployee) .WithPaidTimeOffPolicy(paidTimeOffPolicy); // Ensure the aggregate is in a valid state with which to perform business logic. submittedRequest.ValidateAggregate(); // Perform basic validation against the request to make sure that the employee is OK to submit this paid time off request. Once again, the logic // inside the service is naive and overly-simplistic. A real-life solution would be much more involved. var validationResult = paidTimeOffRequestService.ValidatePaidTimeOffRequest(submittedRequest, existingRequests, paidTimeOffPolicy, today); timeOffRequestViewModel.Result = validationResult; if (validationResult != PaidTimeOffRequestValidationResult.OK) { return(timeOffRequestViewModel); } // Submit the time off request (changes Domain state and creates event). submittedRequest = submittedRequest.Submit(); // PERSISTENCE LAYER var paidTimeOffRequestEntity = mapper.MapDomainEntityToDbEntity <PaidTimeOffRequest, PaidTimeOffRequestEntity>(submittedRequest); paidTimeOffRequestEntity.TenantId = timeOffRequestViewModel.TenantId; await context.PaidTimeOffRequests.AddAsync(paidTimeOffRequestEntity, cancellationToken); await context.SaveChangesAsync(cancellationToken); // PRESENTATION LAYER request.PaidTimeOffRequest.CreatedPaidTimeOffRequest = mapper.MapDomainEntityToViewModel <PaidTimeOffRequest, PaidTimeOffRequestViewModel>(submittedRequest); // DISPATCH DOMAIN EVENTS await submittedRequest.DispatchDomainEventsAsync(); return(request.PaidTimeOffRequest); }