예제 #1
0
            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);
            }