private static DeductibleResult ApplyDeductibleReserveAdjustments(string movementType, DeductibleDefinition deductibleDefinition, DeductibleReserveCapacity deductibleReserveContext, FinancialTransactionContext financialContext, IEnumerable<ClaimFinancialAmount> reserves, ClaimFinancialAmount latestReserve, decimal reserveAdjustment) { string claimDetailRef = financialContext.ClaimDetail.ClaimDetailReference; decimal incurredTotal = deductibleReserveContext.ResolveCapacity(claimDetailRef); decimal revisedAmount = Math.Max(Math.Min(incurredTotal, reserveAdjustment), 0); // if it's a recovery receipt transaction and there is reserve adjustment, stop. if (IsRecovery(financialContext.TransactionSource) && movementType == deductibleDefinition.NonFundedMovementType && ((latestReserve == null && reserveAdjustment != 0) || (latestReserve != null && reserveAdjustment != latestReserve.TransactionAmountClaimCurrency))) { return new DeductibleResult { Success = false, Message = MessageConstants.RecoveryReservesRequireManualReview }; } reserveAdjustment -= AddReserve(revisedAmount, financialContext, financialContext.ClaimDetail, financialContext.ClaimTransactionGroup, deductibleDefinition, deductibleReserveContext, latestReserve); // if the reserve adjustment couldn't be absorbed by the current claim detail apply accross the claim if (reserveAdjustment != 0 && !deductibleDefinition.IsClaimDetailDeductible) { // don't process if currencies aren't all the same if (!AreReservesInSameCurrency(reserves, movementType)) { return new DeductibleResult { Success = false, Message = MessageConstants.ReservesRequireManualReview }; } var reservesForMovementType = reserves.Where(a => a.MovementType == movementType); var claimDetails = from claimDetail in financialContext.ClaimHeader.ClaimDetails join reserve in reservesForMovementType on claimDetail.ClaimDetailReference equals reserve.ClaimDetailReference into cr from groupData in cr.DefaultIfEmpty() where claimDetail.ClaimDetailReference != claimDetailRef orderby groupData != null ? Math.Abs(groupData.TransactionAmountClaimCurrency.GetValueOrDefault()) : 0 descending select claimDetail; foreach (var claimDetail in claimDetails) { incurredTotal = deductibleReserveContext.ResolveCapacity(claimDetail.ClaimDetailReference); ClaimFinancialAmount remainingAmount = reserves.SingleOrDefault(a => a.ClaimDetailReference == claimDetail.ClaimDetailReference && a.MovementType == movementType); decimal remainingAbsAmount = remainingAmount != null ? Math.Abs(remainingAmount.TransactionAmountClaimCurrency.GetValueOrDefault()) : 0; revisedAmount = Math.Max(Math.Min(incurredTotal, remainingAbsAmount + reserveAdjustment), 0); var newClaimTransactionGroup = ResolveClaimTransactionGroup(financialContext.ClaimTransactionHeader, financialContext.ClaimTransactionGroup, claimDetail); revisedAmount = AddReserve(revisedAmount, financialContext, claimDetail, newClaimTransactionGroup, deductibleDefinition, deductibleReserveContext, remainingAmount); reserveAdjustment -= revisedAmount - remainingAbsAmount; if (reserveAdjustment == 0) { break; } } } return new DeductibleResult { Success = true }; }
private static decimal AddReserve(decimal amount, FinancialTransactionContext financialContext, ClaimDetail claimDetail, ClaimTransactionGroup claimTransactionGroup, DeductibleDefinition deductibleDefinition, DeductibleReserveCapacity deductibleReserveContext, ClaimFinancialAmount latestReserve = null) { StaticValues.AmountType amountType; string nonFundedMovementType; if (IsRecovery(financialContext.TransactionSource)) { amountType = StaticValues.AmountType.RecoveryReserve; nonFundedMovementType = deductibleDefinition.RecoveryNonFundedMovementType; } else { amountType = StaticValues.AmountType.Reserve; nonFundedMovementType = deductibleDefinition.NonFundedMovementType; } if (latestReserve == null) { if (amount > 0) { AddClaimTransactionDetails(financialContext.ClaimTransactionHeader.TransactionDate.GetValueOrDefault(DateTime.MinValue), claimTransactionGroup, amountType, deductibleDefinition, amount, latestReserve); deductibleReserveContext.AdjustDeductibleAttached(claimDetail.ClaimDetailReference, nonFundedMovementType, amount); } else { amount = 0; } } else if (amount != Math.Abs(latestReserve.TransactionAmountClaimCurrency.GetValueOrDefault(0))) { AddClaimTransactionDetails(financialContext.ClaimTransactionHeader.TransactionDate.GetValueOrDefault(DateTime.MinValue), claimTransactionGroup, amountType, deductibleDefinition, amount, latestReserve); deductibleReserveContext.AdjustDeductibleAttached(claimDetail.ClaimDetailReference, nonFundedMovementType, amount + latestReserve.TransactionAmountClaimCurrency.GetValueOrDefault()); } return amount; }
/// <summary> /// For each deductible associated with this transaction we need to calculate how the latest transaction has affected the the current deductibles and generate any new deductibles as required /// </summary> /// <param name="productClaimDefinition">calim product</param> /// <param name="financialContext">context variables</param> /// <param name="deductibleDefinitions">deductibles associated with this transaction</param> /// <param name="reserves">historical reserves</param> /// <param name="payments">historical payments</param> /// <param name="recoveryReserves">recovery reserves</param> /// <returns>Deductible Result</returns> private static DeductibleResult CalculateDeductibles(ProductClaimDefinition productClaimDefinition, FinancialTransactionContext financialContext, IEnumerable<DeductibleDefinition> deductibleDefinitions, IEnumerable<ClaimFinancialAmount> reserves, IEnumerable<ClaimFinancialAmount> payments, IEnumerable<ClaimFinancialAmount> recoveryReserves) { var deductibleResult = new DeductibleResult { Success = true }; var deductibleMovementTypes = deductibleDefinitions.SelectMany(a => a.GetMovementTypes()); var recoveryDeductibleMovementTypes = deductibleDefinitions.SelectMany(a => a.GetRecoveryMovementTypes()); var claimDetailRefs = financialContext.ClaimHeader.ClaimDetails.Select(a => a.ClaimDetailReference); var deductibleReserveContext = new DeductibleReserveCapacity(claimDetailRefs, reserves, deductibleMovementTypes); var deductibleRecoveryReserveContext = new DeductibleReserveCapacity(claimDetailRefs, recoveryReserves, recoveryDeductibleMovementTypes); bool isRecovery = IsRecovery(financialContext.TransactionSource); decimal sumOfLowerDeductibles = 0; decimal totalPaid = payments.Where(a => !deductibleMovementTypes.Contains(a.MovementType)).Sum(a => a.TransactionAmountClaimCurrency.GetValueOrDefault()); decimal totalIncurred = CalculateTotalIncurred(productClaimDefinition, totalPaid, reserves.Where(a => !deductibleMovementTypes.Contains(a.MovementType))); decimal totalRecoveryIncurred = totalIncurred + recoveryReserves.Where(a => !recoveryDeductibleMovementTypes.Contains(a.MovementType)).Sum(a => a.TransactionAmountClaimCurrency.GetValueOrDefault(0)); foreach (var deductible in deductibleDefinitions) { decimal deductiblePaid = CalculateDeductiblePayment(deductible, totalPaid, sumOfLowerDeductibles, payments); if (deductiblePaid != 0) { var amountType = isRecovery ? StaticValues.AmountType.RecoveryReceipt : StaticValues.AmountType.Payment; AddClaimTransactionDetails(financialContext.ClaimTransactionHeader.TransactionDate.GetValueOrDefault(DateTime.MinValue), financialContext.ClaimTransactionGroup, amountType, deductible, deductiblePaid); } if (isRecovery && deductible.RecoveryNonFundedMovementType != null) { ClaimFinancialAmount latestReserve = LatestReserveOrNull(recoveryReserves, financialContext, deductible.RecoveryNonFundedMovementType); decimal deductibleRecoveryReserve = CalculateDeductibleRecoveryReserves(deductible, latestReserve, totalIncurred, totalRecoveryIncurred, sumOfLowerDeductibles, recoveryReserves); // although a deductible recovery reserve is a negative amount we want to keep it as a positive for now so we can apply deductible reserve adjustments deductibleRecoveryReserve = -deductibleRecoveryReserve; var reserveAdjResult = ApplyDeductibleReserveAdjustments(deductible.RecoveryNonFundedMovementType, deductible, deductibleRecoveryReserveContext, financialContext, recoveryReserves, latestReserve, deductibleRecoveryReserve); deductibleRecoveryReserveContext.AppendToLowerDeductibles(deductible.RecoveryNonFundedMovementType); if (!reserveAdjResult.Success) { deductibleResult = reserveAdjResult; } } if (financialContext.TransactionSource != StaticValues.ClaimTransactionSource.RecoveryReserve) { ClaimFinancialAmount latestReserve = LatestReserveOrNull(reserves, financialContext, deductible.NonFundedMovementType); decimal deductibleReserve = CalculateDeductibleReserves(deductible, latestReserve, totalPaid, totalIncurred, sumOfLowerDeductibles, reserves); var reserveAdjResult = ApplyDeductibleReserveAdjustments(deductible.NonFundedMovementType, deductible, deductibleReserveContext, financialContext, reserves, latestReserve, deductibleReserve); deductibleReserveContext.AppendToLowerDeductibles(deductible.NonFundedMovementType); if (!reserveAdjResult.Success) { deductibleResult = reserveAdjResult; } } sumOfLowerDeductibles += deductible.Amount; } return deductibleResult; }