/// <summary> /// Creates deductibles (claim transaction details) where required for the new reserve/payment/recovery reserve/recovery receipt transaction /// </summary> /// <param name="financialContext">wrapper for context variables</param> /// <returns>Deductible Result</returns> private static DeductibleResult GenerateDeductibles(FinancialTransactionContext financialContext) { IDictionary<string, ProductClaimDetail> claimDetailProductMap = MapClaimDetailRefToProductClaimDetail(financialContext.ClaimHeader.ClaimDetails, financialContext.ProductClaimDetails); var currentProductClaimDetail = claimDetailProductMap[financialContext.ClaimDetail.ClaimDetailReference]; bool isCalculatingExcess = IsCalculatingExcess(currentProductClaimDetail, financialContext.ClaimDetail); bool isCalculatingDeductibles = IsCalculatingDeductibles(currentProductClaimDetail, financialContext.ProductClaimDefinition, financialContext.ClaimHeader); if (isCalculatingExcess) { var deductibles = ResolveDeductibles(financialContext, currentProductClaimDetail); IEnumerable<ClaimFinancialAmount> reserves, payments, recoveryReserves; LoadFinancialAmounts(financialContext.TransactionSource, financialContext.ProductClaimDefinition, financialContext.ClaimDetail, financialContext.ClaimTransactionHeader, out reserves, out payments, out recoveryReserves); return CalculateDeductibles(financialContext.ProductClaimDefinition, financialContext, deductibles, reserves, payments, recoveryReserves); } else if (isCalculatingDeductibles) { var deductibles = ResolveDeductibles(financialContext); IEnumerable<ClaimFinancialAmount> reserves, payments, recoveryReserves; LoadFinancialAmounts(financialContext.TransactionSource, financialContext.ProductClaimDefinition, claimDetailProductMap, financialContext.ClaimHeader, financialContext.ClaimTransactionHeader, out reserves, out payments, out recoveryReserves); return CalculateDeductibles(financialContext.ProductClaimDefinition, financialContext, deductibles, reserves, payments, recoveryReserves); } return new DeductibleResult { Success = true }; }
/// <summary> /// Try and find the latest deductible reserve /// </summary> /// <param name="reserves">all reserves</param> /// <param name="context">context variables</param> /// <param name="movementType">movement type of the latest reserve</param> /// <returns>latest reserve or null</returns> private static ClaimFinancialAmount LatestReserveOrNull(IEnumerable<ClaimFinancialAmount> reserves, FinancialTransactionContext context, string movementType) { var claimDetailRef = context.ClaimDetail.ClaimDetailReference; var origCurrency = context.ClaimTransactionGroup.OriginalCurrencyCode; var accCurrency = context.ClaimTransactionGroup.AccountingCurrencyCode; return (from reserve in reserves where reserve.ClaimDetailReference == claimDetailRef && reserve.MovementType == movementType && reserve.OriginalCurrencyCode == origCurrency && reserve.AccountingCurrencyCode == accCurrency select reserve) .SingleOrDefault(); }
private static IEnumerable<DeductibleDefinition> ResolveDeductibles(FinancialTransactionContext context, ProductClaimDetail productClaimDetail) { var claimDetail = context.ClaimDetail; var productCode = context.Product.Product.Code; var definitions = new List<DeductibleDefinition>(); var user = context.CurrentUser; var claimTransactionContext = claimDetail.Context; CreateDeductibleDefinitionIfEnabled(claimTransactionContext, true, definitions, claimDetail.PolicyDeductible01, claimDetail.IsDeductible01PaidByInsurer, productClaimDetail.AutomaticDeductible01MovementTypeCode, productClaimDetail.InsurerFundedDeductible01MovementTypeCode, productCode, user); return definitions; }
private static IEnumerable<DeductibleDefinition> ResolveDeductibles(FinancialTransactionContext context) { var productClaimDefinition = context.ProductClaimDefinition; var claim = context.ClaimHeader; var productCode = context.Product.Product.Code; var definitions = new List<DeductibleDefinition>(); var user = context.CurrentUser; var claimTransactionContext = claim.Context; CreateDeductibleDefinitionIfEnabled(claimTransactionContext, false, definitions, claim.PolicyDeductible01, claim.IsDeductible01PaidByInsurer, productClaimDefinition.AutomaticDeductible01MovementTypeCode, productClaimDefinition.InsurerFundedDeductible01MovementTypeCode, productCode, user); CreateDeductibleDefinitionIfEnabled(claimTransactionContext, false, definitions, claim.PolicyDeductible02, claim.IsDeductible02PaidByInsurer, productClaimDefinition.AutomaticDeductible02MovementTypeCode, productClaimDefinition.InsurerFundedDeductible02MovementTypeCode, productCode, user); CreateDeductibleDefinitionIfEnabled(claimTransactionContext, false, definitions, claim.PolicyDeductible03, claim.IsDeductible03PaidByInsurer, productClaimDefinition.AutomaticDeductible03MovementTypeCode, productClaimDefinition.InsurerFundedDeductible03MovementTypeCode, productCode, user); CreateDeductibleDefinitionIfEnabled(claimTransactionContext, false, definitions, claim.PolicyDeductible04, claim.IsDeductible04PaidByInsurer, productClaimDefinition.AutomaticDeductible04MovementTypeCode, productClaimDefinition.InsurerFundedDeductible04MovementTypeCode, productCode, user); CreateDeductibleDefinitionIfEnabled(claimTransactionContext, false, definitions, claim.PolicyDeductible05, claim.IsDeductible05PaidByInsurer, productClaimDefinition.AutomaticDeductible05MovementTypeCode, productClaimDefinition.InsurerFundedDeductible05MovementTypeCode, productCode, user); return definitions; }
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; }
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 }; }
/// <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; }