public async Task <CompensatoryNoticePayCalculationResponseDTO> PerformCompensatoryNoticePayCalculationAsync( CompensatoryNoticePayCalculationRequestModel request, IOptions <ConfigLookupRoot> options) { //set notice start date var noticeStartDate = request.DateNoticeGiven.Date.AddDays(1); //calculate days notice worked var dismissalDate = request.DismissalDate.Date; var insolvencyDate = request.InsolvencyDate.Date; //set paydays_day.number means the integer index of the day of the week 0 = Sunday var payDay = noticeStartDate.AddDays(-1).DayOfWeek; //calculate years of service between employment start date and dismissal date int yearsOfService = await request.InsolvencyEmploymentStartDate.Date.GetServiceYearsAsync(dismissalDate); yearsOfService = Math.Max(yearsOfService, 1); yearsOfService = Math.Min(yearsOfService, 12); var statutaryMax = ConfigValueLookupHelper.GetStatutoryMax(options, dismissalDate); var taxRate = ConfigValueLookupHelper.GetTaxRate(options, dismissalDate); var niThreshold = ConfigValueLookupHelper.GetNIThreshold(options, DateTime.Now); var niRate = ConfigValueLookupHelper.GetNIRate(options, DateTime.Now); var waitingDays = ConfigValueLookupHelper.GetBenefitsWaitingDays(options, dismissalDate); var notionalBenefitWeeklyRate = ConfigValueLookupHelper.GetNotionalBenefitsWeeklyRate(options, dismissalDate, await request.DateOfBirth.Date.GetAge(dismissalDate)); decimal notionalBenefitDailyRate = notionalBenefitWeeklyRate / 7; var projectedNoticeDate = noticeStartDate.AddDays(yearsOfService * 7 - 1); //calculate daily rates decimal dailyPayRate = request.WeeklyWage / request.ShiftPattern.Count; decimal dailyStatutoryMaximum = statutaryMax / 7m; // set claim boudaries var claimStartDate = dismissalDate.AddDays(1); var claimEndDate = projectedNoticeDate; if (request.DeceasedDate.HasValue && request.DeceasedDate.Value.Date < claimEndDate) { claimEndDate = request.DeceasedDate.Value.Date; } var adjClaimEndDate = claimEndDate; if (adjClaimEndDate.DayOfWeek != payDay) { adjClaimEndDate = adjClaimEndDate.AddDays(7); } int daysInClaim = await claimStartDate.GetNumBusinessDaysInRange(claimEndDate, request.ShiftPattern); // find paydays in claim period var payDays = await claimStartDate.GetDaysInRange(adjClaimEndDate, payDay); var weekIndex = 1; var notionalDaysCount = 0; List <CompensatoryNoticePayResult> weeklyResults = new List <CompensatoryNoticePayResult>(); foreach (var payWeekEnd in payDays) { var totalBenefitAmount = 0m; var totalNewEmploymentAmount = 0m; var totalWageIncreaseAmount = 0m; var notionalBenefitAmount = 0m; var maximumDays = 0; var employerDays = 0; // step through days in each pay week for (int j = 6; j >= 0; j--) { // set date to be checked to consecxutive days in pay week var claimDay = payWeekEnd.AddDays(-j); //check if claimday is in the claimperiod if (claimDay >= claimStartDate && claimDay <= claimEndDate) { bool benefitOrIncomeReceived = false; foreach (var benefit in request.Benefits) { var endDate = benefit.BenefitEndDate ?? claimEndDate; if (claimDay >= benefit.BenefitStartDate.Date && claimDay <= endDate) { benefitOrIncomeReceived = true; totalBenefitAmount += await benefit.BenefitAmount.GetDailyAmount(benefit.BenefitStartDate, endDate); } } foreach (var newEmployment in request.NewEmployments) { var endDate = newEmployment.NewEmploymentEndDate ?? claimEndDate; if (claimDay >= newEmployment.NewEmploymentStartDate.Date && claimDay <= endDate) { benefitOrIncomeReceived = true; var newEmploymentEarnings = newEmployment.NewEmploymentWage; if (newEmployment.NewEmploymentWeeklyWage.HasValue) { newEmploymentEarnings = Math.Max(newEmploymentEarnings, await newEmployment.NewEmploymentWeeklyWage.Value.GetEarnings(newEmployment.NewEmploymentStartDate, endDate)); } totalNewEmploymentAmount += await newEmploymentEarnings.GetDailyAmount(newEmployment.NewEmploymentStartDate, endDate); } } foreach (var wageIncrease in request.WageIncreases) { var endDate = wageIncrease.WageIncreaseEndDate ?? claimEndDate; if (claimDay >= wageIncrease.WageIncreaseStartDate.Date && claimDay <= endDate) { benefitOrIncomeReceived = true; totalWageIncreaseAmount += await wageIncrease.WageIncreaseAmount.GetDailyAmount(wageIncrease.WageIncreaseStartDate, endDate); } } foreach (var benefitOverride in request.NotionalBenefitOverrides) { if (claimDay >= benefitOverride.NotionalBenefitOverrideStartDate.Date && claimDay <= benefitOverride.NotionalBenefitOverrideEndDate.Date) { benefitOrIncomeReceived = true; } } if (benefitOrIncomeReceived) { notionalDaysCount = 0; } else { notionalDaysCount++; if (notionalDaysCount > waitingDays) { notionalBenefitAmount += notionalBenefitDailyRate; } } if (await request.ShiftPattern.ContainsDayWeek(claimDay.DayOfWeek)) { employerDays++; } maximumDays++; } } //apply mitigation decimal employerEntitlement = employerDays * dailyPayRate; decimal maximumEntitlement = maximumDays * dailyStatutoryMaximum; decimal mitigation = totalBenefitAmount + totalNewEmploymentAmount + totalWageIncreaseAmount + notionalBenefitAmount; decimal grossEntitlement = Math.Max(employerEntitlement - mitigation, 0); decimal notationalTaxDeducted = 0m; decimal taxDeducted = 0m; decimal niDeducted = 0m; decimal netEntitlement = 0m; bool isTaxable = false; if (dismissalDate <= CutOverDate) { notationalTaxDeducted = Math.Round(await grossEntitlement.GetTaxDeducted(taxRate, request.IsTaxable), 2); grossEntitlement = Math.Round(Math.Min(grossEntitlement - notationalTaxDeducted, maximumEntitlement), 2); netEntitlement = grossEntitlement; } else { isTaxable = true; //Apply StatutoryMaximum grossEntitlement = Math.Min(grossEntitlement, maximumEntitlement); taxDeducted = Math.Round(await grossEntitlement.GetTaxDeducted(taxRate, request.IsTaxable), 2); niDeducted = Math.Round(await grossEntitlement.GetNIDeducted(niThreshold, niRate, request.IsTaxable), 2); grossEntitlement = Math.Round(grossEntitlement, 2); netEntitlement = Math.Min(grossEntitlement - taxDeducted - niDeducted, Math.Round(maximumEntitlement, 2)); } weeklyResults.Add(new CompensatoryNoticePayResult() { WeekNumber = weekIndex, EmployerEntitlement = Math.Round(employerEntitlement, 2), BenefitsDeducted = Math.Round(totalBenefitAmount, 2), NewEmploymentDeducted = Math.Round(totalNewEmploymentAmount, 2), WageIncreaseDeducted = Math.Round(totalWageIncreaseAmount, 2), NotionalBenefitDeducted = Math.Round(notionalBenefitAmount, 2), GrossEntitlement = grossEntitlement, IsTaxable = isTaxable, NotionalTaxDeducted = notationalTaxDeducted, TaxDeducted = taxDeducted, NIDeducted = niDeducted, NetEntitlement = netEntitlement, PreferentialClaim = 0m, NonPreferentialClaim = grossEntitlement }); weekIndex++; } var response = new CompensatoryNoticePayCalculationResponseDTO() { NoticeWeeksDue = payDays.Count, StatutoryMax = Math.Round(statutaryMax, 2), MaxCNPEntitlement = Math.Round(payDays.Count * Math.Min(statutaryMax, request.WeeklyWage), 2), NoticeStartDate = noticeStartDate, ProjectedNoticeDate = projectedNoticeDate, CompensationEndDate = claimEndDate, DaysInClaim = daysInClaim, WeeklyResults = weeklyResults }; return(await Task.FromResult(response)); }
public async Task <NoticePayCompositeCalculationResponseDTO> PerformNoticePayCompositeCalculationAsync(NoticePayCompositeCalculationRequestModel data, IOptions <ConfigLookupRoot> options) { var result = new NoticePayCompositeCalculationResponseDTO(); var cnpOutput = new CompensatoryNoticePayCalculationResponseDTO(); var nwnpOutput = new NoticeWorkedNotPaidCompositeOutput(); var rp1TraceInfo = new TraceInfo(); var rp14TraceInfo = new TraceInfo(); //CNP calculation if (data.Cnp != null) { cnpOutput = await _cnpService.PerformCompensatoryNoticePayCalculationAsync(data.Cnp, options); cnpOutput.WeeklyResults.ForEach(x => x.IsSelected = true); } //NWNP calculation if (data.Nwnp != null && data.Nwnp.Any()) { var statutoryMax = ConfigValueLookupHelper.GetStatutoryMax(options, data.Nwnp.FirstOrDefault().InsolvencyDate); foreach (var nwnp in data.Nwnp) { var traceDate = new TraceInfoDate(); var res = await _nwnpService.PerformNwnpCalculationAsync(nwnp, options, traceDate); if (res.InputSource == InputSource.Rp1) { //nwnpOutput.nwnpResults.rP1ResultsList.Add(res); nwnpOutput.rp1Results.WeeklyResult.AddRange(res.WeeklyResult); rp1TraceInfo.Dates.Add(traceDate); rp1TraceInfo.NumberOfDays = 0.00m; foreach (var week in res.WeeklyResult) { rp1TraceInfo.NumberOfDays += week.EmploymentDays; } } else if (res.InputSource == InputSource.Rp14a) { //nwnpOutput.nwnpResults.rP14aResultsList.Add(res); nwnpOutput.rp14aResults.WeeklyResult.AddRange(res.WeeklyResult); rp14TraceInfo.Dates.Add(traceDate); rp14TraceInfo.NumberOfDays = 0.00m; foreach (var week in res.WeeklyResult) { rp14TraceInfo.NumberOfDays += week.EmploymentDays; } } } var rp1Total = 0m; var rp14aTotal = 0m; //merge pay weeks nwnpOutput.rp1Results = await nwnpOutput.rp1Results.MergePayWeeks(options); nwnpOutput.rp1Results.InputSource = InputSource.Rp1; nwnpOutput.rp1Results.StatutoryMax = statutoryMax; rp1Total = nwnpOutput.rp1Results.WeeklyResult.Sum(x => x.NetEntitlement); //merge pay weeks nwnpOutput.rp14aResults = await nwnpOutput.rp14aResults.MergePayWeeks(options); nwnpOutput.rp14aResults.InputSource = InputSource.Rp14a; nwnpOutput.rp14aResults.StatutoryMax = statutoryMax; rp14aTotal = nwnpOutput.rp14aResults.WeeklyResult.Sum(x => x.NetEntitlement); //select the input source // Choose RP1 list or RP14a list with the lowest total NetEntitlement if ((rp1Total < rp14aTotal && rp14aTotal != 0 && rp1Total != 0) || (rp1Total > 0 && rp14aTotal == 0)) { nwnpOutput.SelectedInputSource = InputSource.Rp1; nwnpOutput.rp1Results.WeeklyResult.ForEach(x => x.IsSelected = true); nwnpOutput.rp14aResults.WeeklyResult.ForEach(x => x.IsSelected = false); nwnpOutput.TraceInfo = await rp1TraceInfo.ConvertToJson(); } else { nwnpOutput.SelectedInputSource = InputSource.Rp14a; nwnpOutput.rp1Results.WeeklyResult.ForEach(x => x.IsSelected = false); nwnpOutput.rp14aResults.WeeklyResult.ForEach(x => x.IsSelected = true); nwnpOutput.TraceInfo = await rp14TraceInfo.ConvertToJson(); } } result.Cnp = cnpOutput; result.Nwnp = nwnpOutput; return(result); }