private static void PrepareCareChargesModel(CareChargesCreateDomain request, CarePackage package)
        {
            foreach (var careCharge in request.CareCharges)
            {
                careCharge.CarePackageId = package.Id;
            }

            var coreCost = package.GetCoreCostDetail();

            // Limit ongoing charges if package interval is finite
            request.CareCharges = request.CareCharges.OrderBy(cc => cc.SubType).ToList();

            if (request.CareCharges.Last().EndDate is null && coreCost.EndDate != null)
            {
                request.CareCharges.Last().EndDate = coreCost.EndDate;
            }

            // if requested 1-12 charge covers period of provisional charge, 1-12 will replace provisional
            var provisionalCharge  = request.CareCharges.SingleOrDefault(cc => cc.SubType is ReclaimSubType.CareChargeProvisional);
            var first12WeeksCharge = request.CareCharges.SingleOrDefault(cc => cc.SubType is ReclaimSubType.CareCharge1To12Weeks);

            if (first12WeeksCharge != null && first12WeeksCharge.StartDate.Date <= provisionalCharge?.StartDate.Date)
            {
                provisionalCharge.Status = ReclaimStatus.Cancelled;
            }
        }
        private void ReplaceUpdatedCareCharges(CareChargesCreateDomain request, CarePackage package)
        {
            foreach (var requestedCharge in request.CareCharges)
            {
                // if existing charge date / cost / collector is changed, cancel it and create a new one instead.
                if (!requestedCharge.Id.IsEmpty())
                {
                    var existingCharge = package.Reclaims.Single(cc => cc.Id == requestedCharge.Id);

                    if (CareChargeHasChanged(existingCharge, requestedCharge) && requestedCharge.Status != ReclaimStatus.Cancelled)
                    {
                        existingCharge.Status = ReclaimStatus.Cancelled;
                        requestedCharge.Id    = null;

                        package.Reclaims.Add(requestedCharge.ToEntity());
                    }
                    else
                    {
                        _mapper.Map(requestedCharge, existingCharge);
                    }
                }
                else
                {
                    package.Reclaims.Add(requestedCharge.ToEntity());
                }
            }
        }
        private void VerifyFailedRequest(CareChargesCreateDomain request, string message)
        {
            _useCase
            .Invoking(async useCase => await useCase.ExecuteAsync(_package.Id, request))
            .Should().Throw <ApiException>()
            .Where(ex => ex.Message.Contains(message));

            _dbManager.Verify(db => db.SaveAsync(It.IsAny <string>()), Times.Never);
        }
        private static void ValidateCareChargesModel(CareChargesCreateDomain request, CarePackage package)
        {
            Debug.Assert(ReclaimSubType.CareChargeProvisional == ReclaimSubType.CareCharge1To12Weeks - 1);
            Debug.Assert(ReclaimSubType.CareCharge1To12Weeks == ReclaimSubType.CareCharge13PlusWeeks - 1);

            var coreCost = package.GetCoreCostDetail();

            var requestedCareCharges = request.CareCharges
                                       .Where(c => c.Status != ReclaimStatus.Cancelled)
                                       .OrderBy(c => c.SubType).ToList();

            // Care charges should follow each other without gaps
            for (var i = 1; i < requestedCareCharges.Count; i++)
            {
                var currentCharge  = requestedCareCharges[i];
                var previousCharge = requestedCareCharges[i - 1];

                if (!package.IsMigrated && currentCharge.SubType != previousCharge.SubType + 1)
                {
                    throw new ApiException($"{GetShortName(previousCharge.SubType)} care charge must be followed by" +
                                           $" {GetShortName(previousCharge.SubType + 1)}", HttpStatusCode.BadRequest);
                }

                var expectedEndDate = currentCharge.StartDate.Date.AddDays(-1);
                if (previousCharge.EndDate is null)
                {
                    throw new ApiException($"{GetShortName(previousCharge.SubType)} care charge must have an end date one day before " +
                                           $"{GetShortName(currentCharge.SubType)} start: {expectedEndDate:yyy-MM-dd}", HttpStatusCode.BadRequest);
                }

                var expectedStartDate = previousCharge.EndDate.Value.Date.AddDays(1);
                if (currentCharge.StartDate.Date != expectedStartDate)
                {
                    throw new ApiException($"{GetShortName(currentCharge.SubType)} care charge must start one day after " +
                                           $"{GetShortName(previousCharge.SubType)} care charge end: {expectedStartDate:yyy-MM-dd}", HttpStatusCode.BadRequest);
                }
            }

            // First care charge must be started at package start date
            if (requestedCareCharges.First()?.StartDate.Date < coreCost.StartDate.Date)
            {
                throw new ApiException("First care charge start date must be greater or equal to package start date " +
                                       $"{coreCost.StartDate:yyyy-MM-dd}", HttpStatusCode.BadRequest);
            }

            // Last care charge end date must not exceed package end date (if any)
            if (coreCost.EndDate.HasValue && requestedCareCharges.Last().EndDate?.Date > coreCost.EndDate.Value.Date)
            {
                throw new ApiException("Last care charge end date expected to be less than or equal " +
                                       $"to {coreCost.EndDate.Value:yyyy-MM-dd}", HttpStatusCode.BadRequest);
            }

            ValidateCareChargeBounds(requestedCareCharges);
        }
        private static void ValidateRequestIntegrity(CareChargesCreateDomain request, CarePackage package)
        {
            var existingCareCharges = package.Reclaims
                                      .Where(r => r.Type is ReclaimType.CareCharge &&
                                             r.Status.In(ReclaimStatus.Pending, ReclaimStatus.Active, ReclaimStatus.Ended))
                                      .ToList();

            // each existing care charge should be presented in upsert request
            var missedCareCharges = existingCareCharges
                                    .Where(existingCharge => !request.CareCharges.Any(
                                               requestedCharge =>
                                               requestedCharge.SubType == existingCharge.SubType &&
                                               requestedCharge.Id == existingCharge.Id))
                                    .Select(existingCareCharge => GetShortName(existingCareCharge.SubType))
                                    .ToList();

            if (missedCareCharges.Count > 0)
            {
                throw new ApiException($"Care charges {String.Join(", ", missedCareCharges)} " +
                                       "must present in the request with valid Id", HttpStatusCode.BadRequest);
            }

            // each requested care charge with non-empty Id should have matching DB record
            var unknownCareCharges = request.CareCharges
                                     .Where(requestedCharge =>
                                            !requestedCharge.Id.IsEmpty() &&
                                            !existingCareCharges.Any(existingCharge => existingCharge.Id == requestedCharge.Id))
                                     .Select(requestedCharge => requestedCharge.Id)
                                     .ToList();

            if (unknownCareCharges.Count > 0)
            {
                throw new ApiException($"Care charges {String.Join(", ", unknownCareCharges)} not found", HttpStatusCode.BadRequest);
            }

            // each care charge sub-type must have only one entry
            var duplicatedSubtypes = request.CareCharges
                                     .GroupBy(careCharge => careCharge.SubType)
                                     .Where(group => group.Count() > 1)
                                     .Select(group => group.Key.GetDisplayName())
                                     .ToList();

            if (duplicatedSubtypes.Count > 0)
            {
                throw new ApiException("Not allowed to have more than one " +
                                       $"{String.Join(", ", duplicatedSubtypes)} in request", HttpStatusCode.BadRequest);
            }
        }
        public async Task ExecuteAsync(Guid carePackageId, CareChargesCreateDomain request)
        {
            // Get package with all reclaims
            var package = await _carePackageGateway
                          .GetPackageAsync(carePackageId, PackageFields.Settings | PackageFields.Details | PackageFields.Reclaims, true)
                          .EnsureExistsAsync($"Care package with id {carePackageId} not found");

            ValidatePackage(package);
            ValidateRequestIntegrity(request, package); // ensure existing reclaims match requested

            // since validating raw request is tricky, first build a model of ordered charges
            // and then validate them to be in package date range and followed each other without any gaps
            PrepareCareChargesModel(request, package);
            ValidateCareChargesModel(request, package);

            // if care charge has date / cost / collector changed, cancel existing one and replace it with new.
            ReplaceUpdatedCareCharges(request, package);
            ReclaimCostValidator.Validate(package);

            await _dbManager.SaveAsync();
        }
 public static CareChargesCreationRequest ToionRequest(this CareChargesCreateDomain input)
 {
     return(_mapper.Map <CareChargesCreationRequest>(input));
 }