diff --git a/findstr b/findstr new file mode 100644 index 00000000..d3aed7fc Binary files /dev/null and b/findstr differ diff --git a/src/Application/Application.csproj b/src/Application/Application.csproj new file mode 100644 index 00000000..ba4dc97e --- /dev/null +++ b/src/Application/Application.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BvgCalculator/Bvg.cs b/src/Application/Bvg/Bvg.cs similarity index 89% rename from src/BvgCalculator/Bvg.cs rename to src/Application/Bvg/Bvg.cs index cc254147..974bd877 100644 --- a/src/BvgCalculator/Bvg.cs +++ b/src/Application/Bvg/Bvg.cs @@ -1,9 +1,7 @@ -using System; -using System.Collections.Generic; -using PensionCoach.Tools.CommonTypes; +using Domain.Enums; using PensionCoach.Tools.CommonUtils; -namespace PensionCoach.Tools.BvgCalculator +namespace Application.Bvg { /// /// @@ -191,31 +189,32 @@ public static decimal GetUwsRateBvg(int year, Gender gender) } private static readonly Dictionary AhvMaxPensionDictionary = new() - { - {1969, null}, - {1970, 0M}, - {1984, 0M}, - {1985, 16560M}, - {1987, 17280M}, - {1989, 18000M}, - {1991, 19200M}, - {1992, 21600M}, - {1994, 22560M}, - {1996, 23280M}, - {1998, 23880M}, - {1999, 24120M}, - {2000, 24120M}, - {2002, 24720M}, - {2004, 25320M}, - {2007, 25800M}, - {2009, 26520M}, - {2011, 27360M}, - {2013, 27840M}, - {2015, 28080M}, - {2019, 28200M}, - {2021, 28440M}, - {9999, 28680M}, - }; + { + {1969, null}, + {1970, 0}, + {1984, 0}, + {1985, 16560}, + {1987, 17280}, + {1989, 18000}, + {1991, 19200}, + {1992, 21600}, + {1994, 22560}, + {1996, 23280}, + {1998, 23880}, + {1999, 24120}, + {2000, 24120}, + {2002, 24720}, + {2004, 25320}, + {2007, 25800}, + {2009, 26520}, + {2011, 27360}, + {2013, 27840}, + {2015, 28080}, + {2019, 28200}, + {2021, 28440}, + {2023, 28680}, + {9999, 29400}, + }; private const decimal MinimalSalaryFactor = 0.125M; private const decimal SalaryEntryThresholdFactor = 0.75M; @@ -268,7 +267,8 @@ private static decimal GetUwsRateBvgFemale(int year) {2014, 0.0150M}, {2016, 0.0175M}, {2017, 0.0125M}, - {9999, 0.0100M}, + {2023, 0.0100M}, + {9999, 0.0125M}, }; } } diff --git a/src/Application/Bvg/BvgCalculator.cs b/src/Application/Bvg/BvgCalculator.cs new file mode 100644 index 00000000..334b7db2 --- /dev/null +++ b/src/Application/Bvg/BvgCalculator.cs @@ -0,0 +1,366 @@ +using Application.Bvg.Models; +using Application.Extensions; +using Domain.Enums; +using Domain.Models.Bvg; +using FluentValidation; +using FluentValidation.Results; +using LanguageExt; +using PensionCoach.Tools.CommonUtils; + +namespace Application.Bvg; + +public class BvgCalculator( + BvgRetirementDateCalculator retirementDateCalculator, + IBvgRetirementCredits retirementCredits, + IValidator bvgPersonValidator) + : IBvgCalculator +{ + public Either Calculate(int calculationYear, decimal retirementCapitalEndOfYear, BvgPerson person) + { + Option validationResult = bvgPersonValidator.Validate(person); + + return validationResult + .Where(r => !r.IsValid) + .Map>(r => + { + var errorMessageLine = string.Join(";", r.Errors.Select(x => x.ErrorMessage)); + return $"validation failed: {errorMessageLine}"; + }) + .IfNone(true) + .Bind(_ => CalculateInternal(calculationYear, retirementCapitalEndOfYear, person)); + } + + public Either InsuredSalary(int calculationYear, BvgPerson person) + { + BvgSalary salary = GetBvgSalary(calculationYear, person); + + return salary.InsuredSalary; + } + + public Either InsuredSalaries(int calculationYear, BvgPerson person) + { + DateTime currentDate = new(calculationYear, 1, 1); + DateTime retirementDate = retirementDateCalculator.DateOfRetirement(person.Gender, person.DateOfBirth); + DateTime technicalBirthdate = person.DateOfBirth.GetBirthdateTechnical(); + TechnicalAge birthdateAsAge = TechnicalAge.From(technicalBirthdate.Year, technicalBirthdate.Month); + + List salaries = []; + decimal salary = InsuredSalary(calculationYear, person).IfLeft(decimal.Zero); + while (currentDate <= retirementDate) + { + TechnicalAge age = TechnicalAge.From(currentDate.Year, currentDate.Month) - birthdateAsAge; + salaries.Add(new BvgTimeSeriesPoint(age, currentDate, salary)); + currentDate = currentDate.AddYears(1); + } + + return salaries.ToArray(); + } + + public Either RetirementCreditFactors(int calculationYear, BvgPerson person) + { + DateTime technicalBirthdate = person.DateOfBirth.GetBirthdateTechnical(); + TechnicalAge birthdateAsAge = TechnicalAge.From(technicalBirthdate.Year, technicalBirthdate.Month); + + List points = []; + foreach (var xBvg in Enumerable.Range(Bvg.EntryAgeBvg, Bvg.FinalAge - Bvg.EntryAgeBvg + 1)) + { + DateTime calculationDate = new DateTime(person.DateOfBirth.Year + xBvg, 1, 1); + TechnicalAge age = TechnicalAge.From(calculationDate.Year, calculationDate.Month) - birthdateAsAge; + points.Add(new BvgTimeSeriesPoint(age, calculationDate, retirementCredits.GetRate(xBvg))); + } + + return points.ToArray(); + } + + public Either RetirementCredits(int calculationYear, BvgPerson person) + { + return from factors in RetirementCreditFactors(calculationYear, person) + from salaries in InsuredSalaries(calculationYear, person) + select Combine(factors, salaries).ToArray(); + + IEnumerable Combine(BvgTimeSeriesPoint[] factors, BvgTimeSeriesPoint[] salaries) + { + return from f in factors + from s in salaries + where f.Date == s.Date + select f with { Value = f.Value * s.Value }; + } + } + + private Either CalculateInternal(int calculationYear, decimal retirementCapitalEndOfYear, BvgPerson person) + { + BvgSalary salary = GetBvgSalary(calculationYear, person); + + decimal retirementCreditFactor = GetRetirementCreditFactor(person, calculationYear); + decimal retirementCredit = salary.InsuredSalary * retirementCreditFactor; + + IReadOnlyCollection retirementCreditSequence = + GetRetirementCreditSequence(person, calculationYear, salary); + + IReadOnlyCollection retirementCapitalSequence = + GetRetirementCapitalSequence(retirementCapitalEndOfYear, calculationYear, person, retirementCreditSequence); + + decimal finalRetirementCapital = + GetFinalRetirementCapital(retirementCapitalSequence); + + decimal finalRetirementCapitalWithoutInterest = + GetFinalRetirementCapitalWithoutInterest(retirementCapitalSequence); + + DateTime dateOfProcess = new DateTime(calculationYear, 1, 1); + + decimal retirementPension = GetRetirementPension(retirementCapitalEndOfYear, person, dateOfProcess, retirementCreditSequence); + + // reset risk benefits to 0 if below salary threshold + decimal disabilityPension = 0; + decimal partnerPension = 0; + decimal childPension = 0; + decimal orphanPension = 0; + + if (salary.EffectiveSalary > Bvg.GetEntranceThreshold(Bvg.GetPensionMaximum(calculationYear))) + { + disabilityPension = GetDisabilityPension(retirementCapitalSequence, person, dateOfProcess); + partnerPension = GetPartnerPension(retirementCapitalSequence, dateOfProcess, person); + childPension = GetChildPensionForDisabled(retirementCapitalSequence, person, dateOfProcess); + orphanPension = childPension; + } + + Either result = new BvgCalculationResult + { + DateOfRetirement = GetRetirementDate(person.DateOfBirth, person.Gender), + EffectiveSalary = salary.EffectiveSalary, + InsuredSalary = salary.InsuredSalary, + RetirementCredit = retirementCredit, + RetirementCreditFactor = retirementCreditFactor, + RetirementPension = retirementPension, + RetirementCapitalEndOfYear = retirementCapitalEndOfYear, + FinalRetirementCapital = finalRetirementCapital, + FinalRetirementCapitalWithoutInterest = finalRetirementCapitalWithoutInterest, + DisabilityPension = disabilityPension, + PartnerPension = partnerPension, + OrphanPension = orphanPension, + ChildPensionForDisabled = childPension, + RetirementCreditSequence = retirementCreditSequence, + RetirementCapitalSequence = retirementCapitalSequence + }; + + return result; + } + + public bool IsRetired(BvgPerson person, DateTime dateOfProcess) + { + DateTime retiredAt = GetRetirementDate(person.DateOfBirth, person.Gender); + + return (new DateTime(retiredAt.Year, retiredAt.Month, 1).AddDays(-1) < dateOfProcess.Date); + } + + public DateTime GetRetirementDate(DateTime dateOfBirth, Gender gender) + { + return retirementDateCalculator.DateOfRetirement(gender, dateOfBirth); + } + + public (int Years, int Months) GetRetirementAge(Gender typeOfGender, DateTime birthdate) + { + return retirementDateCalculator.RetirementAge(typeOfGender, birthdate); + } + + private BvgSalary GetBvgSalary(int calculationYear, BvgPerson person) + { + decimal workingAbilityDegree = decimal.One - person.DisabilityDegree; + + BvgSalary salary = new BvgSalary(); + + bool isRetired = IsRetired(person, new DateTime(calculationYear)); + + if (isRetired && person.DisabilityDegree == decimal.One) + { + return salary; + } + + salary.ReportedSalary = person.ReportedSalary; + salary.EffectiveSalary = person.ReportedSalary * workingAbilityDegree; + salary.InsuredSalary = isRetired ? 0M : GetInsuredSalary(person, calculationYear); + + return salary; + } + + private decimal GetRetirementPension( + decimal retirementEndOfYear, + BvgPerson personDetails, + DateTime dateOfProcess, + IReadOnlyCollection retirementCreditSequence) + { + IEnumerable retirementCapitalSequence = + GetRetirementCapitalSequence(retirementEndOfYear, dateOfProcess.Year, personDetails, retirementCreditSequence); + + RetirementCapital latestElement = retirementCapitalSequence.First(); + + return MathUtils.Round((latestElement.Value) * Bvg.GetUwsRateBvg(dateOfProcess.Year, personDetails.Gender)); + } + + private decimal GetPartnerPension( + IReadOnlyCollection retirementCapitalSequence, + DateTime dateOfProcess, + BvgPerson personDetails) + { + decimal capital = GetFinalRetirementCapitalWithoutInterest(retirementCapitalSequence); + + return MathUtils.Round(capital * Bvg.FactorPartnersPension * + Bvg.GetUwsRateBvg(dateOfProcess.Year, personDetails.Gender)); + } + + private decimal GetChildPensionForDisabled( + IReadOnlyCollection retirementCapitalSequence, + BvgPerson personDetails, + DateTime dateOfProcess) + { + decimal capital = GetFinalRetirementCapitalWithoutInterest(retirementCapitalSequence); + + return MathUtils.Round(capital * Bvg.FactorChildPension * + Bvg.GetUwsRateBvg(dateOfProcess.Year, personDetails.Gender)); + } + + private decimal GetDisabilityPension( + IReadOnlyCollection retirementCapitalSequence, BvgPerson personDetails, DateTime dateOfProcess) + { + decimal capital = GetFinalRetirementCapitalWithoutInterest(retirementCapitalSequence); + + return MathUtils.Round(capital * Bvg.GetUwsRateBvg(dateOfProcess.Year, personDetails.Gender)); + } + + private decimal GetFinalRetirementCapital( + IReadOnlyCollection retirementCapitalSequence) + { + RetirementCapital final = retirementCapitalSequence.First(); + + return MathUtils.Round(final.Value); + } + + private decimal GetFinalRetirementCapitalWithoutInterest(IReadOnlyCollection retirementCapitalSequence) + { + RetirementCapital final = retirementCapitalSequence.First(); + + return MathUtils.Round(final.ValueWithoutInterest); + } + + private IReadOnlyCollection GetRetirementCapitalSequence( + decimal retirementEndOfYear, + int calculationYear, + BvgPerson personDetails, + IReadOnlyCollection retirementCreditSequence) + { + // Date of retirement + DateTime dateOfRetirement = GetRetirementDate(personDetails.DateOfBirth, personDetails.Gender); + + // Interest rates + decimal iBvg = Bvg.GetInterestRate(calculationYear); + + // Retirement assets at end of insurance period Bvg portion + int age = calculationYear - personDetails.DateOfBirth.Year; + var retirementAgeBvg = GetRetirementAge(personDetails.Gender, personDetails.DateOfBirth); + + return BvgCapitalCalculationHelper.GetRetirementCapitalSequence(calculationYear, + dateOfRetirement, + age, + retirementAgeBvg.Years, + iBvg, + retirementEndOfYear, + retirementCreditSequence); + } + + private IReadOnlyCollection GetRetirementCreditSequence( + BvgPerson personDetails, + int calculationYear, + BvgSalary salaryDetails) + { + int xsBvg = GetRetirementAge(personDetails.Gender, personDetails.DateOfBirth).Years; + int xBvg = personDetails.DateOfBirth.GetBvgAge(calculationYear); + + BvgRetirementCreditsTable bvgRetirementCreditTable = new BvgRetirementCreditsTable(); + + return Enumerable.Range(xBvg, xsBvg - xBvg + 1) + .Select(x => + new RetirementCredit(bvgRetirementCreditTable.GetRate(x) * salaryDetails.InsuredSalary, x)) + .ToList(); + } + + private decimal GetRetirementCreditFactor(BvgPerson person, int calculationYear) + { + int xBvg = calculationYear - person.DateOfBirth.Year; + + return retirementCredits.GetRate(xBvg); + } + + private static decimal GetInsuredSalary(BvgPerson person, int calculationYear) + { + const decimal fullEmployedDegree = 1.0M; + + Option insuredSalary = 0M; + + if (person.DisabilityDegree == fullEmployedDegree) + { + return insuredSalary.IfNone(0M); + } + + if (person.DisabilityDegree > 0M) + { + insuredSalary = GetInsuredSalaryWhenDisabled(); + } + else + { + insuredSalary = GetInsuredSalaryWhenNotDisabled(); + } + + return MathUtils.Round5(insuredSalary.IfNone(0M)); + + Option GetInsuredSalaryWhenNotDisabled() + { + decimal ahvMax = Bvg.GetPensionMaximum(calculationYear); + + return Prelude.Some(person.ReportedSalary) + + // check salary entrance level + .Where(v => v > Bvg.GetEntranceThreshold(ahvMax)) + + // restrict by BVG salary max + .Map(v => Math.Min(v, Bvg.GetMaximumSalary(ahvMax))) + + // reduce by coordination deduction + .Map(v => v - GetCoordinationDeduction()) + + .Map(v => Math.Max(v, Bvg.GetMinimumSalary(ahvMax))) + .Map(v => Math.Round(v, 0, MidpointRounding.AwayFromZero)); + } + + Option GetInsuredSalaryWhenDisabled() + { + decimal minSalary = Bvg.GetMinimumSalary(Bvg.GetPensionMaximum(calculationYear)); + + Option disabilityDegree = person.DisabilityDegree; + + return disabilityDegree + .Where(v => v is > 0 and < decimal.One) + .Map(v => fullEmployedDegree - v) + + // scale salary up + .Map(v => person.ReportedSalary / v) + + // check salary entrance level + .Where(v => v > Bvg.GetEntranceThreshold(Bvg.GetPensionMaximum(calculationYear))) + + .Map(v => Math.Min(v, Bvg.GetMaximumSalary(Bvg.GetPensionMaximum(calculationYear)))) + + // reduce by coordination deduction + .Map(v => v - GetCoordinationDeduction()) + + // restrict by BVG salary max + .Map(v => v * (fullEmployedDegree - person.DisabilityDegree)) + .Map(v => v < minSalary ? minSalary : v) + .Map(MathUtils.Round5); + } + + decimal GetCoordinationDeduction() + { + return Bvg.GetCoordinationDeduction(Bvg.GetPensionMaximum(calculationYear)); + } + } +} diff --git a/src/BvgCalculator/BvgCapitalCalculationHelper.cs b/src/Application/Bvg/BvgCapitalCalculationHelper.cs similarity index 67% rename from src/BvgCalculator/BvgCapitalCalculationHelper.cs rename to src/Application/Bvg/BvgCapitalCalculationHelper.cs index 28eb66f8..6c0bf1a5 100644 --- a/src/BvgCalculator/BvgCapitalCalculationHelper.cs +++ b/src/Application/Bvg/BvgCapitalCalculationHelper.cs @@ -1,11 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using PensionCoach.Tools.BvgCalculator.Models; +using Application.Extensions; +using Domain.Models.Bvg; using PensionCoach.Tools.CommonUtils; using static LanguageExt.Prelude; -namespace PensionCoach.Tools.BvgCalculator +namespace Application.Bvg { /// /// @@ -15,36 +13,35 @@ public static class BvgCapitalCalculationHelper /// /// Gets the retirement capital sequence. /// - /// + /// /// The date of retirement. /// The age BVG. /// The retirement age BVG. /// The i BVG. - /// The actuarial reserve accounting year. + /// The actuarial reserve accounting year. /// The retirement credit sequence. /// public static IReadOnlyCollection GetRetirementCapitalSequence( - DateTime processDate, + int calculationYear, DateTime retirementDate, int ageBvg, int retirementAgeBvg, decimal iBvg, - PredecessorRetirementCapital predecessorCapital, + decimal retirementCapitalEndOfYear, IReadOnlyCollection retirementCreditSequence) { // Begin of financial year = January 1 fo the financial year - DateTime beginOfFinancialYear = new DateTime(processDate.Year, 1, 1); + DateTime beginOfFinancialYear = new DateTime(calculationYear, 1, 1); DateTime endOfFinancialYear = beginOfFinancialYear.AddYears(1); if (retirementDate <= endOfFinancialYear) { - decimal aghBoYProRata = predecessorCapital.BeginOfYearAmount; - decimal aghEoYProRata = predecessorCapital.EndOfYearAmount; + decimal aghEoYProRata = retirementCapitalEndOfYear; RetirementCapital aghProRataBoY = new RetirementCapital(beginOfFinancialYear, - aghBoYProRata, - aghBoYProRata); + decimal.Zero, + decimal.Zero); RetirementCapital aghProRataEoY = new RetirementCapital(endOfFinancialYear, aghEoYProRata, @@ -59,27 +56,21 @@ public static IReadOnlyCollection GetRetirementCapitalSequenc return List(aghProRataEndOfPeriod); } + RetirementCapital retirementCapitalItem = new (endOfFinancialYear, retirementCapitalEndOfYear, retirementCapitalEndOfYear); - RetirementCapital retirementCapitalEndOfYear = - new RetirementCapital( - endOfFinancialYear, - predecessorCapital.EndOfYearAmount, - predecessorCapital.EndOfYearAmount) - .Round(); - - var retirementAssets = List(retirementCapitalEndOfYear); + var retirementAssets = List(retirementCapitalItem); retirementAssets = retirementAssets.AddRange( - GetProjection( - ageBvg + 1, - retirementDate, - endOfFinancialYear, - retirementCapitalEndOfYear, - iBvg, - retirementAgeBvg, - retirementCreditSequence)); - - return retirementAssets.Reverse(); + GetProjection( + ageBvg + 1, + retirementDate, + endOfFinancialYear, + retirementCapitalItem, + iBvg, + retirementAgeBvg, + retirementCreditSequence)); + + return retirementAssets.Reverse(); } /// @@ -112,29 +103,6 @@ public static (decimal ValueD1, decimal ValueD2) InterpolateInterval( return (beginOfPeriodValue, endOfPeriodValue); } - /// - /// Gets the retirement credit sequence. - /// - /// The person details. - /// The process date. - /// - /// Process date {processDate} after date of retirement {dateOfRetirement} - internal static IReadOnlyCollection GetRetirementCreditSequence( - BvgPerson personDetails, - DateTime processDate, - BvgSalary salaryDetails) - { - int xsBvg = BvgCalculator.GetRetirementAge(personDetails.Gender); - int xBvg = personDetails.DateOfBirth.GetBvgAge(processDate.Year); - - BvgRetirementCreditsTable bvgRetirementCreditTable = new BvgRetirementCreditsTable(); - - return Enumerable.Range(xBvg, xsBvg - xBvg + 1) - .Select(x => - new RetirementCredit(bvgRetirementCreditTable.GetRate(x) * salaryDetails.InsuredSalary, x)) - .ToList(); - } - private static List GetProjection( int age, DateTime dateOfRetirement, @@ -182,7 +150,7 @@ private static RetirementCapital CalculateNewProjectedRetirementCapital( // Difference in without interest from without interest by plan: // by plan takes the rounded retirement credits (to 0.1CHF) while // the other calculates with effective credits - decimal x1Capital = xCapital * (1M + iProjection) + retirementCredit.AmountRounded10; + decimal x1Capital = xCapital * (1M + iProjection) + MathUtils.Round10(retirementCredit.AmountRaw); decimal x1CapitalWoI = xCapitalWoI + retirementCredit.AmountRaw; return new RetirementCapital( diff --git a/src/BvgCalculator/BvgRetirementCreditsTable.cs b/src/Application/Bvg/BvgRetirementCreditsTable.cs similarity index 81% rename from src/BvgCalculator/BvgRetirementCreditsTable.cs rename to src/Application/Bvg/BvgRetirementCreditsTable.cs index 7d5dff90..c221305b 100644 --- a/src/BvgCalculator/BvgRetirementCreditsTable.cs +++ b/src/Application/Bvg/BvgRetirementCreditsTable.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; -using static PensionCoach.Tools.CommonUtils.LevelValueDictionaryExtensions; +using static PensionCoach.Tools.CommonUtils.LevelValueDictionaryExtensions; -namespace PensionCoach.Tools.BvgCalculator +namespace Application.Bvg { public class BvgRetirementCreditsTable : IBvgRetirementCredits { diff --git a/src/Application/Bvg/BvgRetirementDateCalculator.cs b/src/Application/Bvg/BvgRetirementDateCalculator.cs new file mode 100644 index 00000000..f78f72f9 --- /dev/null +++ b/src/Application/Bvg/BvgRetirementDateCalculator.cs @@ -0,0 +1,67 @@ +using System.ComponentModel; +using Application.Extensions; +using Domain.Enums; + +namespace Application.Bvg; + +public class BvgRetirementDateCalculator +{ + public DateTime DateOfRetirement(Gender gender, DateTime dateOfBirth) + { + (int Year, int Months) finaleAge = RetirementAge(gender, dateOfBirth); + + return DateOfRetirementByAge(dateOfBirth, finaleAge); + } + + public DateTime DateOfRetirementByAge(DateTime dateOfBirth, (int Year, int Months) finaleAge) + { + return dateOfBirth + .GetBirthdateTechnical() + .AddYears(finaleAge.Year) + .AddMonths(finaleAge.Months); + } + + public (int Years, int Months) RetirementAge(Gender gender, DateTime dateOfBirth) + { + return RetirementAgeInternal(gender, dateOfBirth); + } + + private (int Year, int Months) RetirementAgeInternal(Gender gender, DateTime dateOfBirth) + { + const int lastGenerationBeforeTransition = 1960; + const int firstTransitionGeneration = 1961; + const int secondTransitionGeneration = 1962; + const int thirdTransitionGeneration = 1963; + const int additionalMonthsFirstGeneration = 3; + const int additionalMonthsSecondGeneration = 6; + const int additionalMonthsThirdGeneration = 9; + const int retirementAgeFemaleBeforeTransition = 64; + + if (gender == Gender.Undefined) + { + throw new InvalidEnumArgumentException(nameof(Gender)); + } + + int yearOfBirth = dateOfBirth.Year; + + (int retirementAgeMen, int retirementAgeWomen) = (65, 65); + + if (gender == Gender.Female) + { + if (yearOfBirth > lastGenerationBeforeTransition) + { + return yearOfBirth switch + { + firstTransitionGeneration => (retirementAgeFemaleBeforeTransition, additionalMonthsFirstGeneration), + secondTransitionGeneration => (retirementAgeFemaleBeforeTransition, additionalMonthsSecondGeneration), + thirdTransitionGeneration => (retirementAgeFemaleBeforeTransition, additionalMonthsThirdGeneration), + _ => (retirementAgeWomen, 0) + }; + } + + return (retirementAgeFemaleBeforeTransition, 0); + } + + return (retirementAgeMen, 0); + } +} diff --git a/src/Application/Bvg/BvgRevisionCalculator.cs b/src/Application/Bvg/BvgRevisionCalculator.cs new file mode 100644 index 00000000..1fabad17 --- /dev/null +++ b/src/Application/Bvg/BvgRevisionCalculator.cs @@ -0,0 +1,325 @@ +using Application.Bvg.Models; +using Application.Extensions; +using Domain.Enums; +using Domain.Models.Bvg; +using FluentValidation; +using FluentValidation.Results; +using LanguageExt; +using PensionCoach.Tools.CommonUtils; + +namespace Application.Bvg; + +public class BvgRevisionCalculator( + BvgCalculator bvgCalculator, + BvgRetirementDateCalculator retirementDateCalculator, + IBvgRetirementCredits retirementCredits, + ISavingsProcessProjectionCalculator projectionCalculator, + IValidator bvgPersonValidator) + : IBvgCalculator +{ + private const decimal PensionConversionRate = 0.06M; + private const decimal SalaryThresholdFactor = 0.675M; + private const decimal CoordinationDeductionFactor = 0.2M; + private static readonly DateTime StartOfBvgRevision = new(2026, 1, 1); + + public Either Calculate(int calculationYear, decimal retirementCapitalEndOfYear, BvgPerson person) + { + Option validationResult = bvgPersonValidator.Validate(person); + + return validationResult + .Where(r => !r.IsValid) + .Map>(r => + { + var errorMessageLine = string.Join(";", r.Errors.Select(x => x.ErrorMessage)); + return $"validation failed: {errorMessageLine}"; + }) + .IfNone(true) + .Bind(_ => CalculateInternal(calculationYear, retirementCapitalEndOfYear, person)); + } + + public Either InsuredSalary(int calculationYear, BvgPerson person) + { + if(calculationYear < StartOfBvgRevision.Year) + { + return bvgCalculator.InsuredSalary(calculationYear, person); + } + + bool isRetired = IsRetired(person, new DateTime(calculationYear, 1, 1)); + + if (isRetired && person.DisabilityDegree == decimal.One) + { + return decimal.Zero; + } + + return UnconditionedInsuredSalary(calculationYear, person.ReportedSalary, decimal.One - person.DisabilityDegree); + } + + public Either InsuredSalaries(int calculationYear, BvgPerson person) + { + DateTime technicalBirthdate = person.DateOfBirth.GetBirthdateTechnical(); + TechnicalAge birthdateAsAge = (technicalBirthdate.Year, technicalBirthdate.Month); + DateTime dateOfFinalAge = GetRetirementDate(person.DateOfBirth, person.Gender); + + List salaries = []; + for (DateTime currentDate = new DateTime(calculationYear, 1, 1); currentDate <= dateOfFinalAge; currentDate = currentDate.AddMonths(1)) + { + decimal salary = InsuredSalary(currentDate.Year, person).IfLeft(decimal.Zero); + + TechnicalAge age = TechnicalAge.From(currentDate.Year, currentDate.Month) - birthdateAsAge; + + salaries.Add(new BvgTimeSeriesPoint(age, currentDate, salary)); + } + + return salaries.ToArray(); + } + + public Either RetirementCreditFactors(int calculationYear, BvgPerson person) + { + DateTime technicalBirthdate = person.DateOfBirth.GetBirthdateTechnical(); + TechnicalAge birthdateAsAge = TechnicalAge.From(technicalBirthdate.Year, technicalBirthdate.Month); + DateTime dateOfFinalAge = GetRetirementDate(person.DateOfBirth, person.Gender); + + List points = []; + for (DateTime currentDate = new DateTime(calculationYear, 1, 1); currentDate <= dateOfFinalAge; currentDate = currentDate.AddMonths(1)) + { + int xBvg = currentDate.Year - person.DateOfBirth.Year; + decimal factor = currentDate < StartOfBvgRevision + ? retirementCredits.GetRate(xBvg) + : RetirementCreditFactor(xBvg); + + TechnicalAge age = TechnicalAge.From(currentDate.Year, currentDate.Month) - birthdateAsAge; + + points.Add(new BvgTimeSeriesPoint(age, currentDate, factor)); + } + + return points.ToArray(); + } + + public Either RetirementCredits(int calculationYear, BvgPerson person) + { + return from factors in RetirementCreditFactors(calculationYear, person) + from salaries in InsuredSalaries(calculationYear, person) + select Combine(factors, salaries).ToArray(); + + IEnumerable Combine(BvgTimeSeriesPoint[] factors, BvgTimeSeriesPoint[] salaries) + { + return from f in factors + from s in salaries + where f.Date == s.Date + select f with {Value = f.Value * s.Value}; + } + } + + private Either CalculateInternal(int calculationYear, decimal retirementCapitalEndOfYear, BvgPerson person) + { + DateTime retirementDate = GetRetirementDate(person.DateOfBirth, person.Gender); + + decimal insuredSalary = InsuredSalary(calculationYear, person).IfLeft(() => decimal.Zero); + + decimal retirementCreditFactor = GetRetirementCreditFactor(person, calculationYear); + + BvgTimeSeriesPoint[] retirementCreditSequence = RetirementCredits(calculationYear, person).IfLeft(() => []); + + IReadOnlyCollection retirementCapitalSequence = + GetRetirementCapitalSequence(retirementCapitalEndOfYear, calculationYear, person, retirementCreditSequence); + + DateTime processingDate = new(calculationYear, 1, 1); + + decimal retirementCredit = retirementCreditSequence.SingleOrDefault(item => item.Date == processingDate) switch + { + null => decimal.Zero, + { } item => item.Value + }; + + RetirementCapital finalRetirementCapital = retirementCapitalSequence.SingleOrDefault(item => item.Date == retirementDate); + + decimal actualRetirementCapitalEndOfYear = ActualRetirementCapitalEndOfYear(calculationYear, retirementCapitalSequence, retirementDate); + + decimal finalRetirementCapitalWithInterest = finalRetirementCapital switch + { + null => decimal.Zero, + not null => finalRetirementCapital.Value + }; + + decimal finalRetirementCapitalWithoutInterest = finalRetirementCapital switch + { + null => decimal.Zero, + not null => finalRetirementCapital.ValueWithoutInterest + }; + + decimal retirementPension = RetirementPension(finalRetirementCapitalWithInterest, retirementDate, CurrentBvgCalculatorFunc(retirementCredit)); + + // reset risk benefits to 0 if below salary threshold + decimal disabilityPension = DisabilityPension(finalRetirementCapitalWithoutInterest, retirementDate, CurrentBvgCalculatorFunc(retirementCredit)); + decimal partnerPension = disabilityPension * Bvg.FactorPartnersPension; + decimal childPension = disabilityPension * Bvg.FactorChildPension; + decimal orphanPension = childPension; + + Either result = new BvgCalculationResult + { + DateOfRetirement = GetRetirementDate(person.DateOfBirth, person.Gender), + EffectiveSalary = person.ReportedSalary, + InsuredSalary = insuredSalary, + RetirementCredit = retirementCredit, + RetirementCreditFactor = retirementCreditFactor, + RetirementPension = retirementPension, + RetirementCapitalEndOfYear = actualRetirementCapitalEndOfYear, + FinalRetirementCapital = finalRetirementCapitalWithInterest, + FinalRetirementCapitalWithoutInterest = finalRetirementCapitalWithoutInterest, + DisabilityPension = disabilityPension, + PartnerPension = partnerPension, + OrphanPension = orphanPension, + ChildPensionForDisabled = childPension, + RetirementCreditSequence = retirementCreditSequence.Select(item => new RetirementCredit(item.Value, item.Age.Years)).ToList(), + RetirementCapitalSequence = retirementCapitalSequence + }; + + return result; + + Func CurrentBvgCalculatorFunc(decimal currentRetirementCapitalEndOfYear) + { + return () => bvgCalculator.Calculate(calculationYear, currentRetirementCapitalEndOfYear, person).IfLeft(() => null); + } + } + + private static decimal ActualRetirementCapitalEndOfYear(int calculationYear, + IReadOnlyCollection retirementCapitalSequence, DateTime retirementDate) + { + DateTime followingYearDate = new DateTime(calculationYear + 1, 1, 1); + + RetirementCapital match = retirementCapitalSequence.SingleOrDefault(item => item.Date == followingYearDate); + + if (match is null) + { + if (followingYearDate > retirementDate) + { + return retirementCapitalSequence + .Where(item => item.Date.Year == calculationYear) + .MaxBy(item => item.Date) + .Value; + } + return decimal.Zero; + } + + return match.Value; + } + + private static decimal DisabilityPension(decimal finalRetirementCapitalWithoutInterest, DateTime retirementDate, Func currentBvgCalculatorFunc) + { + if (retirementDate < StartOfBvgRevision) + { + return currentBvgCalculatorFunc().DisabilityPension; + } + + return finalRetirementCapitalWithoutInterest * PensionConversionRate; + } + + public bool IsRetired(BvgPerson person, DateTime dateOfProcess) + { + DateTime retiredAt = GetRetirementDate(person.DateOfBirth, person.Gender); + + return retiredAt.AddDays(-1) < dateOfProcess.Date; + } + + public DateTime GetRetirementDate(DateTime dateOfBirth, Gender gender) + { + return retirementDateCalculator.DateOfRetirement(gender, dateOfBirth); + } + + public TechnicalAge GetRetirementAge(Gender typeOfGender, DateTime birthdate) + { + (int years, int months) = retirementDateCalculator.RetirementAge(typeOfGender, birthdate); + + return TechnicalAge.From(years, months); + } + + private decimal RetirementPension(decimal finalRetirementCapital, DateTime retirementDate, Func currentBvgCalculatorFunc) + { + if(retirementDate < StartOfBvgRevision) + { + return currentBvgCalculatorFunc().RetirementPension; + } + + return finalRetirementCapital * PensionConversionRate; + } + + private IReadOnlyCollection GetRetirementCapitalSequence( + decimal retirementCapitalEndOfYear, + int calculationYear, + BvgPerson personDetails, + BvgTimeSeriesPoint[] retirementCreditSequence) + { + // Date of retirement + DateTime dateOfRetirement = GetRetirementDate(personDetails.DateOfBirth, personDetails.Gender); + + // Interest rates + decimal iBvg = Bvg.GetInterestRate(calculationYear); + + // Retirement assets at end of insurance period Bvg portion + TechnicalAge retirementAgeBvg = GetRetirementAge(personDetails.Gender, personDetails.DateOfBirth); + + Func retirementCreditGetter = age => + { + return retirementCreditSequence.SingleOrDefault(p => p.Age == age)?.Value ?? decimal.Zero; + }; + + if (IsRetired(personDetails, new(calculationYear+1, 1, 1))) + { + return [new RetirementCapital(dateOfRetirement, retirementCapitalEndOfYear, retirementCapitalEndOfYear)]; + } + + return projectionCalculator.ProjectionTable( + iBvg, + dateOfRetirement, + dateOfRetirement, + retirementAgeBvg, + retirementAgeBvg, + calculationYear+1, + retirementCapitalEndOfYear, + retirementCreditGetter) + .Select(item => new RetirementCapital(item.DateOfCalculation, item.RetirementCapital, item.RetirementCapitalWithoutInterest)) + .ToArray(); + } + + + private decimal UnconditionedInsuredSalary(int calculationYear, decimal reportedSalary, decimal quota) + { + decimal ahvMax = Bvg.GetPensionMaximum(calculationYear); + return Prelude.Some(MathUtils.Round(reportedSalary)) + + // scale salary up + .Map(salary => salary / quota) + + // check salary entrance level + .Where(v => v > ahvMax * SalaryThresholdFactor) + + .Map(v => Math.Min(v, Bvg.GetMaximalInsurableSalary(ahvMax))) + .Map(v => Math.Min(v, Bvg.GetMaximumSalary(ahvMax))) + + // reduce by coordination deduction + .Map(v => v * (decimal.One - CoordinationDeductionFactor)) + + // restrict by BVG salary max + .Map(v => v * quota) + .Map(MathUtils.Round5) + .IfNone(decimal.Zero); + } + + private decimal GetRetirementCreditFactor(BvgPerson person, int calculationYear) + { + int xBvg = calculationYear - person.DateOfBirth.Year; + + return retirementCredits.GetRate(xBvg); + } + + private static decimal RetirementCreditFactor(int xBvg) + { + return xBvg switch + { + > 24 and <= 44 => 0.09M, + > 44 and <= 65 => 0.14M, + _ => 0 + }; + } + +} diff --git a/src/Application/Bvg/BvgRevisionPensionSupplementCalculator.cs b/src/Application/Bvg/BvgRevisionPensionSupplementCalculator.cs new file mode 100644 index 00000000..4d15db80 --- /dev/null +++ b/src/Application/Bvg/BvgRevisionPensionSupplementCalculator.cs @@ -0,0 +1,33 @@ +namespace Application.Bvg; + +public class BvgRevisionPensionSupplementCalculator : IPensionSupplementCalculator +{ + public decimal CalculatePensionSupplement( + DateTime dateOfBirth, + decimal finalRetirementCapital) + { + const decimal lBound = 220500; + const decimal uBound = lBound * 2M; + + const decimal pensionLowerBound = 1200; + const decimal pensionMediumBound = pensionLowerBound * 1.5M; + const decimal pensionUpperBound = pensionLowerBound * 2M; + + decimal s = decimal.One - (finalRetirementCapital - lBound) / (uBound - lBound); + + decimal pensionIncrease = (dateOfBirth.Year, finalRetirementCapital) switch + { + (_, > uBound) => decimal.Zero, + ( < 1961, _) => decimal.Zero, + ( >= 1961 and <= 1965, <= lBound) => pensionUpperBound, + ( <= 1970, <= lBound) => pensionMediumBound, + ( <= 1975, <= lBound) => pensionLowerBound, + ( >= 1961 and <= 1965, < uBound) => pensionUpperBound * s, + ( <= 1970, < uBound) => pensionMediumBound * s, + ( <= 1975, < uBound) => pensionLowerBound * s, + _ => decimal.Zero + }; + + return pensionIncrease; + } +} diff --git a/src/Application/Bvg/IBvgCalculator.cs b/src/Application/Bvg/IBvgCalculator.cs new file mode 100644 index 00000000..f7aeb13d --- /dev/null +++ b/src/Application/Bvg/IBvgCalculator.cs @@ -0,0 +1,18 @@ +using Application.Bvg.Models; +using Domain.Models.Bvg; +using LanguageExt; + +namespace Application.Bvg; + +public interface IBvgCalculator +{ + Either Calculate(int calculationYear, decimal retirementCapitalEndOfYear, BvgPerson person); + + Either InsuredSalary(int calculationYear, BvgPerson person); + + Either InsuredSalaries(int calculationYear, BvgPerson person); + + Either RetirementCreditFactors(int calculationYear, BvgPerson person); + + Either RetirementCredits(int calculationYear, BvgPerson person); +} diff --git a/src/Application/Bvg/IBvgRetirementCreditsTable.cs b/src/Application/Bvg/IBvgRetirementCreditsTable.cs new file mode 100644 index 00000000..02445853 --- /dev/null +++ b/src/Application/Bvg/IBvgRetirementCreditsTable.cs @@ -0,0 +1,8 @@ +namespace Application.Bvg; + +public interface IBvgRetirementCredits +{ + decimal GetRateInPercentage(int bvgAge); + + decimal GetRate(int bvgAge); +} diff --git a/src/Application/Bvg/IPensionSupplementCalculator.cs b/src/Application/Bvg/IPensionSupplementCalculator.cs new file mode 100644 index 00000000..11e476b7 --- /dev/null +++ b/src/Application/Bvg/IPensionSupplementCalculator.cs @@ -0,0 +1,6 @@ +namespace Application.Bvg; + +public interface IPensionSupplementCalculator +{ + decimal CalculatePensionSupplement(DateTime dateOfBirth, decimal finalRetirementCapital); +} diff --git a/src/Application/Bvg/ISavingsProcessProjectionCalculator.cs b/src/Application/Bvg/ISavingsProcessProjectionCalculator.cs new file mode 100644 index 00000000..704e5558 --- /dev/null +++ b/src/Application/Bvg/ISavingsProcessProjectionCalculator.cs @@ -0,0 +1,17 @@ +using Application.Bvg.Models; +using Domain.Models.Bvg; + +namespace Application.Bvg; + +public interface ISavingsProcessProjectionCalculator +{ + RetirementSavingsProcessResult[] ProjectionTable( + decimal projectionInterestRate, + DateTime dateOfRetirement, + DateTime dateOfEndOfSavings, + TechnicalAge retirementAge, + TechnicalAge finalAge, + int yearOfBeginProjection, + decimal beginOfRetirementCapital, + Func retirementCreditGetter); +} diff --git a/src/Application/Bvg/Models/BvgDate.cs b/src/Application/Bvg/Models/BvgDate.cs new file mode 100644 index 00000000..b08b2264 --- /dev/null +++ b/src/Application/Bvg/Models/BvgDate.cs @@ -0,0 +1,29 @@ +namespace Application.Bvg.Models; + +/// +/// Represents a date in the BVG context able to distinguish begin and end of day date (time 00:00 vs 24:00). +/// eg. 17.09.2021 00:00 vs 17.09.2021 24:00 but one is the begin and the other the end of the day. +/// +/// +/// +public readonly record struct BvgDate(DateTime DateTime, bool IsEndOfDay) +{ + // construct a BvgDate from a DateTime + public BvgDate(DateOnly date, bool isEndOfDay=false) : this(date.ToDateTime(TimeOnly.MinValue), isEndOfDay) + {} + + public static bool operator <(BvgDate left, BvgDate right) => left.DateTime < right.DateTime || (left.DateTime == right.DateTime && !left.IsEndOfDay && right.IsEndOfDay); + + public static bool operator >(BvgDate left, BvgDate right) => left.DateTime > right.DateTime || (left.DateTime == right.DateTime && left.IsEndOfDay && !right.IsEndOfDay); + + public static bool operator <=(BvgDate left, BvgDate right) => !(left > right); + + public static bool operator >=(BvgDate left, BvgDate right) => !(left < right); + + public static implicit operator BvgDate(DateTime dateTime) + { + return new BvgDate(dateTime, false); + } + + public DateTime ToDateTime() => IsEndOfDay ? DateTime.AddDays(1) : DateTime; +} diff --git a/src/BvgCalculator/Models/BvgSalary.cs b/src/Application/Bvg/Models/BvgSalary.cs similarity index 80% rename from src/BvgCalculator/Models/BvgSalary.cs rename to src/Application/Bvg/Models/BvgSalary.cs index 467e9184..4331973b 100644 --- a/src/BvgCalculator/Models/BvgSalary.cs +++ b/src/Application/Bvg/Models/BvgSalary.cs @@ -1,4 +1,4 @@ -namespace PensionCoach.Tools.BvgCalculator.Models +namespace Application.Bvg.Models { internal class BvgSalary { @@ -7,4 +7,4 @@ internal class BvgSalary public decimal InsuredSalary { get; set; } public decimal CoordinationDeduction { get; set; } } -} \ No newline at end of file +} diff --git a/src/Application/Bvg/Models/BvgTimeSeriesPoint.cs b/src/Application/Bvg/Models/BvgTimeSeriesPoint.cs new file mode 100644 index 00000000..af01ab31 --- /dev/null +++ b/src/Application/Bvg/Models/BvgTimeSeriesPoint.cs @@ -0,0 +1,5 @@ +using Domain.Models.Bvg; + +namespace Application.Bvg.Models; + +public record BvgTimeSeriesPoint(TechnicalAge Age, DateTime Date, decimal Value); diff --git a/src/Application/Bvg/Models/RetirementSavingsProcessResult.cs b/src/Application/Bvg/Models/RetirementSavingsProcessResult.cs new file mode 100644 index 00000000..3bed8c34 --- /dev/null +++ b/src/Application/Bvg/Models/RetirementSavingsProcessResult.cs @@ -0,0 +1,17 @@ +using Domain.Models.Bvg; + +namespace Application.Bvg.Models; + +public record RetirementSavingsProcessResult( + DateTime DateOfCalculation, + int BvgAge, + TechnicalAge TechnicalAge, + decimal ProRatedFactor, + decimal GrossInterestRate, + decimal RetirementCredit, + decimal RetirementCapitalWithoutInterest, + decimal RetirementCapital, + bool IsRetirementDate, + bool IsEndOfSavings, + bool IsFullYear, + bool IsFullAge); diff --git a/src/Application/Bvg/SingleSavingsProcessProjectionCalculator.cs b/src/Application/Bvg/SingleSavingsProcessProjectionCalculator.cs new file mode 100644 index 00000000..ef46077b --- /dev/null +++ b/src/Application/Bvg/SingleSavingsProcessProjectionCalculator.cs @@ -0,0 +1,152 @@ +using Domain.Models.Bvg; +using Application.Bvg.Models; + +namespace Application.Bvg; + +public class SingleSavingsProcessProjectionCalculator : ISavingsProcessProjectionCalculator +{ + public RetirementSavingsProcessResult[] ProjectionTable( + decimal projectionInterestRate, + DateTime dateOfRetirement, + DateTime dateOfEndOfSavings, + TechnicalAge retirementAge, + TechnicalAge finalAge, + int yearOfBeginProjection, + decimal beginOfRetirementCapital, + Func retirementCreditGetter) + { + // Values are only projected to final age + // Note: + // person might retired in between, or even before the + // begin of the projection period. + DateTime dateOfTechnicalBirth = dateOfRetirement + .AddMonths(-retirementAge.Months) + .AddYears(-retirementAge.Years); + + DateTime dateOfFinalAge = dateOfTechnicalBirth + .AddMonths(finalAge.Months) + .AddYears(finalAge.Years); + + DateTime startingDate = new(yearOfBeginProjection, 1, 1); + + if (startingDate > dateOfFinalAge) + { + return []; + } + + List results = []; + + // AGH at begin + decimal aghoz = beginOfRetirementCapital; + decimal aghmz = beginOfRetirementCapital; + + TechnicalAge birthdateAsAge = TechnicalAge.From(dateOfTechnicalBirth.Year, dateOfTechnicalBirth.Month); + TechnicalAge startingAge = TechnicalAge.From(startingDate.Year, startingDate.Month) - birthdateAsAge; + + decimal agsj = retirementCreditGetter(startingAge); + + RetirementSavingsProcessResult lastResult = new( + DateOfCalculation: startingDate, + BvgAge: startingDate.Year - dateOfTechnicalBirth.Year, + TechnicalAge: startingAge, + ProRatedFactor: decimal.One, + GrossInterestRate: projectionInterestRate, + RetirementCredit: agsj, + RetirementCapitalWithoutInterest: beginOfRetirementCapital, + RetirementCapital: beginOfRetirementCapital, + dateOfRetirement == startingDate, + false, + IsFullYear: true, + startingAge.Months == 0); + + results.Add(lastResult); + + int offset = 1; + TechnicalAge currentAge = startingAge; + for (DateTime currentDate = startingDate.AddMonths(1); currentDate <= dateOfFinalAge; currentDate = currentDate.AddMonths(1)) + { + bool isEndOfYear = offset % 12 == 0; + + currentAge += TechnicalAge.From(0,1); + + // pro-rated factor + decimal phi = offset / 12M; + + decimal agsPhi = agsj * phi; + + decimal aghozPhi = aghoz + agsPhi; + decimal aghmzPhi = aghmz * (1M + phi * projectionInterestRate) + agsPhi; + + lastResult = new RetirementSavingsProcessResult( + DateOfCalculation: currentDate, + BvgAge: currentDate.Year - dateOfTechnicalBirth.Year, + TechnicalAge: currentAge, + ProRatedFactor: phi, + GrossInterestRate: projectionInterestRate, + RetirementCredit: agsPhi, + RetirementCapitalWithoutInterest: aghozPhi, + RetirementCapital: aghmzPhi, + dateOfRetirement == currentDate, + dateOfEndOfSavings == currentDate, + IsFullYear: isEndOfYear, + currentAge.Months == 0); + + results.Add(lastResult); + + if (isEndOfYear) + { + aghoz += agsj; + aghmz = aghmz * (1M + projectionInterestRate) + agsj; + offset = 0; + } + + agsj = retirementCreditGetter(currentAge); + + offset++; + } + + int releaseOffset = 1; + aghoz = lastResult.RetirementCapitalWithoutInterest; + aghmz = lastResult.RetirementCapital; + for (DateTime currentDate = dateOfRetirement.AddMonths(1); currentDate <= dateOfEndOfSavings; currentDate = currentDate.AddMonths(1)) + { + bool isEndOfYear = offset % 12 == 0; + + currentAge += TechnicalAge.From(0, 1); + + decimal phi = releaseOffset / 12M; + + decimal aghozPhi = aghoz; + decimal aghmzPhi = aghmz * (1M + phi * projectionInterestRate); + + lastResult = new RetirementSavingsProcessResult( + DateOfCalculation: currentDate, + BvgAge: currentDate.Year - dateOfTechnicalBirth.Year, + TechnicalAge: currentAge, + ProRatedFactor: phi, + GrossInterestRate: projectionInterestRate, + RetirementCredit: decimal.Zero, + RetirementCapitalWithoutInterest: aghozPhi, + RetirementCapital: aghmzPhi, + dateOfRetirement == currentDate, + dateOfEndOfSavings == currentDate, + IsFullYear: isEndOfYear, + currentAge.Months == 0); + + results.Add(lastResult); + + if (isEndOfYear) + { + aghmz *= (1M + projectionInterestRate * phi); + offset = 0; + releaseOffset = 0; + } + + offset++; + releaseOffset++; + } + + return results.ToArray(); + } +} + diff --git a/src/Application/Extensions/BvgCalculatorsCollectionExtensions.cs b/src/Application/Extensions/BvgCalculatorsCollectionExtensions.cs new file mode 100644 index 00000000..c54294de --- /dev/null +++ b/src/Application/Extensions/BvgCalculatorsCollectionExtensions.cs @@ -0,0 +1,24 @@ +using Application.Bvg; +using Application.Validators; +using Domain.Models.Bvg; +using FluentValidation; +using Microsoft.Extensions.DependencyInjection; + +namespace Application.Extensions +{ + public static class BvgCalculatorsCollectionExtensions + { + public static void AddBvgCalculators(this IServiceCollection serviceCollection) + { + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + + serviceCollection.AddSingleton, BvgPersonValidator>(); + } + } +} diff --git a/src/BvgCalculator/BvgRetirementCapitalCalculationExtensions.cs b/src/Application/Extensions/BvgRetirementCapitalCalculationExtensions.cs similarity index 89% rename from src/BvgCalculator/BvgRetirementCapitalCalculationExtensions.cs rename to src/Application/Extensions/BvgRetirementCapitalCalculationExtensions.cs index f4cf0146..c3dd757e 100644 --- a/src/BvgCalculator/BvgRetirementCapitalCalculationExtensions.cs +++ b/src/Application/Extensions/BvgRetirementCapitalCalculationExtensions.cs @@ -1,8 +1,7 @@ -using System; -using PensionCoach.Tools.BvgCalculator.Models; +using Domain.Models.Bvg; using PensionCoach.Tools.CommonUtils; -namespace PensionCoach.Tools.BvgCalculator +namespace Application.Extensions { public static class BvgCalculationExtensions { diff --git a/src/Application/Extensions/OccupationalBenefitsDateExtensions.cs b/src/Application/Extensions/OccupationalBenefitsDateExtensions.cs new file mode 100644 index 00000000..d80b5c65 --- /dev/null +++ b/src/Application/Extensions/OccupationalBenefitsDateExtensions.cs @@ -0,0 +1,54 @@ +using Domain.Enums; +using Domain.Models.Bvg; + +namespace Application.Extensions; + +/// +/// Extension methods related to date calculations in +/// the context of occupational benefits (Vorsorge) +/// +public static class OccupationalBenefitsDateExtensions +{ + public static DateTime GetRetirementDate(this DateTime birthdate, int retirementAge) + { + + // Date of retirement + return birthdate + .GetBirthdateTechnical() + .AddYears(retirementAge); + } + + public static DateTime GetRetirementDate(this DateTime birthdate, Gender gender) + { + // FinalAgeByPlan BVG + int xsBvg = Bvg.Bvg.GetRetirementAge(gender); + + // Date of retirement + return GetRetirementDate(birthdate, xsBvg); + } + + /// + /// Gets the birthdate technical: first day of following month. + /// + /// The birthdate. + /// + public static DateTime GetBirthdateTechnical(this DateTime birthdate) + { + return new DateTime(birthdate.Year, birthdate.Month, 1).AddMonths(1); + } + + public static int GetBvgAge(this DateTime birthdate, int calculationYear) + { + return calculationYear - birthdate.Year; + } + + public static DateTime BeginOfYear(this DateTime date) + { + return new DateTime(date.Year, 1, 1); + } + + public static DateTime EndOfYear(this DateTime date) + { + return new DateTime(date.Year, 1, 1).AddYears(1); + } +} diff --git a/src/Application/Features/CheckSettings/ICheckSettingsConnector.cs b/src/Application/Features/CheckSettings/ICheckSettingsConnector.cs new file mode 100644 index 00000000..abe8110f --- /dev/null +++ b/src/Application/Features/CheckSettings/ICheckSettingsConnector.cs @@ -0,0 +1,7 @@ +namespace Application.Features.CheckSettings +{ + public interface ICheckSettingsConnector + { + Task> GetAsync(); + } +} diff --git a/src/Application/Features/FullTaxCalculation/ITaxCalculatorConnector.cs b/src/Application/Features/FullTaxCalculation/ITaxCalculatorConnector.cs new file mode 100644 index 00000000..7bb4850f --- /dev/null +++ b/src/Application/Features/FullTaxCalculation/ITaxCalculatorConnector.cs @@ -0,0 +1,16 @@ +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Features.FullTaxCalculation +{ + public interface ITaxCalculatorConnector + { + Task> CalculateAsync( + int calculationYear, int bfsMunicipalityId, TaxPerson person, bool withMaxAvailableCalculationYear = false); + + Task> CalculateAsync( + int calculationYear, int bfsMunicipalityId, CapitalBenefitTaxPerson person, bool withMaxAvailableCalculationYear = false); + + Task GetSupportedTaxYears(); + } +} diff --git a/src/Application/Features/FullTaxCalculation/ITaxSupportedYearProvider.cs b/src/Application/Features/FullTaxCalculation/ITaxSupportedYearProvider.cs new file mode 100644 index 00000000..a0914aed --- /dev/null +++ b/src/Application/Features/FullTaxCalculation/ITaxSupportedYearProvider.cs @@ -0,0 +1,9 @@ +namespace Application.Features.FullTaxCalculation +{ + public interface ITaxSupportedYearProvider + { + int[] GetSupportedTaxYears(); + + int MapToSupportedYear(int taxYear); + } +} diff --git a/src/Application/Features/FullTaxCalculation/TaxCalculatorConnector.cs b/src/Application/Features/FullTaxCalculation/TaxCalculatorConnector.cs new file mode 100644 index 00000000..d0a502ed --- /dev/null +++ b/src/Application/Features/FullTaxCalculation/TaxCalculatorConnector.cs @@ -0,0 +1,56 @@ +using Application.Municipality; +using Application.Tax.Contracts; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Features.FullTaxCalculation +{ + public class TaxCalculatorConnector : ITaxCalculatorConnector + { + private readonly int[] supportedTaxYears = { 2019 }; + + private readonly IFullWealthAndIncomeTaxCalculator fullWealthAndIncomeTaxCalculator; + private readonly IFullCapitalBenefitTaxCalculator fullCapitalBenefitTaxCalculator; + private readonly IMunicipalityConnector municipalityResolver; + + public TaxCalculatorConnector( + IFullWealthAndIncomeTaxCalculator fullWealthAndIncomeTaxCalculator, + IFullCapitalBenefitTaxCalculator fullCapitalBenefitTaxCalculator, + IMunicipalityConnector municipalityResolver) + { + this.fullWealthAndIncomeTaxCalculator = fullWealthAndIncomeTaxCalculator; + this.fullCapitalBenefitTaxCalculator = fullCapitalBenefitTaxCalculator; + this.municipalityResolver = municipalityResolver; + } + + public async Task> CalculateAsync( + int calculationYear, int bfsMunicipalityId, TaxPerson person, bool withMaxAvailableCalculationYear = false) + { + Either municipalityData = + await municipalityResolver.GetAsync(bfsMunicipalityId, calculationYear); + + return await municipalityData + .BindAsync(m => fullWealthAndIncomeTaxCalculator.CalculateAsync( + calculationYear, m, person)); + } + + public async Task> CalculateAsync( + int calculationYear, int bfsMunicipalityId, CapitalBenefitTaxPerson person, bool withMaxAvailableCalculationYear = false) + { + Either municipalityData = + await municipalityResolver.GetAsync(bfsMunicipalityId, calculationYear); + + return await municipalityData + .BindAsync(m => fullCapitalBenefitTaxCalculator.CalculateAsync( + calculationYear, + m, + person)); + } + + public Task GetSupportedTaxYears() + { + return supportedTaxYears.AsTask(); + } + } +} diff --git a/src/TaxCalculator/TaxCalculatorServiceCollectionExtensions.cs b/src/Application/Features/FullTaxCalculation/TaxCalculatorServiceCollectionExtensions.cs similarity index 84% rename from src/TaxCalculator/TaxCalculatorServiceCollectionExtensions.cs rename to src/Application/Features/FullTaxCalculation/TaxCalculatorServiceCollectionExtensions.cs index e9a189e4..99a07256 100644 --- a/src/TaxCalculator/TaxCalculatorServiceCollectionExtensions.cs +++ b/src/Application/Features/FullTaxCalculation/TaxCalculatorServiceCollectionExtensions.cs @@ -1,28 +1,28 @@ -using System; +using Application.Features.MarginalTaxCurve; +using Application.Mapping; +using Application.Municipality; +using Application.Tax.Contracts; +using Application.Tax.Estv; +using Application.Tax.Mock; +using Application.Tax.Proprietary; +using Application.Tax.Proprietary.Basis.CapitalBenefit; +using Application.Tax.Proprietary.Basis.Income; +using Application.Tax.Proprietary.Basis.Wealth; +using Application.Tax.Proprietary.Contracts; +using Application.Tax.Proprietary.Models; +using Application.Validators; using AutoMapper; +using Domain.Enums; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; using FluentValidation; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.CommonUtils; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -using PensionCoach.Tools.TaxCalculator.Basis.CapitalBenefit; -using PensionCoach.Tools.TaxCalculator.Basis.Income; -using PensionCoach.Tools.TaxCalculator.Basis.Wealth; -using PensionCoach.Tools.TaxCalculator.Estv; -using PensionCoach.Tools.TaxCalculator.Mapping; -using PensionCoach.Tools.TaxCalculator.Mock; -using PensionCoach.Tools.TaxCalculator.Proprietary; -using PensionCoach.Tools.TaxCalculator.Validators; -namespace PensionCoach.Tools.TaxCalculator +namespace Application.Features.FullTaxCalculation { public static class TaxCalculatorServiceCollectionExtensions { - public static void AddTaxCalculators(this IServiceCollection collection, IConfiguration configuration) + public static void AddTaxCalculators(this IServiceCollection collection, ApplicationMode applicationMode) { collection.AddTransient(); collection.AddTransient(); @@ -32,10 +32,8 @@ public static void AddTaxCalculators(this IServiceCollection collection, IConfig collection.AddTransient(); collection.AddTransient(); collection.AddTransient(); - collection.AddTransient(); - collection.AddTransient(); - collection.AddFullTaxCalculators(configuration); + collection.AddFullTaxCalculators(applicationMode); var mappingConfig = new MapperConfiguration(mc => { @@ -51,11 +49,9 @@ public static void AddTaxCalculators(this IServiceCollection collection, IConfig collection.AddCantonCapitalBenefitTaxCalculatorFactory(); } - private static void AddFullTaxCalculators(this IServiceCollection collection, IConfiguration configuration) + private static void AddFullTaxCalculators(this IServiceCollection collection, ApplicationMode applicationMode) { - ApplicationMode typeOfTaxCalculator = configuration.GetApplicationMode(); - - switch (typeOfTaxCalculator) + switch (applicationMode) { case ApplicationMode.Proprietary: collection.AddTransient(); diff --git a/src/TaxCalculator.Abstractions/IMarginalTaxCurveCalculatorConnector.cs b/src/Application/Features/MarginalTaxCurve/IMarginalTaxCurveCalculatorConnector.cs similarity index 70% rename from src/TaxCalculator.Abstractions/IMarginalTaxCurveCalculatorConnector.cs rename to src/Application/Features/MarginalTaxCurve/IMarginalTaxCurveCalculatorConnector.cs index 2a7fd0d2..9478d925 100644 --- a/src/TaxCalculator.Abstractions/IMarginalTaxCurveCalculatorConnector.cs +++ b/src/Application/Features/MarginalTaxCurve/IMarginalTaxCurveCalculatorConnector.cs @@ -1,9 +1,8 @@ -using LanguageExt; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using System.Threading.Tasks; +using Application.Tax.Proprietary.Models; +using Domain.Models.Tax; +using LanguageExt; -namespace PensionCoach.Tools.TaxCalculator.Abstractions; +namespace Application.Features.MarginalTaxCurve; public interface IMarginalTaxCurveCalculatorConnector { diff --git a/src/Application/Features/MarginalTaxCurve/MarginalTaxCurveCalculatorConnector.cs b/src/Application/Features/MarginalTaxCurve/MarginalTaxCurveCalculatorConnector.cs new file mode 100644 index 00000000..d5118be6 --- /dev/null +++ b/src/Application/Features/MarginalTaxCurve/MarginalTaxCurveCalculatorConnector.cs @@ -0,0 +1,290 @@ +using Application.Municipality; +using Application.Tax.Contracts; +using Application.Tax.Proprietary.Models; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using LanguageExt; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace Application.Features.MarginalTaxCurve; + +public class MarginalTaxCurveCalculatorConnector : IMarginalTaxCurveCalculatorConnector +{ + private readonly IFullWealthAndIncomeTaxCalculator fullWealthAndIncomeTaxCalculator; + private readonly IFullCapitalBenefitTaxCalculator fullCapitalBenefitTaxCalculator; + private readonly IMunicipalityConnector municipalityResolver; + + public MarginalTaxCurveCalculatorConnector( + IFullWealthAndIncomeTaxCalculator fullWealthAndIncomeTaxCalculator, + IFullCapitalBenefitTaxCalculator fullCapitalBenefitTaxCalculator, + IMunicipalityConnector municipalityResolver) + { + this.fullWealthAndIncomeTaxCalculator = fullWealthAndIncomeTaxCalculator; + this.fullCapitalBenefitTaxCalculator = fullCapitalBenefitTaxCalculator; + this.municipalityResolver = municipalityResolver; + } + + public async Task> CalculateIncomeTaxCurveAsync( + int calculationYear, + int bfsMunicipalityId, + TaxPerson person, + int lowerLimit, + int upperLimit, + int numberOfSamples) + { + MarginalTaxCurveResult result = new(); + + Either municipalityResult = await municipalityResolver + .GetAsync(bfsMunicipalityId, calculationYear); + + (await municipalityResult + .MapAsync(model => CalculateInternalAsync(model, person))) + .Iter(t => result.MarginalTaxCurve = t); + + (await municipalityResult + .MapAsync(municipalityModel => CalculateSingleMarginalTaxRate(municipalityModel, person))) + .Iter(taxRate => taxRate.Iter(rate => result.CurrentMarginalTaxRate = rate)); + + Merge(result); + + return result; + + void Merge(MarginalTaxCurveResult beforeMergeResult) + { + if (beforeMergeResult.CurrentMarginalTaxRate is null) + { + return; + } + + if (beforeMergeResult.MarginalTaxCurve.All(p => + p.Salary != beforeMergeResult.CurrentMarginalTaxRate.Salary)) + { + beforeMergeResult.MarginalTaxCurve.Add(beforeMergeResult.CurrentMarginalTaxRate); + } + } + + async Task> CalculateInternalAsync( + MunicipalityModel municipalityModel, TaxPerson taxPerson) + { + List incomeTaxes = new(); + + decimal previousMarginalTaxRate = 0; + foreach (decimal currentSalary in LinearSpace(lowerLimit, upperLimit, numberOfSamples)) + { + var currentPerson = taxPerson with { TaxableIncome = currentSalary, TaxableFederalIncome = currentSalary }; + + (await CalculateSingleMarginalTaxRate(municipalityModel, currentPerson)) + .Iter(r => + { + if (r.Rate > previousMarginalTaxRate) + { + incomeTaxes.Add(new MarginalTaxInfo(r.Salary, r.Rate, r.TotalTaxAmount)); + previousMarginalTaxRate = r.Rate; + } + }); + } + + return incomeTaxes; + } + + async Task> CalculateSingleMarginalTaxRate( + MunicipalityModel municipalityModel, TaxPerson taxPerson) + { + const decimal delta = 1000M; + + var x0Person = person with + { + TaxableIncome = taxPerson.TaxableIncome, TaxableFederalIncome = taxPerson.TaxableFederalIncome + }; + + Either tax0 = + await fullWealthAndIncomeTaxCalculator.CalculateAsync(calculationYear, municipalityModel, x0Person); + + var x1Person = taxPerson with + { + TaxableIncome = taxPerson.TaxableIncome + delta, + TaxableFederalIncome = taxPerson.TaxableFederalIncome + delta + }; + + Either tax1 = + await fullWealthAndIncomeTaxCalculator.CalculateAsync(calculationYear, municipalityModel, x1Person); + + var r = + from t0 in tax0 + from t1 in tax1 + select new MarginalTaxInfo( + taxPerson.TaxableIncome, + (t1.TotalTaxAmount - t0.TotalTaxAmount) / delta, + t0.TotalTaxAmount); + + return r; + } + } + + public async Task> CalculateCapitalBenefitTaxCurveAsync( + int calculationYear, + int bfsMunicipalityId, + CapitalBenefitTaxPerson person, + int lowerLimit, + int upperLimit, + int numberOfSamples) + { + MarginalTaxCurveResult result = new(); + + Either municipalityResult = await municipalityResolver + .GetAsync(bfsMunicipalityId, calculationYear); + + (await municipalityResult + .MapAsync(model => CalculateInternalAsync(model, person))) + .Iter(t => result.MarginalTaxCurve = t); + + (await municipalityResult + .MapAsync(model => CalculateSingleMarginalTaxRate(model, person))) + .Iter(taxRate => taxRate.Iter(rate => result.CurrentMarginalTaxRate = rate)); + + // Create pairs of tax rates from the curve and check if the current tax rate is between them. + // If it is, adjust the current tax rate to the one from the curve. + foreach (var pair in result.MarginalTaxCurve.Zip(result.MarginalTaxCurve.Skip(1))) + { + if (pair.Item1.Salary <= result.CurrentMarginalTaxRate.Salary && + pair.Item2.Salary > result.CurrentMarginalTaxRate.Salary) + { + result.CurrentMarginalTaxRate = result.CurrentMarginalTaxRate with { Rate = pair.Item1.Rate }; + } + } + + + return result; + + async Task> CalculateSingleMarginalTaxRate( + MunicipalityModel municipalityModel, CapitalBenefitTaxPerson taxPerson) + { + decimal delta = 1000M; + + Either tax0 = + await fullCapitalBenefitTaxCalculator.CalculateAsync(calculationYear, municipalityModel, taxPerson); + + var x1Person = taxPerson with { TaxableCapitalBenefits = taxPerson.TaxableCapitalBenefits + delta }; + + Either tax1 = + await fullCapitalBenefitTaxCalculator.CalculateAsync(calculationYear, municipalityModel, x1Person); + + Either r = + from t0 in tax0 + from t1 in tax1 + select new MarginalTaxInfo( + taxPerson.TaxableCapitalBenefits, + (t1.TotalTaxAmount - t0.TotalTaxAmount) / delta, + t0.TotalTaxAmount); + + return r; + } + + async Task> CalculateInternalAsync( + MunicipalityModel municipalityModel, CapitalBenefitTaxPerson taxPerson) + { + int currentSalary = (int)taxPerson.TaxableCapitalBenefits; + int[] linearSpace = LinearSpace(lowerLimit, upperLimit, numberOfSamples); + int currentValueIndex = SortedIndex(linearSpace, currentSalary); + linearSpace = InsertAt(linearSpace, (int)taxPerson.TaxableCapitalBenefits, currentValueIndex); + + List taxes = new(); + + decimal previousMarginalTaxRate = 0; + foreach (decimal salary in linearSpace) + { + var currentPerson = taxPerson with { TaxableCapitalBenefits = salary }; + + (await CalculateSingleMarginalTaxRate(municipalityModel, currentPerson)) + .Iter(r => + { + if (r.Rate <= previousMarginalTaxRate) + { + if (taxes.Count == 0) + { + taxes.Add(new MarginalTaxInfo(r.Salary, r.Rate, r.TotalTaxAmount)); + previousMarginalTaxRate = r.Rate; + } + else + { + taxes.Add(taxes[^1] with { Salary = r.Salary, TotalTaxAmount = r.TotalTaxAmount }); + } + } + else if (r.Rate > previousMarginalTaxRate) + { + taxes.Add(new MarginalTaxInfo(r.Salary, r.Rate, r.TotalTaxAmount)); + previousMarginalTaxRate = r.Rate; + } + }); + } + + return taxes; + } + } + + protected static int[] LinearSpace(int start, int end, int size) + { + int[] result = new int[size]; + decimal step = (end - start) / (size - 1M); + + for (int i = 0; i < size; i++) + { + result[i] = (int)(start + (i * step)); + } + + // Ensure the end value is exactly as specified, avoiding floating-point arithmetic errors + if (size > 1) + { + result[size - 1] = end; + } + + return result; + } + + private static int SortedIndex(int[] array, int value) + { + int low = 0; + int high = array.Length; + + while (low < high) + { + var mid = (low + high) >>> 1; + if (array[mid] < value) low = mid + 1; + else high = mid; + } + + return low; + } + + private static int[] InsertAt(int[] originalArray, int value, int index) + { + // Handle the case where index is out of bounds + if (index < 0 || index > originalArray.Length) + { + index = originalArray.Length; // Append to the end if index is out of bounds + } + else if (originalArray[index] == value) + { + return originalArray; + } + + // Create a new array with one extra space + int[] newArray = new int[originalArray.Length + 1]; + + for (int i = 0, j = 0; i < newArray.Length; i++) + { + if (i == index) + { + // Insert the new value at the specified index + newArray[i] = value; + } + else + { + // Copy the value from the original array + newArray[i] = originalArray[j++]; + } + } + + return newArray; + } +} diff --git a/src/Application/Features/PensionVersusCapital/IPensionVersusCapitalCalculator.cs b/src/Application/Features/PensionVersusCapital/IPensionVersusCapitalCalculator.cs new file mode 100644 index 00000000..f5244b6d --- /dev/null +++ b/src/Application/Features/PensionVersusCapital/IPensionVersusCapitalCalculator.cs @@ -0,0 +1,16 @@ +using Domain.Enums; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Features.PensionVersusCapital; + +public interface IPensionVersusCapitalCalculator +{ + Task> CalculateAsync( + int calculationYear, + int municipalityId, + Canton canton, + decimal retirementPension, + decimal retirementCapital, + TaxPerson taxPerson); +} diff --git a/src/Application/Features/PensionVersusCapital/PensionVersusCapitalCalculator.cs b/src/Application/Features/PensionVersusCapital/PensionVersusCapitalCalculator.cs new file mode 100644 index 00000000..d7a782e5 --- /dev/null +++ b/src/Application/Features/PensionVersusCapital/PensionVersusCapitalCalculator.cs @@ -0,0 +1,74 @@ +using Application.Features.FullTaxCalculation; +using Domain.Enums; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Features.PensionVersusCapital; + +public class PensionVersusCapitalCalculator : IPensionVersusCapitalCalculator +{ + private readonly ITaxCalculatorConnector taxCalculatorConnector; + + public PensionVersusCapitalCalculator(ITaxCalculatorConnector taxCalculatorConnector) + { + this.taxCalculatorConnector = taxCalculatorConnector; + } + + public async Task> CalculateAsync( + int calculationYear, + int municipalityId, + Canton canton, + decimal retirementPension, + decimal retirementCapital, + TaxPerson taxPerson) + { + CapitalBenefitTaxPerson capitalBenefitTaxPerson = new() + { + Name = "Benefit Person", + CivilStatus = taxPerson.CivilStatus, + TaxableCapitalBenefits = retirementCapital, + ReligiousGroupType = taxPerson.ReligiousGroupType, + NumberOfChildren = taxPerson.NumberOfChildren, + PartnerReligiousGroupType = taxPerson.PartnerReligiousGroupType + }; + + Either justOtherIncomeTaxCalculationResult = + await taxCalculatorConnector.CalculateAsync(calculationYear, municipalityId, taxPerson, true); + + Either capitalBenefitTaxCalculationResult = + await taxCalculatorConnector.CalculateAsync(calculationYear, municipalityId, capitalBenefitTaxPerson, true); + + var taxPersonWithPensionIncome = taxPerson with + { + TaxableIncome = taxPerson.TaxableIncome + retirementPension, + TaxableFederalIncome = taxPerson.TaxableFederalIncome + retirementPension, + }; + + Either withPensionIncomeTaxCalculationResult = + await taxCalculatorConnector.CalculateAsync(calculationYear, municipalityId, taxPersonWithPensionIncome, true); + + Either r = from benefitsTax in capitalBenefitTaxCalculationResult + from otherIncomeTax in justOtherIncomeTaxCalculationResult + from withPensionIncomeTax in withPensionIncomeTaxCalculationResult + select CalculateBreakEvenFactor(benefitsTax, otherIncomeTax, withPensionIncomeTax); + + return r; + + decimal CalculateBreakEvenFactor( + FullCapitalBenefitTaxResult benefitTaxResult, + FullTaxResult otherIncomeTaxResult, + FullTaxResult withPensionIncomeTaxResult) + { + decimal capitalBenefitNet = capitalBenefitTaxPerson.TaxableCapitalBenefits - benefitTaxResult.TotalTaxAmount; + decimal incomeNet = taxPersonWithPensionIncome.TaxableIncome - taxPerson.TaxableIncome; + decimal totalTaxNet = withPensionIncomeTaxResult.TotalTaxAmount - otherIncomeTaxResult.TotalTaxAmount; + + if (incomeNet == decimal.Zero) + { + return 0m; + } + + return capitalBenefitNet / (incomeNet - totalTaxNet); + } + } +} diff --git a/src/Application/Features/PensionVersusCapital/ToolsCollectionExtensions.cs b/src/Application/Features/PensionVersusCapital/ToolsCollectionExtensions.cs new file mode 100644 index 00000000..260e7c15 --- /dev/null +++ b/src/Application/Features/PensionVersusCapital/ToolsCollectionExtensions.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Application.Features.PensionVersusCapital; + +public static class ToolsCollectionExtensions +{ + public static void AddToolsCalculators(this IServiceCollection serviceCollection) + { + serviceCollection.AddSingleton(); + } +} diff --git a/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Abstractions/ITaxComparer.cs b/src/Application/Features/TaxComparison/ITaxComparer.cs similarity index 68% rename from src/Tax.Tools.Comparison/Tax.Tools.Comparison.Abstractions/ITaxComparer.cs rename to src/Application/Features/TaxComparison/ITaxComparer.cs index bdc319f0..ad673319 100644 --- a/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Abstractions/ITaxComparer.cs +++ b/src/Application/Features/TaxComparison/ITaxComparer.cs @@ -1,9 +1,8 @@ -using System.Collections.Generic; +using Domain.Models.Tax; +using Domain.Models.TaxComparison; using LanguageExt; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxComparison; -namespace Tax.Tools.Comparison.Abstractions +namespace Application.Features.TaxComparison { public interface ITaxComparer { diff --git a/src/TaxCalculator.WebApi/Models/CapitalBenefitTaxRequest.cs b/src/Application/Features/TaxComparison/Models/CapitalBenefitTaxComparerRequest.cs similarity index 52% rename from src/TaxCalculator.WebApi/Models/CapitalBenefitTaxRequest.cs rename to src/Application/Features/TaxComparison/Models/CapitalBenefitTaxComparerRequest.cs index 20926103..7d66f161 100644 --- a/src/TaxCalculator.WebApi/Models/CapitalBenefitTaxRequest.cs +++ b/src/Application/Features/TaxComparison/Models/CapitalBenefitTaxComparerRequest.cs @@ -1,25 +1,23 @@ using System.ComponentModel.DataAnnotations; -using PensionCoach.Tools.CommonTypes; +using Domain.Enums; -namespace TaxCalculator.WebApi.Models +namespace Application.Features.TaxComparison.Models { - public class CapitalBenefitTaxRequest + public class CapitalBenefitTaxComparerRequest { [MaxLength(50)] public string Name { get; set; } - [Range(2018, 2099, ErrorMessage = "Valid tax years start from 2018")] - public int CalculationYear { get; set; } - public CivilStatus CivilStatus { get; set; } public ReligiousGroupType ReligiousGroup { get; set; } public ReligiousGroupType? PartnerReligiousGroup { get; set; } - [Required] - [Range(typeof(int), "0", "10000", ErrorMessage = "BFS number not valid")] - public int BfsMunicipalityId { get; set; } + /// + /// List of BFS number defines for which municipalites a comparison is calculated. + /// + public int[] BfsNumberList { get; set; } [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] public decimal TaxableBenefits { get; set; } diff --git a/src/Application/Features/TaxComparison/Models/IncomeAndWealthComparerRequest.cs b/src/Application/Features/TaxComparison/Models/IncomeAndWealthComparerRequest.cs new file mode 100644 index 00000000..97633d8a --- /dev/null +++ b/src/Application/Features/TaxComparison/Models/IncomeAndWealthComparerRequest.cs @@ -0,0 +1,31 @@ +using System.ComponentModel.DataAnnotations; +using Domain.Enums; + +namespace Application.Features.TaxComparison.Models +{ + public class IncomeAndWealthComparerRequest + { + [MaxLength(50)] + public string Name { get; set; } + + public CivilStatus CivilStatus { get; set; } + + public ReligiousGroupType ReligiousGroup { get; set; } + + public ReligiousGroupType? PartnerReligiousGroup { get; set; } + + /// + /// List of BFS number defines for which municipalites a comparison is calculated. + /// + public int[] BfsNumberList { get; set; } + + [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] + public decimal TaxableIncome { get; set; } + + [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] + public decimal TaxableFederalIncome { get; set; } + + [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] + public decimal TaxableWealth { get; set; } + } +} diff --git a/src/Application/Features/TaxComparison/Models/TaxComparerResponse.cs b/src/Application/Features/TaxComparison/Models/TaxComparerResponse.cs new file mode 100644 index 00000000..de925992 --- /dev/null +++ b/src/Application/Features/TaxComparison/Models/TaxComparerResponse.cs @@ -0,0 +1,25 @@ +using Domain.Enums; +using Domain.Models.Tax; + +namespace Application.Features.TaxComparison.Models; + +public class TaxComparerResponse +{ + public string Name { get; set; } + + public int MunicipalityId { get; set; } + + public string MunicipalityName { get; set; } + + public Canton Canton { get; set; } + + public int MaxSupportedTaxYear { get; set; } + + public decimal TotalTaxAmount { get; set; } + + public TaxAmountDetail TaxDetails { get; set; } + + public int TotalCount { get; set; } + + public int CountComputed { get; set; } +} diff --git a/src/Tax.Tools.Comparison.Domain/TaxComparerResultReportModel.cs b/src/Application/Features/TaxComparison/Models/TaxComparerResultReportModel.cs similarity index 81% rename from src/Tax.Tools.Comparison.Domain/TaxComparerResultReportModel.cs rename to src/Application/Features/TaxComparison/Models/TaxComparerResultReportModel.cs index 07d44cf1..87a00c1d 100644 --- a/src/Tax.Tools.Comparison.Domain/TaxComparerResultReportModel.cs +++ b/src/Application/Features/TaxComparison/Models/TaxComparerResultReportModel.cs @@ -1,6 +1,6 @@ -using PensionCoach.Tools.CommonTypes; +using Domain.Enums; -namespace PensionCoach.Tools.TaxComparison +namespace Application.Features.TaxComparison.Models { public class TaxComparerResultReportModel { diff --git a/src/Tax.Tools.Comparison/Tax.Tools.Comparison/TaxComparer.cs b/src/Application/Features/TaxComparison/TaxComparer.cs similarity index 93% rename from src/Tax.Tools.Comparison/Tax.Tools.Comparison/TaxComparer.cs rename to src/Application/Features/TaxComparison/TaxComparer.cs index cc3894ac..46a7f0ae 100644 --- a/src/Tax.Tools.Comparison/Tax.Tools.Comparison/TaxComparer.cs +++ b/src/Application/Features/TaxComparison/TaxComparer.cs @@ -1,17 +1,13 @@ -using LanguageExt; -using System.Linq; -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Threading.Channels; +using Application.Municipality; +using Application.Tax.Contracts; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using Domain.Models.TaxComparison; using FluentValidation; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using Tax.Tools.Comparison.Abstractions; -using System.Threading.Channels; -using System.Threading; -using PensionCoach.Tools.TaxComparison; - -namespace Tax.Tools.Comparison +using LanguageExt; + +namespace Application.Features.TaxComparison { public class TaxComparer : ITaxComparer { diff --git a/src/Application/Features/TaxComparison/TaxComparerServiceCollectionExtensions.cs b/src/Application/Features/TaxComparison/TaxComparerServiceCollectionExtensions.cs new file mode 100644 index 00000000..a9eed1bb --- /dev/null +++ b/src/Application/Features/TaxComparison/TaxComparerServiceCollectionExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Application.Features.TaxComparison +{ + public static class TaxComparerServiceCollectionExtensions + { + public static void AddTaxComparers(this IServiceCollection collection) + { + collection.AddTransient(); + } + } +} diff --git a/src/Application/Features/TaxScenarios/ITaxScenarioCalculator.cs b/src/Application/Features/TaxScenarios/ITaxScenarioCalculator.cs new file mode 100644 index 00000000..1d42d891 --- /dev/null +++ b/src/Application/Features/TaxScenarios/ITaxScenarioCalculator.cs @@ -0,0 +1,34 @@ +using Domain.Models.Scenarios; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Features.TaxScenarios; + +public interface ITaxScenarioCalculator +{ + /// + /// + /// + /// + /// + Task> CapitalBenefitTransferInsAsync( + int startingYear, + int bfsMunicipalityId, + TaxPerson person, + CapitalBenefitTransferInsScenarioModel scenarioModel); + + Task> ThirdPillarVersusSelfInvestmentAsync( + int startingYear, + int bfsMunicipalityId, + TaxPerson person, + ThirdPillarVersusSelfInvestmentScenarioModel scenarioModel); + + Task> PensionVersusCapitalComparisonAsync( + int calculationYear, + int municipalityId, + decimal yearConsumptionAmount, + decimal retirementPension, + decimal retirementCapital, + decimal netWealthReturn, + TaxPerson taxPerson); +} diff --git a/src/Tax.Tools.Comparison.Domain/CapitalBenefitTransferInComparerRequest.cs b/src/Application/Features/TaxScenarios/Models/CapitalBenefitTransferInComparerRequest.cs similarity index 87% rename from src/Tax.Tools.Comparison.Domain/CapitalBenefitTransferInComparerRequest.cs rename to src/Application/Features/TaxScenarios/Models/CapitalBenefitTransferInComparerRequest.cs index 6ae3acbb..192f8d52 100644 --- a/src/Tax.Tools.Comparison.Domain/CapitalBenefitTransferInComparerRequest.cs +++ b/src/Application/Features/TaxScenarios/Models/CapitalBenefitTransferInComparerRequest.cs @@ -1,8 +1,8 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using PensionCoach.Tools.CommonTypes; +using System.ComponentModel.DataAnnotations; +using Domain.Enums; +using Domain.Models.Tax; -namespace PensionCoach.Tools.TaxComparison; +namespace Application.Features.TaxScenarios.Models; public class CapitalBenefitTransferInComparerRequest { @@ -30,7 +30,7 @@ public class CapitalBenefitTransferInComparerRequest [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] public decimal TaxableWealth { get; set; } - public IReadOnlyCollection TransferIns { get; set;} + public IReadOnlyCollection TransferIns { get; set; } /// /// Gets or sets yearly net return on transfer-ins. @@ -38,7 +38,7 @@ public class CapitalBenefitTransferInComparerRequest public decimal NetWealthReturn { get; set; } public decimal NetPensionCapitalReturn { get; set; } - + public bool WithCapitalBenefitTaxation { get; set; } /// @@ -46,7 +46,7 @@ public class CapitalBenefitTransferInComparerRequest /// The amount when starting withdrawals does not include the previously added transfer-ins. /// public decimal CapitalBenefitsBeforeWithdrawal { get; set; } - + public IReadOnlyCollection Withdrawals { get; set; } } diff --git a/src/Application/Features/TaxScenarios/Models/ThirdPillarVersusSelfInvestmentComparerRequest.cs b/src/Application/Features/TaxScenarios/Models/ThirdPillarVersusSelfInvestmentComparerRequest.cs new file mode 100644 index 00000000..072803bb --- /dev/null +++ b/src/Application/Features/TaxScenarios/Models/ThirdPillarVersusSelfInvestmentComparerRequest.cs @@ -0,0 +1,57 @@ +using System.ComponentModel.DataAnnotations; +using Domain.Enums; + +namespace Application.Features.TaxScenarios.Models; + +public class ThirdPillarVersusSelfInvestmentComparerRequest +{ + [MaxLength(50)] + public string Name { get; set; } + + [Range(2018, 2099, ErrorMessage = "Valid tax years start from 2018")] + public int CalculationYear { get; set; } + + /// + /// Final transfer-in year. At this year the third-pillar account is closed and the money is transferred to the wealth. + /// + public int FinalYear { get; set; } + + public CivilStatus CivilStatus { get; set; } + + public ReligiousGroupType ReligiousGroup { get; set; } + + public ReligiousGroupType? PartnerReligiousGroup { get; set; } + + [Range(typeof(int), "0", "100000", ErrorMessage = "BFS Number not valid")] + public int BfsMunicipalityId { get; set; } + + [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] + public decimal TaxableIncome { get; set; } + + [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] + public decimal TaxableFederalIncome { get; set; } + + [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] + public decimal TaxableWealth { get; set; } + + /// + /// The amount invested in the third pillar or self-investment account. + /// First investment is done in the calculation year up to final year. + /// + public decimal InvestmentAmount { get; set; } + + /// + /// Gets or sets the net growth rate of the investment account which is taxed by the wealth tax scheme. + /// + public decimal InvestmentNetGrowthRate { get; set; } + + /// + /// Gets or sets the net income rate of the investment account which is taxed by the income tax scheme. + /// + public decimal InvestmentNetIncomeRate { get; set; } + + /// + /// Gets or sets the net growth rate of the third-pillar account. + /// + public decimal ThirdPillarNetGrowthRate { get; set; } +} diff --git a/src/Application/Features/TaxScenarios/TaxScenarioCalculator.cs b/src/Application/Features/TaxScenarios/TaxScenarioCalculator.cs new file mode 100644 index 00000000..fe19b682 --- /dev/null +++ b/src/Application/Features/TaxScenarios/TaxScenarioCalculator.cs @@ -0,0 +1,589 @@ +using Application.MultiPeriodCalculator; +using Application.Municipality; +using Domain.Contracts; +using Domain.Enums; +using Domain.Models.Cashflows; +using Domain.Models.MultiPeriod; +using Domain.Models.MultiPeriod.Actions; +using Domain.Models.MultiPeriod.Definitions; +using Domain.Models.Municipality; +using Domain.Models.Scenarios; +using Domain.Models.Tax; +using LanguageExt; +using PensionCoach.Tools.CommonTypes.MultiPeriod; +using PensionCoach.Tools.CommonTypes.Tax; + +namespace Application.Features.TaxScenarios; + +public class TaxScenarioCalculator : ITaxScenarioCalculator +{ + private readonly IMultiPeriodCashFlowCalculator multiPeriodCashFlowCalculator; + private readonly IMunicipalityConnector municipalityResolver; + + public TaxScenarioCalculator( + IMultiPeriodCashFlowCalculator multiPeriodCashFlowCalculator, + IMunicipalityConnector municipalityResolver) + { + this.multiPeriodCashFlowCalculator = multiPeriodCashFlowCalculator; + this.municipalityResolver = municipalityResolver; + } + + public async Task> CapitalBenefitTransferInsAsync( + int startingYear, int bfsMunicipalityId, TaxPerson person, CapitalBenefitTransferInsScenarioModel scenarioModel) + { + var birthdate = new DateTime(1969, 3, 17); + + MultiPeriodOptions options = new(); + options.CapitalBenefitsNetGrowthRate = scenarioModel.NetReturnCapitalBenefits; + options.WealthNetGrowthRate = scenarioModel.NetReturnWealth; + options.SavingsQuota = decimal.Zero; + + CashFlowDefinitionHolder cashFlowDefinitionHolder = CreateScenarioDefinitions(); + + Either municipalityData = + await municipalityResolver.GetAsync(bfsMunicipalityId, startingYear); + + Either scenarioResult = await municipalityData + .BindAsync(m => + multiPeriodCashFlowCalculator.CalculateAsync(startingYear, 0, GetPerson(m, birthdate, person), + cashFlowDefinitionHolder, options)); + + CashFlowDefinitionHolder benchmarkDefinitions = CreateBenchmarkDefinitions(); + + Either benchmarkResult = await municipalityData + .BindAsync(m => + multiPeriodCashFlowCalculator.CalculateAsync(startingYear, 0, GetPerson(m, birthdate, person), + benchmarkDefinitions, options)); + + var benchmarkSeriesResult = benchmarkResult + .Map(r => r.Accounts + .Where(a => a.AccountType is AccountType.Wealth or AccountType.OccupationalPension)); + + var scenarioSeriesResult = scenarioResult + .Map(r => r.Accounts + .Where(a => a.AccountType is AccountType.Wealth or AccountType.OccupationalPension)); + + return from b in benchmarkSeriesResult + from s in scenarioSeriesResult + select CalculateDelta(b.ToList(), s.ToList()); + + CashFlowDefinitionHolder CreateBenchmarkDefinitions() + { + CashFlowDefinitionHolder holder = new CashFlowDefinitionHolder(); + holder.Composites = CreateDefaultComposites(person, scenarioModel).ToList(); + holder.CashFlowActions = GetClearAccountAction(scenarioModel).ToList(); + + return holder; + } + + CashFlowDefinitionHolder CreateScenarioDefinitions() + { + CashFlowDefinitionHolder holder = new CashFlowDefinitionHolder(); + holder.Composites = CreateTransferInDefinitions(scenarioModel) + .Concat(CreateDefaultComposites(person, scenarioModel)) + .ToList(); + holder.CashFlowActions = GetClearAccountAction(scenarioModel).ToList(); + + return holder; + } + } + + public async Task> ThirdPillarVersusSelfInvestmentAsync( + int startingYear, + int bfsMunicipalityId, + TaxPerson person, + ThirdPillarVersusSelfInvestmentScenarioModel scenarioModel) + { + var birthdate = new DateTime(1969, 3, 17); + + MultiPeriodOptions options = new(); + options.CapitalBenefitsNetGrowthRate = decimal.Zero; + options.WealthNetGrowthRate = decimal.Zero; + options.InvestmentNetGrowthRate = scenarioModel.InvestmentNetGrowthRate; + options.SavingsQuota = decimal.Zero; + + CashFlowDefinitionHolder cashFlowDefinitionHolder = CreateScenarioDefinitions(); + + Either municipalityData = + await municipalityResolver.GetAsync(bfsMunicipalityId, startingYear); + + Either scenarioResult = await municipalityData + .BindAsync(m => + multiPeriodCashFlowCalculator.CalculateAsync(startingYear, 0, GetPerson(m, birthdate, person), + cashFlowDefinitionHolder, options)); + + var scenarioSeriesResult = scenarioResult + .Map(r => r.Accounts + .Where(a => a.AccountType is AccountType.Wealth or AccountType.ThirdPillar)); + + CashFlowDefinitionHolder benchmarkDefinitions = CreateBenchmarkDefinitions(); + + Either benchmarkResult = await municipalityData + .BindAsync(m => + multiPeriodCashFlowCalculator.CalculateAsync( + startingYear, + scenarioModel.FinalYear - startingYear, + GetPerson(m, birthdate, person), benchmarkDefinitions, options)); + + var benchmarkSeriesResult = benchmarkResult + .Map(r => r.Accounts + .Where(a => a.AccountType is AccountType.Wealth or AccountType.ThirdPillar or AccountType.Investment)); + + return from b in benchmarkSeriesResult + from s in scenarioSeriesResult + select CalculateDeltaForThirdPillarComparison(b.ToList(), s.ToList()); + + CashFlowDefinitionHolder CreateScenarioDefinitions() + { + CashFlowDefinitionHolder holder = new CashFlowDefinitionHolder(); + + var thirdPillarInvestmentDefinitions = new List + { + new ThirdPillarPaymentsDefinition + { + DateOfStart = new DateTime(startingYear, 1, 1), + NetGrowthRate = decimal.Zero, + NumberOfInvestments = scenarioModel.FinalYear - startingYear + 1, + YearlyAmount = scenarioModel.InvestmentAmount, + } + }; + + var thirdPillarWithdrawalDefinitions = new List + { + new DynamicTransferAccountAction + { + Header = + new CashFlowHeader {Id = Guid.NewGuid().ToString(), Name = "Third Pillar Withdrawal"}, + DateOfProcess = new DateTime(scenarioModel.FinalYear, 12, 31), + TransferRatio = decimal.One, + Flow = new FlowPair(AccountType.ThirdPillar, AccountType.Wealth), + IsTaxable = true, + TaxType = TaxType.CapitalBenefits + } + }; + + holder.Composites = thirdPillarInvestmentDefinitions + .Concat(CreateSalaryPaymentDefinition(person, scenarioModel.FinalYear)) + .ToList(); + holder.CashFlowActions = thirdPillarWithdrawalDefinitions.ToList(); + + return holder; + } + + CashFlowDefinitionHolder CreateBenchmarkDefinitions() + { + CashFlowDefinitionHolder holder = new CashFlowDefinitionHolder(); + + holder.InvestmentDefinitions = new List + { + new() + { + Header = new CashFlowHeader {Id = Guid.NewGuid().ToString(), Name = "Self Investment"}, + DateOfProcess = new DateTime(startingYear, 1, 1), + NetCapitalGrowthRate = scenarioModel.InvestmentNetGrowthRate, + NetIncomeRate = scenarioModel.InvestmentNetIncomeYield, + InitialInvestment = 0, + RecurringInvestment = new RecurringInvestment + { + Amount = scenarioModel.InvestmentAmount, + Frequency = FrequencyType.Yearly, + }, + InvestmentPeriod = new InvestmentPeriod + { + Year = startingYear, + NumberOfPeriods = scenarioModel.FinalYear - startingYear + 1, + } + } + }; + + holder.Composites = CreateSalaryPaymentDefinition(person, scenarioModel.FinalYear).ToList(); + + return holder; + } + } + + /// + /// Compares the pension income versus capital consumption scenario. Pension income is the benchmark scenario. + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task> PensionVersusCapitalComparisonAsync( + int calculationYear, + int municipalityId, + decimal yearConsumptionAmount, + decimal retirementPension, + decimal retirementCapital, + decimal netWealthReturn, + TaxPerson taxPerson) + { + const int numberOfYears = 25; + + var birthdate = new DateTime(1969, 3, 17); + + MultiPeriodOptions options = new() + { + CapitalBenefitsNetGrowthRate = decimal.Zero, + WealthNetGrowthRate = netWealthReturn, + SavingsQuota = decimal.Zero + }; + + CashFlowDefinitionHolder benchmarkDefinitionHolder = CreateBenchmarkCashFlowDefinitions(); + + Either municipalityData = + await municipalityResolver.GetAsync(municipalityId, calculationYear); + + Either benchmarkScenarioResult = await municipalityData + .BindAsync(m => multiPeriodCashFlowCalculator.CalculateAsync( + calculationYear, 0, GetPerson(m, birthdate, taxPerson), benchmarkDefinitionHolder, options)); + + Either> benchmarkSeriesResult = benchmarkScenarioResult + .Map(r => r.Accounts + .Where(a => a.AccountType is AccountType.Wealth)); + + CashFlowDefinitionHolder scenarioDefinitionHolder = CreateScenarioCashFlowDefinitions(); + + Either scenarioResult = await municipalityData + .BindAsync(m => multiPeriodCashFlowCalculator.CalculateAsync( + calculationYear, 0, GetPerson(m, birthdate, taxPerson), scenarioDefinitionHolder, options)); + + Either> scenarioSeriesResult = scenarioResult + .Map(r => r.Accounts + .Where(a => a.AccountType is AccountType.Wealth)); + + var result = from b in benchmarkSeriesResult + from s in scenarioSeriesResult + select CalculateDeltaForPensionVersusCapitalComparison(b.ToList(), s.ToList()); + + return from r in result + from br in benchmarkScenarioResult + from sr in scenarioResult + select SetTransactions(r, br, sr); + + ScenarioCalculationResult SetTransactions( + ScenarioCalculationResult finalResult, MultiPeriodCalculationResult multiPeriodBenchmarkResult, MultiPeriodCalculationResult multiPeriodScenarioResult) + { + finalResult.BenchmarkTransactions = new AccountTransactionResultHolder() + { + WealthAccount = multiPeriodBenchmarkResult.Transactions.WealthAccount + }; + + // set scenario transactions + finalResult.ScenarioTransactions = new AccountTransactionResultHolder() + { + WealthAccount = multiPeriodScenarioResult.Transactions.WealthAccount + }; + + return finalResult; + } + + CashFlowDefinitionHolder CreateBenchmarkCashFlowDefinitions() + { + CashFlowDefinitionHolder holder = new CashFlowDefinitionHolder(); + + holder.Composites = CreateBenchmarkDefinitions(calculationYear + numberOfYears).ToList(); + + return holder; + } + + CashFlowDefinitionHolder CreateScenarioCashFlowDefinitions() + { + CashFlowDefinitionHolder holder = new CashFlowDefinitionHolder(); + + holder.Composites = CreateScenarioDefinitions(calculationYear + numberOfYears).ToList(); + holder.CashFlowActions = new List + { + new DynamicTransferAccountAction() + { + Header = + new CashFlowHeader + { + Id = Guid.NewGuid().ToString(), Name = "Capital Benefit Withdrawal" + }, + DateOfProcess = new DateTime(calculationYear, 1, 2), + TransferRatio = decimal.One, + Flow = new FlowPair(AccountType.OccupationalPension, AccountType.Wealth), + IsTaxable = true, + TaxType = TaxType.CapitalBenefits + } + }; + + return holder; + } + + IEnumerable CreateBenchmarkDefinitions(int finalYear) + { + yield return new SalaryPaymentsDefinition + { + YearlyAmount = taxPerson.TaxableIncome, + DateOfEndOfPeriod = new DateTime(finalYear, 1, 1), + }; + + yield return new SalaryPaymentsDefinition + { + YearlyAmount = retirementPension, + DateOfEndOfPeriod = new DateTime(finalYear, 1, 1), + }; + + yield return new SetupAccountDefinition + { + InitialWealth = taxPerson.TaxableWealth + }; + + decimal deltaWealthConsumption = (taxPerson.TaxableIncome + retirementPension) - yearConsumptionAmount; + + foreach (var fixedTransferDefinition in CreateYearlyWealthConsumption(deltaWealthConsumption)) + { + yield return fixedTransferDefinition; + } + } + + IEnumerable CreateScenarioDefinitions(int finalYear) + { + yield return new SalaryPaymentsDefinition + { + YearlyAmount = taxPerson.TaxableIncome, + DateOfEndOfPeriod = new DateTime(finalYear, 1, 1), + }; + + yield return new SetupAccountDefinition + { + InitialOccupationalPensionAssets = retirementCapital, + InitialWealth = taxPerson.TaxableWealth + }; + + decimal deltaWealthConsumption = taxPerson.TaxableIncome - yearConsumptionAmount; + + foreach (var fixedTransferDefinition in CreateYearlyWealthConsumption(deltaWealthConsumption)) + { + yield return fixedTransferDefinition; + } + } + + IEnumerable CreateYearlyWealthConsumption(decimal deltaWealthConsumption) + { + foreach (var year in Enumerable.Range(calculationYear, numberOfYears)) + { + yield return new FixedTransferAmountDefinition + { + Header = new CashFlowHeader { Id = Guid.NewGuid().ToString(), Name = $"Wealth Consumption in {year}" }, + DateOfProcess = new DateTime(year, 12, 31), + TransferAmount = Math.Abs(deltaWealthConsumption), + Flow = new FlowPair(AccountType.Wealth, AccountType.Exogenous), + IsTaxable = false, + TaxType = TaxType.Undefined + }; + } + } + } + + private static IEnumerable CreateSalaryPaymentDefinition( + TaxPerson person, int finalYear) + { + ICompositeCashFlowDefinition salaryPaymentDefinition = new SalaryPaymentsDefinition + { + YearlyAmount = person.TaxableIncome, + DateOfEndOfPeriod = new DateTime(finalYear, 1, 1), + }; + + return new List { salaryPaymentDefinition }; + } + + private IEnumerable GetClearAccountAction(CapitalBenefitTransferInsScenarioModel scenarioModel) + { + if (scenarioModel is { WithCapitalBenefitWithdrawal: false }) + { + yield break; + } + + foreach (var withdrawal in scenarioModel.Withdrawals) + { + DateTime withdrawalDate = new DateTime(withdrawal.DateOfTransferIn.Year, 12, 31); + + yield return new DynamicTransferAccountAction + { + Header = new CashFlowHeader { Id = Guid.NewGuid().ToString(), Name = "Capital Benefit Withdrawal" }, + DateOfProcess = withdrawalDate, + TransferRatio = withdrawal.Amount, + Flow = new FlowPair(AccountType.OccupationalPension, AccountType.Wealth), + IsTaxable = true, + TaxType = TaxType.CapitalBenefits + }; + } + } + + private static IEnumerable CreateTransferInDefinitions(CapitalBenefitTransferInsScenarioModel scenarioModel) + { + // one purchase transfer-in for each single transfer-in + // as they might not be continuously + foreach (var singleTransferIn in scenarioModel.TransferIns) + { + yield return new PurchaseInsuranceYearsPaymentsDefinition + { + DateOfStart = singleTransferIn.DateOfTransferIn, + NetGrowthRate = scenarioModel.NetReturnCapitalBenefits, + NumberOfInvestments = 1, + YearlyAmount = singleTransferIn.Amount, + }; + } + } + + private static IEnumerable CreateDefaultComposites( + TaxPerson person, CapitalBenefitTransferInsScenarioModel scenarioModel) + { + DateTime finalSalaryPaymentDate = scenarioModel.TransferIns.Max(t => t.DateOfTransferIn).AddYears(1); + + DateTime finalDate = scenarioModel.WithCapitalBenefitWithdrawal + ? scenarioModel.Withdrawals.Min(w => w.DateOfTransferIn) + : finalSalaryPaymentDate; + + yield return new SalaryPaymentsDefinition + { + YearlyAmount = person.TaxableIncome, + DateOfEndOfPeriod = finalDate, + NetGrowthRate = decimal.Zero, + }; + + yield return new SetupAccountDefinition + { + InitialOccupationalPensionAssets = decimal.Zero, + InitialWealth = person.TaxableWealth + }; + + yield return new FixedTransferAmountDefinition + { + DateOfProcess = new DateTime(finalDate.Year, 1, 1), + Flow = new FlowPair(AccountType.Exogenous, AccountType.OccupationalPension), + TransferAmount = scenarioModel.CapitalBenefitsBeforeWithdrawal, + TaxType = TaxType.Undefined, + IsTaxable = false, + }; + } + + private static ScenarioCalculationResult CalculateDeltaForPensionVersusCapitalComparison( + IReadOnlyCollection benchmark, + IReadOnlyCollection scenario) + { + IEnumerable deltaResults = + from b in benchmark.Where(item => item.AccountType == AccountType.Wealth) + from s in scenario.Where(item => item.AccountType == AccountType.Wealth) + where b.Year == s.Year + select new SinglePeriodCalculationResult + { + Amount = s.Amount - b.Amount, + Year = b.Year, + AccountType = AccountType.Exogenous + }; + + List deltaSeries = deltaResults.ToList(); + + return new ScenarioCalculationResult + { + StartingYear = Math.Min(benchmark.Min(a => a.Year), scenario.Min(a => a.Year)), + NumberOfPeriods = deltaSeries.Count, + BenchmarkSeries = benchmark.ToList(), + ScenarioSeries = scenario.ToList(), + DeltaSeries = deltaSeries + }; + } + + private static ScenarioCalculationResult CalculateDeltaForThirdPillarComparison( + IReadOnlyCollection benchmark, + IReadOnlyCollection scenario) + { + var sumBenchmarkSeries = + from w in benchmark.Where(item => item.AccountType == AccountType.Wealth) + from p in benchmark.Where(item => item.AccountType == AccountType.Investment) + where w.Year == p.Year + select new { Sum = w.Amount + p.Amount, w.Year }; + + var sumScenarioSeries = + from w in scenario.Where(item => item.AccountType == AccountType.Wealth) + from p in scenario.Where(item => item.AccountType == AccountType.ThirdPillar) + where w.Year == p.Year + select new { Sum = w.Amount + p.Amount, w.Year }; + + IEnumerable deltaResults = from bSum in sumBenchmarkSeries + from sSum in sumScenarioSeries + where bSum.Year == sSum.Year + select new SinglePeriodCalculationResult + { + Amount = sSum.Sum - bSum.Sum, + Year = bSum.Year, + AccountType = AccountType.Exogenous + }; + + List deltaSeries = deltaResults.ToList(); + + return new ScenarioCalculationResult + { + StartingYear = Math.Min(benchmark.Min(a => a.Year), scenario.Min(a => a.Year)), + NumberOfPeriods = deltaSeries.Count, + BenchmarkSeries = benchmark.ToList(), + ScenarioSeries = scenario.ToList(), + DeltaSeries = deltaSeries + }; + } + + private static ScenarioCalculationResult CalculateDelta( + IReadOnlyCollection benchmark, + IReadOnlyCollection scenario) + { + var sumBenchmarkSeries = + from w in benchmark.Where(item => item.AccountType == AccountType.Wealth) + from p in benchmark.Where(item => item.AccountType == AccountType.OccupationalPension) + where w.Year == p.Year + select new { Sum = w.Amount + p.Amount, w.Year }; + + var sumScenarioSeries = + from w in scenario.Where(item => item.AccountType == AccountType.Wealth) + from p in scenario.Where(item => item.AccountType == AccountType.OccupationalPension) + where w.Year == p.Year + select new { Sum = w.Amount + p.Amount, w.Year }; + + IEnumerable deltaResults = from bSum in sumBenchmarkSeries + from sSum in sumScenarioSeries + where bSum.Year == sSum.Year + select new SinglePeriodCalculationResult + { + Amount = sSum.Sum - bSum.Sum, + Year = bSum.Year, + AccountType = AccountType.Exogenous + }; + + List deltaSeries = deltaResults.ToList(); + + return new ScenarioCalculationResult + { + StartingYear = Math.Min(benchmark.Min(a => a.Year), scenario.Min(a => a.Year)), + NumberOfPeriods = deltaSeries.Count, + BenchmarkSeries = benchmark.ToList(), + ScenarioSeries = scenario.ToList(), + DeltaSeries = deltaSeries + }; + } + + private static MultiPeriodCalculatorPerson GetPerson(MunicipalityModel municipality, DateTime birthday, TaxPerson person) + { + return new MultiPeriodCalculatorPerson + { + CivilStatus = person.CivilStatus, + DateOfBirth = birthday, + Gender = Gender.Male, + Name = "Purchase Scenario", + Canton = municipality.Canton, + MunicipalityId = municipality.BfsNumber, + Income = person.TaxableIncome, + Wealth = person.TaxableWealth, + CapitalBenefits = (0, 0), + NumberOfChildren = 0, + PartnerReligiousGroupType = person.PartnerReligiousGroupType, + ReligiousGroupType = person.ReligiousGroupType, + }; + } +} diff --git a/src/TaxCalculator/Mapping/MappingProfile.cs b/src/Application/Mapping/MappingProfile.cs similarity index 84% rename from src/TaxCalculator/Mapping/MappingProfile.cs rename to src/Application/Mapping/MappingProfile.cs index d37e58f5..eac4311f 100644 --- a/src/TaxCalculator/Mapping/MappingProfile.cs +++ b/src/Application/Mapping/MappingProfile.cs @@ -1,12 +1,10 @@ -using System; -using System.Globalization; +using System.Globalization; using AutoMapper; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -using Tax.Data.Abstractions.Models; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; -namespace PensionCoach.Tools.TaxCalculator.Mapping +namespace Application.Mapping { public class MappingProfile : Profile { diff --git a/src/Application/MultiPeriodCalculator/CashFlowCalculatorsServiceCollectionExtensions.cs b/src/Application/MultiPeriodCalculator/CashFlowCalculatorsServiceCollectionExtensions.cs new file mode 100644 index 00000000..0d83255d --- /dev/null +++ b/src/Application/MultiPeriodCalculator/CashFlowCalculatorsServiceCollectionExtensions.cs @@ -0,0 +1,13 @@ +using Application.Features.TaxScenarios; +using Microsoft.Extensions.DependencyInjection; + +namespace Application.MultiPeriodCalculator; + +public static class CashFlowCalculatorsServiceCollectionExtensions +{ + public static void AddCashFlowCalculators(this IServiceCollection collection) + { + collection.AddTransient(); + collection.AddTransient(); + } +} diff --git a/src/Calculators.CashFlow/CashFlowHelperExtensions.cs b/src/Application/MultiPeriodCalculator/CashFlowHelperExtensions.cs similarity index 86% rename from src/Calculators.CashFlow/CashFlowHelperExtensions.cs rename to src/Application/MultiPeriodCalculator/CashFlowHelperExtensions.cs index b15f75de..02383eba 100644 --- a/src/Calculators.CashFlow/CashFlowHelperExtensions.cs +++ b/src/Application/MultiPeriodCalculator/CashFlowHelperExtensions.cs @@ -1,17 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Calculators.CashFlow.Accounts; -using Calculators.CashFlow.Models; -using PensionCoach.Tools.BvgCalculator; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.MultiPeriod; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Actions; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; +using Application.Extensions; +using Domain.Contracts; +using Domain.Enums; +using Domain.Models.Cashflows; +using Domain.Models.Cashflows.Accounts; +using Domain.Models.MultiPeriod; +using Domain.Models.MultiPeriod.Actions; +using Domain.Models.MultiPeriod.Definitions; using PensionCoach.Tools.CommonTypes.Tax; using PensionCoach.Tools.CommonUtils; -namespace Calculators.CashFlow; +namespace Application.MultiPeriodCalculator; public static class CashFlowHelperExtensions { @@ -180,6 +178,28 @@ public static IEnumerable CreateGenericDefinition( IsTaxable = false, TaxType = TaxType.CapitalBenefits }; + + yield return new StaticGenericCashFlowDefinition + { + Header = new CashFlowHeader + { + Id = "setupInvestmentAccount", + Name = "Investment Account" + }, + DateOfProcess = dateOfStart, + InitialAmount = accountDefinition.InitialInvestmentAssets, + Flow = new FlowPair(AccountType.Exogenous, AccountType.Investment), + RecurringInvestment = new RecurringInvestment + { + Amount = 0, + Frequency = FrequencyType.Yearly, + }, + InvestmentPeriod = new InvestmentPeriod + { + Year = dateOfStart.Year, + NumberOfPeriods = 0 + }, + }; } public static IEnumerable CreateGenericDefinition( @@ -352,12 +372,35 @@ public static IEnumerable CreateGenericDefinition(thi }; } + public static IEnumerable CreateGenericDefinition(this InvestmentPortfolioDefinition investmentPortfolioDefinition) + { + yield return new StaticGenericCashFlowDefinition + { + Header = investmentPortfolioDefinition.Header, + DateOfProcess = investmentPortfolioDefinition.DateOfProcess, + InitialAmount = decimal.Zero, + NetGrowthRate = investmentPortfolioDefinition.NetCapitalGrowthRate, + RecurringInvestment = investmentPortfolioDefinition.RecurringInvestment, + InvestmentPeriod = investmentPortfolioDefinition.InvestmentPeriod, + Flow = new FlowPair(AccountType.Wealth, AccountType.Investment), + }; + } + public static IEnumerable CreateGenericDefinition( - this ICompositeCashFlowDefinition cashFlowAction, MultiPeriodCalculatorPerson person, DateTime dateOfStart) + this ICompositeCashFlowDefinition cashFlowDefinition, MultiPeriodCalculatorPerson person, DateTime dateOfStart) { - return cashFlowAction switch + return cashFlowDefinition switch { OrdinaryRetirementAction a => a.CreateCashFlows(person), + _ => CreateGenericDefinition(cashFlowDefinition, dateOfStart) + }; + } + + public static IEnumerable CreateGenericDefinition( + this ICompositeCashFlowDefinition cashFlowDefinition, DateTime dateOfStart) + { + return cashFlowDefinition switch + { SetupAccountDefinition s => s.CreateGenericDefinition(dateOfStart), SalaryPaymentsDefinition p => p.CreateGenericDefinition(dateOfStart), FixedTransferAmountDefinition t => t.CreateGenericDefinition(), diff --git a/src/Application/MultiPeriodCalculator/IMultiPeriodCashFlowCalculator.cs b/src/Application/MultiPeriodCalculator/IMultiPeriodCashFlowCalculator.cs new file mode 100644 index 00000000..8da9c005 --- /dev/null +++ b/src/Application/MultiPeriodCalculator/IMultiPeriodCashFlowCalculator.cs @@ -0,0 +1,33 @@ +using Domain.Models.MultiPeriod; +using LanguageExt; +using PensionCoach.Tools.CommonTypes.MultiPeriod; + +namespace Application.MultiPeriodCalculator +{ + public interface IMultiPeriodCashFlowCalculator + { + /// + /// Calculates how taxable assets evolves over time. + /// Takes a list of cash-flow definitions sums them up by groups of target/source pairs along the timeline. + /// Then, iterates along the timeline and calculates for a given year: + /// 1. adds cash-flow amount for the given year to its associated asset type + /// 2. calculates tax amount for each target asset type + /// 3. deduct tax amount from asset values + /// 4. move flow asset types to its stock asset type (ie. salary does not stay after paying tax for it but + /// is moved to taxable wealth). + /// + /// + /// + /// + /// + /// + /// + Task> CalculateAsync( + int startingYear, + int minimumNumberOfPeriods, + MultiPeriodCalculatorPerson person, + CashFlowDefinitionHolder cashFlowDefinitionHolder, + MultiPeriodOptions options); + + } +} diff --git a/src/Application/MultiPeriodCalculator/MultiPeriodCashFlowCalculator.cs b/src/Application/MultiPeriodCalculator/MultiPeriodCashFlowCalculator.cs new file mode 100644 index 00000000..73fb5d64 --- /dev/null +++ b/src/Application/MultiPeriodCalculator/MultiPeriodCashFlowCalculator.cs @@ -0,0 +1,481 @@ +using Application.Extensions; +using Application.Municipality; +using Application.Tax.Contracts; +using Domain.Contracts; +using Domain.Enums; +using Domain.Models.Cashflows; +using Domain.Models.Cashflows.Accounts; +using Domain.Models.MultiPeriod; +using Domain.Models.MultiPeriod.Actions; +using Domain.Models.MultiPeriod.Definitions; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using LanguageExt; +using Microsoft.Extensions.Logging; +using PensionCoach.Tools.CommonTypes.MultiPeriod; +using PensionCoach.Tools.CommonTypes.Tax; + +namespace Application.MultiPeriodCalculator +{ + public class MultiPeriodCashFlowCalculator : IMultiPeriodCashFlowCalculator + { + private readonly IFullWealthAndIncomeTaxCalculator _fullTaxCalculator; + private readonly IFullCapitalBenefitTaxCalculator _capitalBenefitCalculator; + private readonly IMunicipalityConnector municipalityConnector; + private readonly ILogger _logger; + + public MultiPeriodCashFlowCalculator( + IFullWealthAndIncomeTaxCalculator fullTaxCalculator, + IFullCapitalBenefitTaxCalculator capitalBenefitCalculator, + IMunicipalityConnector municipalityConnector, + ILogger logger) + { + _fullTaxCalculator = fullTaxCalculator; + _capitalBenefitCalculator = capitalBenefitCalculator; + this.municipalityConnector = municipalityConnector; + _logger = logger; + } + + /// + public async Task> CalculateAsync( + int startingYear, + int minimumNumberOfPeriods, + MultiPeriodCalculatorPerson person, + CashFlowDefinitionHolder cashFlowDefinitionHolder, + MultiPeriodOptions options) + { + DateTime dateOfStart = new DateTime(startingYear, 1, 1); + + ExogenousAccount exogenousAccount = new() { Id = Guid.NewGuid(), Name = "Exogenous Account", }; + + IncomeAccount incomeAccount = new() { Id = Guid.NewGuid(), Name = "Income Account", }; + + WealthAccount wealthAccount = new() { Id = Guid.NewGuid(), Name = "Wealth Account", }; + + InvestmentAccount investmentAccount = SetupInvestmentAccount(cashFlowDefinitionHolder); + + OccupationalPensionAccount occupationalPensionAccount = new() + { + Id = Guid.NewGuid(), Name = "Occupational Pension Account", + }; + + ThirdPillarAccount thirdPillarAccount = new() + { + Id = Guid.NewGuid(), + Name = "Third Pillar Account", + }; + + TaxAccount taxAccount = new() { Id = Guid.NewGuid(), Name = "Tax Account", }; + + IEnumerable definitionFromInvestments = cashFlowDefinitionHolder.InvestmentDefinitions + .SelectMany(investment => investment.CreateGenericDefinition()); + + IEnumerable definitionFromComposites = cashFlowDefinitionHolder.Composites + .SelectMany(composite => composite.CreateGenericDefinition(person, dateOfStart)); + + IEnumerable staticCashFlowsFromComposites = definitionFromComposites + .Concat(definitionFromInvestments) + .OfType() + .SelectMany(d => d.GenerateCashFlow()) + .AggregateCashFlows(); + + IEnumerable staticCashFlowsFromGenerics = cashFlowDefinitionHolder.StaticGenericCashFlowDefinitions + .SelectMany(d => d.GenerateCashFlow()) + .AggregateCashFlows(); + + IEnumerable staticCashFlows = staticCashFlowsFromGenerics + .Concat(staticCashFlowsFromComposites) + .ToList(); + + var combinedList = definitionFromComposites + .Concat(cashFlowDefinitionHolder.CashFlowActions).ToList(); + + int finalYear = combinedList.Count > 0 + ? combinedList.Max(d => d.DateOfProcess.Year) + : startingYear + minimumNumberOfPeriods; + + List singlePeriodCalculationResults = + Enumerable.Empty().ToList(); + + Dictionary currentAccounts = new Dictionary + { + { AccountType.Exogenous, exogenousAccount }, + { AccountType.Income, incomeAccount }, + { AccountType.Wealth, wealthAccount }, + { AccountType.Investment, investmentAccount }, + { AccountType.OccupationalPension, occupationalPensionAccount }, + { AccountType.ThirdPillar, thirdPillarAccount }, + { AccountType.Tax, taxAccount } + }; + + MultiPeriodCalculatorPerson currentPerson = person; + for (int currentYear = startingYear; currentYear <= finalYear; currentYear++) + { + DateOnly startingDate = new DateOnly(currentYear, 1, 1); + DateOnly finalDate = new DateOnly(currentYear, 1, 1).AddYears(1); + + // all days in the current year + for (DateOnly currentDate = startingDate; currentDate < finalDate; currentDate = currentDate.AddDays(1)) + { + var currentDateAsDateTime = currentDate.ToDateTime(TimeOnly.MinValue); + + List currentDateStaticCashFlows = staticCashFlows + .Where(item => item.DateOfProcess == currentDate) + .ToList(); + + List currentDateChangeResidenceActions = cashFlowDefinitionHolder + .ChangeResidenceActions + .Where(item => item.DateOfProcess == currentDateAsDateTime) + .ToList(); + + List currentDateDynamicCashFlows = definitionFromComposites + .Concat(cashFlowDefinitionHolder.CashFlowActions) + .OfType() + .Where(item => item.DateOfProcess == currentDateAsDateTime) + .SelectMany(item => item.CreateGenericDefinition(currentAccounts)) + .SelectMany(item => item.GenerateCashFlow()) + .ToList(); + + // 1. change residence + foreach (var changeAction in currentDateChangeResidenceActions.OfType()) + { + currentPerson = currentPerson with + { + MunicipalityId = changeAction.DestinationMunicipalityId, + Canton = changeAction.DestinationCanton, + }; + + currentAccounts = ProcessResidenceChangeAction(currentAccounts, changeAction); + } + + // 2. process simple cash-flow: move amount from source to target account + foreach (CashFlowModel cashFlow in currentDateStaticCashFlows.Concat(currentDateDynamicCashFlows)) + { + currentAccounts = await ProcessSimpleCashFlowAsync(currentAccounts, cashFlow, person); + } + } + + currentAccounts = await ProcessEndOfYearSettlementAsync(currentAccounts, currentPerson, finalDate, options); + + // collect calculation results + int year = currentYear; + currentAccounts + .Select(pair => new SinglePeriodCalculationResult + { + Year = year, Amount = pair.Value.Balance, AccountType = pair.Key + }) + .Iter(item => singlePeriodCalculationResults.Add(item)); + } + + var accountTransactionResult = new AccountTransactionResult + { + Id = exogenousAccount.Id, + Name = exogenousAccount.Name, + Transactions = exogenousAccount.Transactions, + }; + + var incomeTransactionResult = new AccountTransactionResult + { + Id = incomeAccount.Id, + Name = incomeAccount.Name, + Transactions = incomeAccount.Transactions, + }; + + var wealthTransactionResult = new AccountTransactionResult + { + Id = wealthAccount.Id, + Name = wealthAccount.Name, + Transactions = wealthAccount.Transactions, + }; + + var investmentTransactionResult = new AccountTransactionResult + { + Id = investmentAccount.Id, + Name = investmentAccount.Name, + Transactions = investmentAccount.Transactions, + }; + + var occupationalTransactionResult = new AccountTransactionResult + { + Id = occupationalPensionAccount.Id, + Name = occupationalPensionAccount.Name, + Transactions = occupationalPensionAccount.Transactions, + }; + + var thirdPillarTransactionResult = new AccountTransactionResult + { + Id = thirdPillarAccount.Id, + Name = thirdPillarAccount.Name, + Transactions = thirdPillarAccount.Transactions, + }; + + var taxTransactionResult = new AccountTransactionResult + { + Id = taxAccount.Id, + Name = taxAccount.Name, + Transactions = taxAccount.Transactions, + }; + + return new MultiPeriodCalculationResult + { + StartingYear = startingYear, + NumberOfPeriods = finalYear - startingYear + 1, + Accounts = singlePeriodCalculationResults, + Transactions = new AccountTransactionResultHolder + { + ExogenousAccount = accountTransactionResult, + IncomeAccount = incomeTransactionResult, + WealthAccount = wealthTransactionResult, + InvestmentAccount = investmentTransactionResult, + OccupationalPensionAccount = occupationalTransactionResult, + ThirdPillarAccount = thirdPillarTransactionResult, + TaxAccount = taxTransactionResult + } + }; + } + + private static InvestmentAccount SetupInvestmentAccount(CashFlowDefinitionHolder cashFlowDefinitionHolder) + { + InvestmentPortfolioDefinition firstInvestmentAccount = cashFlowDefinitionHolder.InvestmentDefinitions.FirstOrDefault(); + + if (firstInvestmentAccount == null) + { + return new InvestmentAccount + { + Id = Guid.NewGuid(), + Name = "Investment Account", + NetGrowthRate = 0.0M, + NetIncomeYield = 0.0M, + }; + } + + return new() + { + Id = Guid.NewGuid(), + Name = firstInvestmentAccount.Header.Name, + NetGrowthRate = firstInvestmentAccount.NetCapitalGrowthRate, + NetIncomeYield = firstInvestmentAccount.NetIncomeRate, + }; + } + + public Task> CalculateWithSetupsAsync( + int startingYear, + int minimumNumberOfPeriods, + MultiPeriodCalculatorPerson person, + CashFlowDefinitionHolder cashFlowDefinitionHolder, + MultiPeriodOptions options) + { + ICompositeCashFlowDefinition accountSetupDefinition = new SetupAccountDefinition + { + InitialOccupationalPensionAssets = person.CapitalBenefits.PensionPlan + person.CapitalBenefits.Pillar3a, + InitialWealth = person.Wealth + }; + + CashFlowDefinitionHolder extendedDefinitionHolder = cashFlowDefinitionHolder with + { + Composites = cashFlowDefinitionHolder.Composites + .Concat(new[] { accountSetupDefinition }) + .ToList() + }; + + return CalculateAsync(startingYear, minimumNumberOfPeriods, person, extendedDefinitionHolder, options); + } + + private async Task> ProcessSimpleCashFlowAsync( + Dictionary currentAccounts, CashFlowModel cashFlow, MultiPeriodCalculatorPerson person) + { + ICashFlowAccount creditAccount = currentAccounts[cashFlow.Target]; + ICashFlowAccount debitAccount = currentAccounts[cashFlow.Source]; + + ExecuteTransaction(debitAccount, creditAccount, "Simple cash-flow", cashFlow.DateOfProcess.ToDateTime(TimeOnly.MinValue), cashFlow.Amount); + + if (cashFlow.IsTaxable) + { + // Tax reduces wealth as transaction is taxable. + if (cashFlow.TaxType == TaxType.CapitalBenefits) + { + var taxPaymentAmount = await CalculateCapitalBenefitsTaxAsync(cashFlow.DateOfProcess.Year, person, cashFlow.Amount); + + ICashFlowAccount wealthAccount = currentAccounts[AccountType.Wealth]; + ICashFlowAccount taxAccount = currentAccounts[AccountType.Tax]; + + ExecuteTransaction(wealthAccount, taxAccount, "Tax payment", cashFlow.DateOfProcess.ToDateTime(TimeOnly.MinValue), taxPaymentAmount); + } + } + + return currentAccounts; + } + + private Dictionary ProcessResidenceChangeAction( + Dictionary currentAccounts, ChangeResidenceAction action) + { + ICashFlowAccount wealthAccount = currentAccounts[AccountType.Wealth]; + ICashFlowAccount exogenousAccount = currentAccounts[AccountType.Exogenous]; + + ExecuteTransaction(wealthAccount, exogenousAccount, "Residence change costs", action.DateOfProcess, action.ChangeCost); + + return currentAccounts; + } + + private async Task> ProcessEndOfYearSettlementAsync( + Dictionary currentAccounts, + MultiPeriodCalculatorPerson person, + DateOnly finalDate, + MultiPeriodOptions options) + { + ICashFlowAccount incomeAccount = currentAccounts[AccountType.Income]; + ICashFlowAccount wealthAccount = currentAccounts[AccountType.Wealth]; + ICashFlowAccount investmentAccount = currentAccounts[AccountType.Investment]; + ICashFlowAccount exogenousAccount = currentAccounts[AccountType.Exogenous]; + ICashFlowAccount occupationalPensionAccount = currentAccounts[AccountType.OccupationalPension]; + ICashFlowAccount thirdPillarAccount = currentAccounts[AccountType.ThirdPillar]; + ICashFlowAccount taxAccount = currentAccounts[AccountType.Tax]; + + DateTime finalDateAsDateTime = finalDate.ToDateTime(TimeOnly.MinValue); + + // compound wealth and capital benefits accounts + // todo: instead of assuming account balance is compounded a full year a time-weighted calculation should be used (TWR). + // This can be achieved by the ordered list of transaction on the accounts. + decimal wealthCompoundedReturn = wealthAccount.Balance * options.WealthNetGrowthRate; + ExecuteTransaction(exogenousAccount, wealthAccount, "Compound Return Wealth", finalDateAsDateTime, wealthCompoundedReturn); + + InvestmentTransactions(investmentAccount); + + decimal occupationalPensionCompoundedReturn = occupationalPensionAccount.Balance * options.CapitalBenefitsNetGrowthRate; + ExecuteTransaction(exogenousAccount, occupationalPensionAccount, "Compound Return Occupational Pension", finalDateAsDateTime, occupationalPensionCompoundedReturn); + + decimal thirdPillarCompoundedReturn = thirdPillarAccount.Balance * options.CapitalBenefitsNetGrowthRate; + ExecuteTransaction(exogenousAccount, thirdPillarAccount, "Compound Return Third Pillar", finalDateAsDateTime, thirdPillarCompoundedReturn); + + // savings quota: take share from current income account and move it to wealth + decimal newSavings = incomeAccount.Balance * SavingsQuota(finalDate, options, person); + + // savings are subject to wealth tax but are not deducted from the income/salary account to keep taxable salary amount clean + ExecuteTransaction(exogenousAccount, wealthAccount, "Savings Quota", finalDateAsDateTime, newSavings); + + // take each account amount, calculate tax, and deduct it from wealth + var totalTaxAmount = + await CalculateIncomeAndWealthTaxAsync(finalDate.Year, person, incomeAccount.Balance, wealthAccount.Balance + investmentAccount.Balance); + + ExecuteTransaction(wealthAccount, taxAccount, "Yearly Income and Wealth Tax", finalDateAsDateTime, totalTaxAmount); + + // Clear income account as it begin at 0 in the new year + ExecuteTransaction(incomeAccount, exogenousAccount, "Clear income account", finalDateAsDateTime, incomeAccount.Balance); + + return currentAccounts; + + void InvestmentTransactions(ICashFlowAccount cashFlowAccount) + { + var account = cashFlowAccount as InvestmentAccount; + + if (account == null) + { + throw new ArgumentException($"Account {cashFlowAccount.Name} is not a investment account", nameof(cashFlowAccount)); + } + + decimal investmentCapitalGain = account.Balance * account.NetGrowthRate; + decimal investmentIncome = account.Balance * account.NetIncomeYield; + + ExecuteTransaction(exogenousAccount, investmentAccount, "Capital Gains", finalDateAsDateTime, investmentCapitalGain); + ExecuteTransaction(exogenousAccount, investmentAccount, "Income", finalDateAsDateTime, investmentIncome); + + // income is taxable + ExecuteTransaction(exogenousAccount, incomeAccount, "Investement Income", finalDateAsDateTime, investmentCapitalGain); + } + } + + private async Task CalculateIncomeAndWealthTaxAsync( + int currentYear, MultiPeriodCalculatorPerson calculatorPerson, decimal income, decimal wealth) + { + TaxPerson taxPerson = new() + { + Name = calculatorPerson.Name, + CivilStatus = calculatorPerson.CivilStatus, + NumberOfChildren = calculatorPerson.NumberOfChildren, + ReligiousGroupType = calculatorPerson.ReligiousGroupType, + PartnerReligiousGroupType = calculatorPerson.PartnerReligiousGroupType, + TaxableWealth = Math.Max(0, wealth), + TaxableFederalIncome = income, + TaxableIncome = income + }; + + Either municipality = + await municipalityConnector.GetAsync(calculatorPerson.MunicipalityId, currentYear); + + Either result = await municipality + .BindAsync(m => _fullTaxCalculator.CalculateAsync(currentYear, m, taxPerson, true)); + + return result.Match( + Right: r => r.TotalTaxAmount, + Left: error => + { + _logger.LogError(error); + return decimal.Zero; + }); + } + + private async Task CalculateCapitalBenefitsTaxAsync( + int currentYear, MultiPeriodCalculatorPerson person, decimal amount) + { + CapitalBenefitTaxPerson taxPerson = new() + { + Name = person.Name, + CivilStatus = person.CivilStatus, + NumberOfChildren = person.NumberOfChildren, + ReligiousGroupType = person.ReligiousGroupType, + PartnerReligiousGroupType = person.PartnerReligiousGroupType, + TaxableCapitalBenefits = amount + }; + + Either municipality = + await municipalityConnector.GetAsync(person.MunicipalityId, currentYear); + + Either result = await municipality + .BindAsync(m => _capitalBenefitCalculator.CalculateAsync( + currentYear, m, taxPerson, true)); + + return result.Match( + Right: r => r.TotalTaxAmount, + Left: error => + { + _logger.LogError(error); + return decimal.Zero; + + }); + } + + private static void ExecuteTransaction( + ICashFlowAccount debitAccount, ICashFlowAccount creditAccount, string description, DateTime transactionDate, decimal amount) + { + if (amount == decimal.Zero) + { + return; + } + + AccountTransaction trxCreditAccount = + new($"{description}: inflow from {debitAccount.Name}", transactionDate, amount); + + creditAccount.Balance += amount; + creditAccount.Transactions.Add(trxCreditAccount); + + + AccountTransaction trxDebitAccount = + new($"{description}: outflow to {creditAccount.Name}", transactionDate, -amount); + + debitAccount.Balance -= amount; + debitAccount.Transactions.Add(trxDebitAccount); + } + + private decimal SavingsQuota(DateOnly dateOfValidity, MultiPeriodOptions options, MultiPeriodCalculatorPerson person) + { + DateOnly retirementDate = DateOnly.FromDateTime(person.DateOfBirth.GetRetirementDate(person.Gender)); + + if (dateOfValidity < retirementDate) + { + return options.SavingsQuota; + } + + return decimal.Zero; + } + } +} diff --git a/src/TaxCalculator.Abstractions/IMunicipalityConnector.cs b/src/Application/Municipality/IMunicipalityConnector.cs similarity index 70% rename from src/TaxCalculator.Abstractions/IMunicipalityConnector.cs rename to src/Application/Municipality/IMunicipalityConnector.cs index eca0a337..b28bce84 100644 --- a/src/TaxCalculator.Abstractions/IMunicipalityConnector.cs +++ b/src/Application/Municipality/IMunicipalityConnector.cs @@ -1,10 +1,8 @@ -using System.Collections.Generic; -using System.Threading.Tasks; +using Domain.Models.Municipality; +using Domain.Models.Tax; using LanguageExt; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -namespace PensionCoach.Tools.TaxCalculator.Abstractions +namespace Application.Municipality { public interface IMunicipalityConnector { diff --git a/src/Application/Municipality/IMunicipalityRepository.cs b/src/Application/Municipality/IMunicipalityRepository.cs new file mode 100644 index 00000000..0090c2be --- /dev/null +++ b/src/Application/Municipality/IMunicipalityRepository.cs @@ -0,0 +1,15 @@ +using Domain.Models.Municipality; + +namespace Application.Municipality +{ + public interface IMunicipalityRepository + { + IEnumerable GetAll(); + + MunicipalityEntity Get(int bfsNumber, int year); + + IEnumerable GetAllSupportTaxCalculation(); + + IEnumerable Search(MunicipalitySearchFilter searchFilter); + } +} diff --git a/src/Application/Municipality/ProprietaryMunicipalityConnector.cs b/src/Application/Municipality/ProprietaryMunicipalityConnector.cs new file mode 100644 index 00000000..38624f25 --- /dev/null +++ b/src/Application/Municipality/ProprietaryMunicipalityConnector.cs @@ -0,0 +1,98 @@ +using Application.Tax.Proprietary.Repositories; +using AutoMapper; +using Domain.Enums; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Municipality +{ + public class ProprietaryMunicipalityConnector : IMunicipalityConnector + { + private readonly IMapper mapper; + private readonly IMunicipalityRepository municipalityRepository; + private readonly IStateTaxRateRepository stateTaxRateRepository; + + public ProprietaryMunicipalityConnector( + IMapper mapper, + IMunicipalityRepository municipalityRepository, + IStateTaxRateRepository stateTaxRateRepository) + { + this.mapper = mapper; + this.municipalityRepository = municipalityRepository; + this.stateTaxRateRepository = stateTaxRateRepository; + } + + public Task> GetAllAsync() + { + return Task.FromResult(mapper.Map>(municipalityRepository.GetAll())); + } + + /// + /// Searches the specified search filter. + /// + /// The search filter. + /// List of municipalities. + public IEnumerable Search(MunicipalitySearchFilter searchFilter) + { + foreach (var entity in municipalityRepository.Search(searchFilter)) + { + var model = mapper.Map(entity); + + if (searchFilter.YearOfValidity.HasValue) + { + if (!model.DateOfMutation.HasValue) + { + yield return model; + } + else if (model.DateOfMutation.Value.Year > searchFilter.YearOfValidity) + { + yield return model; + } + } + else + { + yield return model; + } + } + } + + public Task> GetAsync(int bfsNumber, int year) + { + Option entity = municipalityRepository.GetAll() + .FirstOrDefault(item => item.BfsNumber == bfsNumber + && string.IsNullOrEmpty(item.DateOfMutation)); + + return entity + .Match>( + Some: item => mapper.Map(item), + None: () => $"Municipality not found by BFS number {bfsNumber}") + .AsTask(); + } + + /// + public Task> GetAllSupportTaxCalculationAsync() + { + IReadOnlyCollection municipalities = + stateTaxRateRepository.TaxRates() + .AsEnumerable() + .GroupBy(keySelector => new + { + Id = keySelector.BfsId, + Name = keySelector.MunicipalityName, + keySelector.Canton, + }) + .Select(item => new TaxSupportedMunicipalityModel + { + BfsMunicipalityNumber = item.Key.Id, + Name = item.Key.Name, + Canton = Enum.Parse(item.Key.Canton), + MaxSupportedYear = item.Max(entity => entity.Year), + }) + .OrderBy(item => item.Name) + .ToList(); + + return Task.FromResult(municipalities); + } + } +} diff --git a/src/Application/Tax.Contracts/IFullCapitalBenefitTaxCalculator.cs b/src/Application/Tax.Contracts/IFullCapitalBenefitTaxCalculator.cs new file mode 100644 index 00000000..ce8bd4df --- /dev/null +++ b/src/Application/Tax.Contracts/IFullCapitalBenefitTaxCalculator.cs @@ -0,0 +1,14 @@ +using Domain.Models.Municipality; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Tax.Contracts; + +public interface IFullCapitalBenefitTaxCalculator +{ + Task> CalculateAsync( + int calculationYear, + MunicipalityModel municipality, + CapitalBenefitTaxPerson person, + bool withMaxAvailableCalculationYear = false); +} diff --git a/src/Application/Tax.Contracts/IFullWealthAndIncomeTaxCalculator.cs b/src/Application/Tax.Contracts/IFullWealthAndIncomeTaxCalculator.cs new file mode 100644 index 00000000..7f85edc4 --- /dev/null +++ b/src/Application/Tax.Contracts/IFullWealthAndIncomeTaxCalculator.cs @@ -0,0 +1,11 @@ +using Domain.Models.Municipality; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Tax.Contracts; + +public interface IFullWealthAndIncomeTaxCalculator +{ + Task> CalculateAsync( + int calculationYear, MunicipalityModel municipality, TaxPerson person, bool withMaxAvailableCalculationYear = false); +} diff --git a/src/Application/Tax.Estv/Client/IEstvCalculatorClient.cs b/src/Application/Tax.Estv/Client/IEstvCalculatorClient.cs new file mode 100644 index 00000000..8c52957c --- /dev/null +++ b/src/Application/Tax.Estv/Client/IEstvCalculatorClient.cs @@ -0,0 +1,14 @@ +using Application.Tax.Estv.Client.Models; +using Domain.Models.Tax; + +namespace Application.Tax.Estv.Client +{ + public interface IEstvTaxCalculatorClient + { + Task GetTaxLocationsAsync(string zip, string city); + + Task CalculateIncomeAndWealthTaxAsync(int taxLocationId, int taxYear, TaxPerson person); + + Task CalculateCapitalBenefitTaxAsync(int taxLocationId, int taxYear, CapitalBenefitTaxPerson person); + } +} diff --git a/src/Application/Tax.Estv/Client/Models/ChildModel.cs b/src/Application/Tax.Estv/Client/Models/ChildModel.cs new file mode 100644 index 00000000..7e8f3b09 --- /dev/null +++ b/src/Application/Tax.Estv/Client/Models/ChildModel.cs @@ -0,0 +1,7 @@ +namespace Application.Tax.Estv.Client.Models +{ + public class ChildModel + { + public int Age { get; set; } + } +} diff --git a/src/Application/Tax.Estv/Client/Models/SimpleCapitalTaxResult.cs b/src/Application/Tax.Estv/Client/Models/SimpleCapitalTaxResult.cs new file mode 100644 index 00000000..fe1f2d09 --- /dev/null +++ b/src/Application/Tax.Estv/Client/Models/SimpleCapitalTaxResult.cs @@ -0,0 +1,12 @@ +namespace Application.Tax.Estv.Client.Models +{ + public class + SimpleCapitalTaxResult + { + public int TaxCanton { get; set; } + public int TaxChurch { get; set; } + public int TaxCity { get; set; } + public int TaxFed { get; set; } + public TaxLocation Location { get; set; } + } +} diff --git a/src/Application/Tax.Estv/Client/Models/SimpleTaxResult.cs b/src/Application/Tax.Estv/Client/Models/SimpleTaxResult.cs new file mode 100644 index 00000000..480c2cf5 --- /dev/null +++ b/src/Application/Tax.Estv/Client/Models/SimpleTaxResult.cs @@ -0,0 +1,20 @@ +namespace Application.Tax.Estv.Client.Models +{ + public class SimpleTaxResult + { + public int IncomeSimpleTaxCanton { get; set; } + public int FortuneTaxCanton { get; set; } + public int IncomeSimpleTaxCity { get; set; } + public int IncomeTaxChurch { get; set; } + public int IncomeTaxCity { get; set; } + public int IncomeSimpleTaxFed { get; set; } + public int PersonalTax { get; set; } + public int FortuneTaxCity { get; set; } + public int FortuneSimpleTaxCanton { get; set; } + public int IncomeTaxFed { get; set; } + public int FortuneSimpleTaxCity { get; set; } + public int IncomeTaxCanton { get; set; } + public TaxLocation Location { get; set; } + public int FortuneTaxChurch { get; set; } + } +} diff --git a/src/Application/Tax.Estv/Client/Models/TaxLocation.cs b/src/Application/Tax.Estv/Client/Models/TaxLocation.cs new file mode 100644 index 00000000..5138ff30 --- /dev/null +++ b/src/Application/Tax.Estv/Client/Models/TaxLocation.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace Application.Tax.Estv.Client.Models +{ + public class TaxLocation + { + [JsonPropertyName("TaxLocationID")] + public int Id { get; set; } + + public string ZipCode { get; set; } + + [JsonPropertyName("BfsID")] + public int BfsId { get; set; } + + [JsonPropertyName("CantonID")] + public int CantonId { get; set; } + + public string BfsName { get; set; } + + public string City { get; set; } + + public string Canton { get; set; } + } +} diff --git a/src/Application/Tax.Estv/Client/Models/TaxLocationFilter.cs b/src/Application/Tax.Estv/Client/Models/TaxLocationFilter.cs new file mode 100644 index 00000000..3e949bfb --- /dev/null +++ b/src/Application/Tax.Estv/Client/Models/TaxLocationFilter.cs @@ -0,0 +1,9 @@ +namespace Application.Tax.Estv.Client.Models +{ + public class TaxLocationRequest + { + public string Search { get; set; } + + public int Language { get; set; } = 1; + } +} diff --git a/src/Application/Tax.Estv/EstvFullCapitalBenefitTaxCalculator.cs b/src/Application/Tax.Estv/EstvFullCapitalBenefitTaxCalculator.cs new file mode 100644 index 00000000..625d0376 --- /dev/null +++ b/src/Application/Tax.Estv/EstvFullCapitalBenefitTaxCalculator.cs @@ -0,0 +1,68 @@ +using Application.Features.FullTaxCalculation; +using Application.Tax.Contracts; +using Application.Tax.Estv.Client; +using Application.Tax.Estv.Client.Models; +using Application.Tax.Proprietary.Abstractions.Models; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Tax.Estv +{ + /// + /// Facade for the ESTV tax calculation service. + /// + public class EstvFullCapitalBenefitTaxCalculator : IFullCapitalBenefitTaxCalculator + { + private readonly IEstvTaxCalculatorClient estvTaxCalculatorClient; + private readonly ITaxSupportedYearProvider taxSupportedYearProvider; + + public EstvFullCapitalBenefitTaxCalculator( + IEstvTaxCalculatorClient estvTaxCalculatorClient, + ITaxSupportedYearProvider taxSupportedYearProvider) + { + this.estvTaxCalculatorClient = estvTaxCalculatorClient; + this.taxSupportedYearProvider = taxSupportedYearProvider; + } + + public async Task> CalculateAsync( + int calculationYear, + MunicipalityModel municipality, + CapitalBenefitTaxPerson person, + bool withMaxAvailableCalculationYear = false) + { + if (!municipality.EstvTaxLocationId.HasValue) + { + return "ESTV tax location id is null"; + } + + int supportedTaxYear = taxSupportedYearProvider.MapToSupportedYear(calculationYear); + + SimpleCapitalTaxResult calculationResult = await estvTaxCalculatorClient.CalculateCapitalBenefitTaxAsync( + municipality.EstvTaxLocationId.Value, supportedTaxYear, person); + + decimal municipalityRate = calculationResult.TaxCanton == 0 + ? decimal.Zero + : calculationResult.TaxCity / (decimal)calculationResult.TaxCanton * 100M; + + return new FullCapitalBenefitTaxResult + { + FederalResult = new BasisTaxResult { TaxAmount = calculationResult.TaxFed }, + StateResult = new CapitalBenefitTaxResult + { + MunicipalityRate = municipalityRate, + CantonRate = 100, + ChurchTax = new ChurchTaxResult + { + TaxAmount = calculationResult.TaxChurch, + }, + BasisTax = new BasisTaxResult + { + TaxAmount = calculationResult.TaxCanton, + DeterminingFactorTaxableAmount = municipalityRate + } + } + }; + } + } +} diff --git a/src/Application/Tax.Estv/EstvFullTaxCalculator.cs b/src/Application/Tax.Estv/EstvFullTaxCalculator.cs new file mode 100644 index 00000000..0b779a89 --- /dev/null +++ b/src/Application/Tax.Estv/EstvFullTaxCalculator.cs @@ -0,0 +1,79 @@ +using Application.Features.FullTaxCalculation; +using Application.Tax.Contracts; +using Application.Tax.Estv.Client; +using Application.Tax.Estv.Client.Models; +using Application.Tax.Proprietary.Abstractions.Models; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Tax.Estv +{ + public class EstvFullTaxCalculator : IFullWealthAndIncomeTaxCalculator + { + private readonly IEstvTaxCalculatorClient estvTaxCalculatorClient; + private readonly ITaxSupportedYearProvider taxSupportedYearProvider; + + public EstvFullTaxCalculator(IEstvTaxCalculatorClient estvTaxCalculatorClient, ITaxSupportedYearProvider taxSupportedYearProvider) + { + this.estvTaxCalculatorClient = estvTaxCalculatorClient; + this.taxSupportedYearProvider = taxSupportedYearProvider; + } + + public async Task> CalculateAsync( + int calculationYear, MunicipalityModel municipality, TaxPerson person, bool withMaxAvailableCalculationYear = false) + { + if (!municipality.EstvTaxLocationId.HasValue) + { + return "ESTV tax location id is null"; + } + + int supportedTaxYear = taxSupportedYearProvider.MapToSupportedYear(calculationYear); + + SimpleTaxResult estvResult = await estvTaxCalculatorClient.CalculateIncomeAndWealthTaxAsync( + municipality.EstvTaxLocationId.Value, supportedTaxYear, person); + + decimal simpleTaxRate = decimal.Zero; + if (estvResult.IncomeTaxCanton > decimal.Zero) + { + simpleTaxRate = estvResult.IncomeTaxCity / (decimal)estvResult.IncomeTaxCanton * 100M; + } + + decimal wealthTaxRate = decimal.Zero; + if (estvResult.FortuneTaxCanton > decimal.Zero) + { + simpleTaxRate = estvResult.FortuneTaxCity / (decimal)estvResult.FortuneTaxCanton * 100M; + } + + return new FullTaxResult + { + FederalTaxResult = new BasisTaxResult + { + TaxAmount = estvResult.IncomeTaxFed, + DeterminingFactorTaxableAmount = decimal.Zero + }, + StateTaxResult = new StateTaxResult + { + BasisIncomeTax = new BasisTaxResult + { + TaxAmount = estvResult.IncomeTaxCanton, + DeterminingFactorTaxableAmount = simpleTaxRate + }, + BasisWealthTax = new BasisTaxResult + { + TaxAmount = estvResult.FortuneTaxCanton, + DeterminingFactorTaxableAmount = wealthTaxRate + }, + ChurchTax = new ChurchTaxResult + { + TaxAmount = estvResult.IncomeTaxChurch + estvResult.FortuneTaxChurch, + TaxAmountPartner = null, + }, + PollTaxAmount = estvResult.PersonalTax, + CantonRate = 100M, + MunicipalityRate = simpleTaxRate + } + }; + } + } +} diff --git a/src/Application/Tax.Estv/EstvMunicipalityConnector.cs b/src/Application/Tax.Estv/EstvMunicipalityConnector.cs new file mode 100644 index 00000000..15fbce63 --- /dev/null +++ b/src/Application/Tax.Estv/EstvMunicipalityConnector.cs @@ -0,0 +1,90 @@ +using Application.Municipality; +using AutoMapper; +using Domain.Enums; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Tax.Estv +{ + public class EstvMunicipalityConnector : IMunicipalityConnector + { + private readonly IMapper mapper; + private readonly IMunicipalityRepository municipalityRepository; + + public EstvMunicipalityConnector( + IMapper mapper, + IMunicipalityRepository municipalityRepository) + { + this.mapper = mapper; + this.municipalityRepository = municipalityRepository; + } + + public Task> GetAllAsync() + { + return Task.FromResult( + mapper.Map>(municipalityRepository.GetAll())); + } + + /// + /// Searches the specified search filter. + /// + /// The search filter. + /// List of municipalities. + public IEnumerable Search(MunicipalitySearchFilter searchFilter) + { + foreach (MunicipalityEntity entity in municipalityRepository.Search(searchFilter)) + { + var model = mapper.Map(entity); + + if (searchFilter.YearOfValidity.HasValue) + { + if (!model.DateOfMutation.HasValue) + { + yield return model; + } + else if (model.DateOfMutation.Value.Year > searchFilter.YearOfValidity) + { + yield return model; + } + } + else + { + yield return model; + } + } + } + + public Task> GetAsync(int bfsNumber, int year) + { + Option entity = + municipalityRepository.Get(bfsNumber, year); + + return entity + .Match>( + Some: item => mapper.Map(item), + None: () => $"Municipality not found by BFS number {bfsNumber}") + .AsTask(); + } + + /// + public Task> GetAllSupportTaxCalculationAsync() + { + const int maxEstvSupportedYear = 2022; + + IReadOnlyCollection list = municipalityRepository + .GetAllSupportTaxCalculation() + .Select(item => new TaxSupportedMunicipalityModel + { + BfsMunicipalityNumber = item.BfsNumber, + Name = item.Name, + Canton = Enum.Parse(item.Canton), + MaxSupportedYear = maxEstvSupportedYear, + EstvTaxLocationId = item.TaxLocationId + }) + .ToList(); + + return Task.FromResult(list); + } + } +} diff --git a/src/Application/Tax.Estv/EstvTaxSupportedYearProvider.cs b/src/Application/Tax.Estv/EstvTaxSupportedYearProvider.cs new file mode 100644 index 00000000..30a87136 --- /dev/null +++ b/src/Application/Tax.Estv/EstvTaxSupportedYearProvider.cs @@ -0,0 +1,19 @@ +using Application.Features.FullTaxCalculation; + +namespace Application.Tax.Estv +{ + public class EstvTaxSupportedYearProvider : ITaxSupportedYearProvider + { + private readonly int[] supportedTaxYears = { 2019, 2020, 2021, 2022, 2023 }; + + public int[] GetSupportedTaxYears() + { + return supportedTaxYears; + } + + public int MapToSupportedYear(int taxYear) + { + return GetSupportedTaxYears().Max(); + } + } +} diff --git a/src/Application/Tax.Mock/MockedFullTaxCalculator.cs b/src/Application/Tax.Mock/MockedFullTaxCalculator.cs new file mode 100644 index 00000000..df71384b --- /dev/null +++ b/src/Application/Tax.Mock/MockedFullTaxCalculator.cs @@ -0,0 +1,91 @@ +using Application.Features.FullTaxCalculation; +using Application.Municipality; +using Application.Tax.Contracts; +using Application.Tax.Proprietary; +using Domain.Enums; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Tax.Mock +{ + public class MockedFullTaxCalculator + : IFullWealthAndIncomeTaxCalculator, IFullCapitalBenefitTaxCalculator, IMunicipalityConnector, ITaxSupportedYearProvider + { + const int DefaultBfsMunicipalityId = 261; + const Canton DefaultCanton = Canton.ZH; + + private readonly ProprietaryFullTaxCalculator fullTaxCalculator; + private readonly ProprietaryFullCapitalBenefitTaxCalculator fullCapitalBenefitTaxCalculator; + + public MockedFullTaxCalculator( + ProprietaryFullTaxCalculator fullTaxCalculator, + ProprietaryFullCapitalBenefitTaxCalculator fullCapitalBenefitTaxCalculator) + { + this.fullTaxCalculator = fullTaxCalculator; + this.fullCapitalBenefitTaxCalculator = fullCapitalBenefitTaxCalculator; + } + + public Task> CalculateAsync( + int calculationYear, MunicipalityModel municipality, TaxPerson person, bool withMaxAvailableCalculationYear = false) + { + MunicipalityModel adaptedModel = GetAdaptedModel(); + + return fullTaxCalculator.CalculateAsync(calculationYear, adaptedModel, person, withMaxAvailableCalculationYear); + } + + public Task> CalculateAsync( + int calculationYear, MunicipalityModel municipality, CapitalBenefitTaxPerson person, bool withMaxAvailableCalculationYear = false) + { + MunicipalityModel adaptedModel = GetAdaptedModel(); + + return fullCapitalBenefitTaxCalculator.CalculateAsync(calculationYear, adaptedModel, person, withMaxAvailableCalculationYear); + } + + public Task> GetAllAsync() + { + return Search(null).AsTask(); + } + + public IEnumerable Search(MunicipalitySearchFilter searchFilter) + { + yield return GetAdaptedModel(); + } + + public Task> GetAsync(int bfsNumber, int year) + { + Either municipality = GetAdaptedModel(); + + return municipality.AsTask(); + } + + public Task> GetAllSupportTaxCalculationAsync() + { + MunicipalityModel adaptedModel = GetAdaptedModel(); + + IReadOnlyCollection municipalities = new List + { + new() { MaxSupportedYear = 2022, BfsMunicipalityNumber = adaptedModel.BfsNumber, Canton = adaptedModel.Canton } + }; + + return municipalities.AsTask(); + } + + public int[] GetSupportedTaxYears() + { + int[] years = { 2022, 2023 }; + + return years; + } + + public int MapToSupportedYear(int taxYear) + { + return GetSupportedTaxYears().Max(); + } + + private MunicipalityModel GetAdaptedModel() + { + return new MunicipalityModel { BfsNumber = DefaultBfsMunicipalityId, Canton = DefaultCanton }; + } + } +} diff --git a/src/Application/Tax.Proprietary/Basis/CapitalBenefit/MissingCapitalBenefitTaxCalculator.cs b/src/Application/Tax.Proprietary/Basis/CapitalBenefit/MissingCapitalBenefitTaxCalculator.cs new file mode 100644 index 00000000..6188a909 --- /dev/null +++ b/src/Application/Tax.Proprietary/Basis/CapitalBenefit/MissingCapitalBenefitTaxCalculator.cs @@ -0,0 +1,32 @@ +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax; +using LanguageExt; +using Microsoft.Extensions.Logging; + +namespace Application.Tax.Proprietary.Basis.CapitalBenefit; + +/// +/// Null calculator for missing capital benefit calculator. +/// +public class MissingCapitalBenefitTaxCalculator : ICapitalBenefitTaxCalculator +{ + private readonly ILogger logger; + + public MissingCapitalBenefitTaxCalculator(ILogger logger) + { + this.logger = logger; + } + + public Task> CalculateAsync( + int calculationYear, int municipalityId, Canton canton, CapitalBenefitTaxPerson person) + { + string msg = $"No capital benefit tax calculator for canton {canton} available"; + + Either result = msg; + + logger.LogWarning(msg); + + return Task.FromResult(result); + } +} diff --git a/src/Application/Tax.Proprietary/Basis/CapitalBenefit/SGCapitalBenefitTaxCalculator.cs b/src/Application/Tax.Proprietary/Basis/CapitalBenefit/SGCapitalBenefitTaxCalculator.cs new file mode 100644 index 00000000..ddfd4017 --- /dev/null +++ b/src/Application/Tax.Proprietary/Basis/CapitalBenefit/SGCapitalBenefitTaxCalculator.cs @@ -0,0 +1,107 @@ +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using Application.Tax.Proprietary.Models; +using Application.Tax.Proprietary.Repositories; +using AutoMapper; +using Domain.Enums; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; +using FluentValidation; +using LanguageExt; +using static LanguageExt.Prelude; + +namespace Application.Tax.Proprietary.Basis.CapitalBenefit; + +public class SGCapitalBenefitTaxCalculator : ICapitalBenefitTaxCalculator +{ + private readonly IStateTaxRateRepository stateTaxRateRepository; + private readonly IMapper mapper; + private readonly IValidator validator; + private readonly IChurchTaxCalculator churchTaxCalculator; + + public SGCapitalBenefitTaxCalculator( + IMapper mapper, + IValidator validator, + IChurchTaxCalculator churchTaxCalculator, + IStateTaxRateRepository stateTaxRateRepository) + { + this.stateTaxRateRepository = stateTaxRateRepository; + this.mapper = mapper; + this.validator = validator; + this.churchTaxCalculator = churchTaxCalculator; + } + + /// + public async Task> CalculateAsync( + int calculationYear, + int municipalityId, + Canton canton, + CapitalBenefitTaxPerson capitalBenefitTaxPerson) + { + const decimal taxRateForSingle = 2.2M / 100M; + const decimal taxRateForMarried = 2.0M / 100M; + + var validationResult = validator.Validate(capitalBenefitTaxPerson); + if (!validationResult.IsValid) + { + return + string.Join(";", validationResult.Errors.Select(x => x.ErrorMessage)); + } + + var taxRateEntity = stateTaxRateRepository.TaxRates(calculationYear, municipalityId); + + if (taxRateEntity == null) + { + return + $"No tax rate available for municipality {municipalityId} and year {calculationYear}"; + } + + BasisTaxResult basisTaxResult = GetBasisCapitalBenefitTaxAmount(capitalBenefitTaxPerson); + + ChurchTaxPerson churchTaxPerson = mapper.Map(capitalBenefitTaxPerson); + + Either churchTaxResult = + await churchTaxCalculator.CalculateAsync( + churchTaxPerson, + taxRateEntity, + new AggregatedBasisTaxResult + { + IncomeTax = basisTaxResult, + WealthTax = new BasisTaxResult(), + }); + + return churchTaxResult.Map(Update); + + CapitalBenefitTaxResult Update(ChurchTaxResult churchResult) + { + return new CapitalBenefitTaxResult + { + BasisTax = basisTaxResult, + ChurchTax = churchResult, + CantonRate = taxRateEntity.TaxRateCanton, + MunicipalityRate = taxRateEntity.TaxRateMunicipality, + }; + } + + BasisTaxResult GetBasisCapitalBenefitTaxAmount(CapitalBenefitTaxPerson person) + { + var amount = Some(person.CivilStatus) + .Match( + Some: status => status switch + { + CivilStatus.Single => + capitalBenefitTaxPerson.TaxableCapitalBenefits * taxRateForSingle, + CivilStatus.Married => + capitalBenefitTaxPerson.TaxableCapitalBenefits * taxRateForMarried, + _ => 0M, + }, + None: () => 0); + + return new BasisTaxResult + { + DeterminingFactorTaxableAmount = amount, + TaxAmount = amount, + }; + } + } +} diff --git a/src/Application/Tax.Proprietary/Basis/CapitalBenefit/SOCapitalBenefitTaxCalculator.cs b/src/Application/Tax.Proprietary/Basis/CapitalBenefit/SOCapitalBenefitTaxCalculator.cs new file mode 100644 index 00000000..e18e6a15 --- /dev/null +++ b/src/Application/Tax.Proprietary/Basis/CapitalBenefit/SOCapitalBenefitTaxCalculator.cs @@ -0,0 +1,72 @@ +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using AutoMapper; +using Domain.Enums; +using Domain.Models.Tax; +using FluentValidation; +using LanguageExt; + +namespace Application.Tax.Proprietary.Basis.CapitalBenefit; + +public class SOCapitalBenefitTaxCalculator : ICapitalBenefitTaxCalculator +{ + private const decimal ScaleFactor = 0.25M; + + private readonly IMapper mapper; + private readonly IValidator validator; + private readonly IStateTaxCalculator stateTaxCalculator; + + public SOCapitalBenefitTaxCalculator( + IMapper mapper, + IValidator validator, + IStateTaxCalculator stateTaxCalculator) + { + this.mapper = mapper; + this.validator = validator; + this.stateTaxCalculator = stateTaxCalculator; + } + + /// + public async Task> CalculateAsync( + int calculationYear, + int municipalityId, + Canton canton, + CapitalBenefitTaxPerson capitalBenefitTaxPerson) + { + var validationResult = validator.Validate(capitalBenefitTaxPerson); + if (!validationResult.IsValid) + { + return + string.Join(";", validationResult.Errors.Select(x => x.ErrorMessage)); + } + + var stateTaxPerson = mapper.Map(capitalBenefitTaxPerson); + stateTaxPerson.TaxableIncome = capitalBenefitTaxPerson.TaxableCapitalBenefits; + + var stateTaxResult = await stateTaxCalculator + .CalculateAsync(calculationYear, municipalityId, Canton.SO, stateTaxPerson); + + return stateTaxResult.Map(Scale); + + static CapitalBenefitTaxResult Scale(StateTaxResult intermediateResult) + { + return new CapitalBenefitTaxResult + { + BasisTax = new BasisTaxResult + { + DeterminingFactorTaxableAmount = + intermediateResult.BasisIncomeTax.DeterminingFactorTaxableAmount * ScaleFactor, + TaxAmount = + intermediateResult.BasisIncomeTax.TaxAmount * ScaleFactor, + }, + ChurchTax = new ChurchTaxResult + { + TaxAmount = intermediateResult.ChurchTax.TaxAmount * ScaleFactor, + TaxAmountPartner = intermediateResult.ChurchTax.TaxAmountPartner * ScaleFactor + }, + CantonRate = intermediateResult.CantonRate, + MunicipalityRate = intermediateResult.MunicipalityRate, + }; + } + } +} diff --git a/src/Application/Tax.Proprietary/Basis/CapitalBenefit/ZHCapitalBenefitTaxCalculator.cs b/src/Application/Tax.Proprietary/Basis/CapitalBenefit/ZHCapitalBenefitTaxCalculator.cs new file mode 100644 index 00000000..9072aa4a --- /dev/null +++ b/src/Application/Tax.Proprietary/Basis/CapitalBenefit/ZHCapitalBenefitTaxCalculator.cs @@ -0,0 +1,80 @@ +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using AutoMapper; +using Domain.Enums; +using Domain.Models.Tax; +using FluentValidation; +using LanguageExt; + +namespace Application.Tax.Proprietary.Basis.CapitalBenefit; + +public class ZHCapitalBenefitTaxCalculator : ICapitalBenefitTaxCalculator +{ + private readonly IStateTaxCalculator stateTaxCalculator; + private readonly IValidator validator; + private readonly IMapper mapper; + + public ZHCapitalBenefitTaxCalculator( + IStateTaxCalculator stateTaxCalculator, + IValidator validator, + IMapper mapper) + { + this.stateTaxCalculator = stateTaxCalculator; + this.validator = validator; + this.mapper = mapper; + } + + /// + public async Task> CalculateAsync( + int calculationYear, + int municipalityId, + Canton canton, + CapitalBenefitTaxPerson capitalBenefitTaxPerson) + { + var validationResult = validator.Validate(capitalBenefitTaxPerson); + if (!validationResult.IsValid) + { + var errorMessageLine = + string.Join(";", validationResult.Errors.Select(x => x.ErrorMessage)); + + return errorMessageLine; + } + + const decimal annuitizeFactor = 10; + TaxPerson taxPerson = mapper.Map(capitalBenefitTaxPerson); + + taxPerson.TaxableIncome = capitalBenefitTaxPerson.TaxableCapitalBenefits / annuitizeFactor; + + var stateTaxResult = await stateTaxCalculator.CalculateAsync( + calculationYear, + municipalityId, + canton, + taxPerson); + + return stateTaxResult + .Map(r => Scale(r, annuitizeFactor)); + } + + private CapitalBenefitTaxResult Scale(StateTaxResult intermediateResult, decimal scaleFactor) + { + var result = new CapitalBenefitTaxResult + { + BasisTax = new BasisTaxResult + { + DeterminingFactorTaxableAmount = + intermediateResult.BasisIncomeTax.DeterminingFactorTaxableAmount * scaleFactor, + TaxAmount = + intermediateResult.BasisIncomeTax.TaxAmount * scaleFactor, + }, + ChurchTax = new ChurchTaxResult + { + TaxAmount = intermediateResult.ChurchTax.TaxAmount * scaleFactor, + TaxAmountPartner = intermediateResult.ChurchTax.TaxAmountPartner * scaleFactor + }, + CantonRate = intermediateResult.CantonRate, + MunicipalityRate = intermediateResult.MunicipalityRate, + }; + + return result; + } +} diff --git a/src/TaxCalculator/Basis/Income/DefaultBasisIncomeTaxCalculator.cs b/src/Application/Tax.Proprietary/Basis/Income/DefaultBasisIncomeTaxCalculator.cs similarity index 82% rename from src/TaxCalculator/Basis/Income/DefaultBasisIncomeTaxCalculator.cs rename to src/Application/Tax.Proprietary/Basis/Income/DefaultBasisIncomeTaxCalculator.cs index 087acbeb..a96a12c9 100644 --- a/src/TaxCalculator/Basis/Income/DefaultBasisIncomeTaxCalculator.cs +++ b/src/Application/Tax.Proprietary/Basis/Income/DefaultBasisIncomeTaxCalculator.cs @@ -1,32 +1,31 @@ -using System.Linq; -using System.Threading.Tasks; +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using Application.Tax.Proprietary.Enums; +using Application.Tax.Proprietary.Repositories; +using Domain.Enums; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; using FluentValidation; using FluentValidation.Results; using LanguageExt; -using PensionCoach.Tools.CommonTypes; using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -using Tax.Data.Abstractions; -using Tax.Data.Abstractions.Models; -namespace PensionCoach.Tools.TaxCalculator.Basis.Income; +namespace Application.Tax.Proprietary.Basis.Income; /// /// Default income tax calculator which is the same for ZH. /// -/// +/// public class DefaultBasisIncomeTaxCalculator : IDefaultBasisIncomeTaxCalculator { private const int IncomeTaxTypeId = (int)TaxType.Income; private readonly IValidator taxPersonValidator; - private readonly ITaxTariffData tariffData; + private readonly ITaxTariffRepository tariffData; public DefaultBasisIncomeTaxCalculator( IValidator taxPersonValidator, - ITaxTariffData tariffData) + ITaxTariffRepository tariffData) { this.taxPersonValidator = taxPersonValidator; this.tariffData = tariffData; diff --git a/src/TaxCalculator/Basis/Income/MissingBasisIncomeTaxCalculator.cs b/src/Application/Tax.Proprietary/Basis/Income/MissingBasisIncomeTaxCalculator.cs similarity index 64% rename from src/TaxCalculator/Basis/Income/MissingBasisIncomeTaxCalculator.cs rename to src/Application/Tax.Proprietary/Basis/Income/MissingBasisIncomeTaxCalculator.cs index ddb4a655..454fce5c 100644 --- a/src/TaxCalculator/Basis/Income/MissingBasisIncomeTaxCalculator.cs +++ b/src/Application/Tax.Proprietary/Basis/Income/MissingBasisIncomeTaxCalculator.cs @@ -1,17 +1,16 @@ -using System.Threading.Tasks; +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax.Person; using LanguageExt; using Microsoft.Extensions.Logging; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -namespace PensionCoach.Tools.TaxCalculator.Basis.Income; +namespace Application.Tax.Proprietary.Basis.Income; /// /// Null basis income calculator for missing cantons. /// -/// +/// public class MissingBasisIncomeTaxCalculator : IBasisIncomeTaxCalculator { private readonly ILogger logger; diff --git a/src/TaxCalculator/Basis/Income/SGBasisIncomeTaxCalculator.cs b/src/Application/Tax.Proprietary/Basis/Income/SGBasisIncomeTaxCalculator.cs similarity index 79% rename from src/TaxCalculator/Basis/Income/SGBasisIncomeTaxCalculator.cs rename to src/Application/Tax.Proprietary/Basis/Income/SGBasisIncomeTaxCalculator.cs index e1f54a7f..3e9a1c5b 100644 --- a/src/TaxCalculator/Basis/Income/SGBasisIncomeTaxCalculator.cs +++ b/src/Application/Tax.Proprietary/Basis/Income/SGBasisIncomeTaxCalculator.cs @@ -1,16 +1,15 @@ -using System.Threading.Tasks; +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax.Person; using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -namespace PensionCoach.Tools.TaxCalculator.Basis.Income; +namespace Application.Tax.Proprietary.Basis.Income; /// /// Basis calculator for SG. /// -/// +/// public class SGBasisIncomeTaxCalculator : IBasisIncomeTaxCalculator { private readonly IDefaultBasisIncomeTaxCalculator defaultBasisIncomeTaxCalculator; diff --git a/src/TaxCalculator/Basis/Income/SOBasisIncomeTaxCalculator.cs b/src/Application/Tax.Proprietary/Basis/Income/SOBasisIncomeTaxCalculator.cs similarity index 81% rename from src/TaxCalculator/Basis/Income/SOBasisIncomeTaxCalculator.cs rename to src/Application/Tax.Proprietary/Basis/Income/SOBasisIncomeTaxCalculator.cs index 50cb43a9..cad87257 100644 --- a/src/TaxCalculator/Basis/Income/SOBasisIncomeTaxCalculator.cs +++ b/src/Application/Tax.Proprietary/Basis/Income/SOBasisIncomeTaxCalculator.cs @@ -1,16 +1,15 @@ -using System.Threading.Tasks; +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax.Person; using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -namespace PensionCoach.Tools.TaxCalculator.Basis.Income; +namespace Application.Tax.Proprietary.Basis.Income; /// /// Basis calculator for SO. /// -/// +/// public class SOBasisIncomeTaxCalculator : IBasisIncomeTaxCalculator { private readonly IDefaultBasisIncomeTaxCalculator defaultBasisIncomeTaxCalculator; diff --git a/src/Application/Tax.Proprietary/Basis/Wealth/MissingBasisWealthTaxCalculator.cs b/src/Application/Tax.Proprietary/Basis/Wealth/MissingBasisWealthTaxCalculator.cs new file mode 100644 index 00000000..b6a5adbd --- /dev/null +++ b/src/Application/Tax.Proprietary/Basis/Wealth/MissingBasisWealthTaxCalculator.cs @@ -0,0 +1,33 @@ +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax.Person; +using LanguageExt; +using Microsoft.Extensions.Logging; + +namespace Application.Tax.Proprietary.Basis.Wealth; + +/// +/// Null calculator for missing wealth calculators. +/// +public class MissingBasisWealthTaxCalculator : IBasisWealthTaxCalculator +{ + private readonly ILogger logger; + + public MissingBasisWealthTaxCalculator(ILogger logger) + { + this.logger = logger; + } + + public Task> CalculateAsync( + int calculationYear, Canton canton, BasisTaxPerson person) + { + string msg = $"No wealth tax calculator for canton {canton} available"; + + Either result = msg; + + logger.LogWarning(msg); + + return Task.FromResult(result); + } +} diff --git a/src/Application/Tax.Proprietary/Basis/Wealth/SGBasisWealthTaxCalculator.cs b/src/Application/Tax.Proprietary/Basis/Wealth/SGBasisWealthTaxCalculator.cs new file mode 100644 index 00000000..d57da236 --- /dev/null +++ b/src/Application/Tax.Proprietary/Basis/Wealth/SGBasisWealthTaxCalculator.cs @@ -0,0 +1,31 @@ +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax.Person; +using LanguageExt; + +namespace Application.Tax.Proprietary.Basis.Wealth; + +public class SGBasisWealthTaxCalculator : IBasisWealthTaxCalculator +{ + private const decimal TaxRate = 1.7M / 1000M; + private const decimal MinLevel = 1000M; + + public Task> CalculateAsync(int calculationYear, Canton canton, BasisTaxPerson person) + { + Either taxResult = new BasisTaxResult(); + + if (person.TaxableAmount < MinLevel) + { + return taxResult.AsTask(); + } + + taxResult.IfRight(r => + { + r.TaxAmount = person.TaxableAmount * TaxRate; + r.DeterminingFactorTaxableAmount = person.TaxableAmount; + }); + + return taxResult.AsTask(); + } +} diff --git a/src/Application/Tax.Proprietary/Basis/Wealth/SOBasisWealthTaxCalculator.cs b/src/Application/Tax.Proprietary/Basis/Wealth/SOBasisWealthTaxCalculator.cs new file mode 100644 index 00000000..188858e4 --- /dev/null +++ b/src/Application/Tax.Proprietary/Basis/Wealth/SOBasisWealthTaxCalculator.cs @@ -0,0 +1,98 @@ +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using Application.Tax.Proprietary.Enums; +using Application.Tax.Proprietary.Repositories; +using Domain.Enums; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; +using FluentValidation; +using LanguageExt; +using PensionCoach.Tools.CommonTypes.Tax; + +namespace Application.Tax.Proprietary.Basis.Wealth; + +/// +/// Wealth tax calculator for SO. +/// +/// +public class SOBasisWealthTaxCalculator : IBasisWealthTaxCalculator +{ + private const int TaxTypeId = (int)TaxType.Wealth; + + private readonly IValidator taxPersonValidator; + private readonly ITaxTariffRepository tariffData; + + public SOBasisWealthTaxCalculator( + IValidator taxPersonValidator, + ITaxTariffRepository tariffData) + { + this.taxPersonValidator = taxPersonValidator; + this.tariffData = tariffData; + } + + public Task> CalculateAsync( + int calculationYear, Canton canton, BasisTaxPerson person) + { + var validationResult = taxPersonValidator.Validate(person); + if (!validationResult.IsValid) + { + var errorMessageLine = string.Join(";", validationResult.Errors.Select(x => x.ErrorMessage)); + return Task.FromResult>($"validation failed: {errorMessageLine}"); + } + + var tariffItems = + tariffData.Get(new TaxFilterModel + { + Year = calculationYear, + Canton = canton.ToString(), + }) + .OrderBy(item => item.TaxAmount); + + return Map(person.CivilStatus) + + // get all income level candidate + .Map(typeId => tariffItems + .Where(item => item.TariffType == (int)TariffType.Base) + .Where(item => item.TaxType == TaxTypeId) + .Where(item => item.IncomeLevel <= person.TaxableAmount) + .OrderByDescending(item => item.IncomeLevel) + .DefaultIfEmpty(new TaxTariffModel()) + .First()) + + // calculate result + .Map(tariff => CalculateIncomeTax(person, tariff)) + .Match>( + Some: r => r, + None: () => "Tariff not available") + .AsTask(); + } + + private BasisTaxResult CalculateIncomeTax(BasisTaxPerson person, TaxTariffModel tariff) + { + var referenceTaxableIncome = + person.TaxableAmount - (person.TaxableAmount % tariff.IncomeIncrement); + + var incrementMultiplier = (referenceTaxableIncome - tariff.IncomeLevel) / tariff.IncomeIncrement; + + var baseTaxAmount = (incrementMultiplier * tariff.TaxTariffRatePercent / 1000m) + tariff.TaxAmount; + + return new BasisTaxResult + { + DeterminingFactorTaxableAmount = referenceTaxableIncome, + TaxAmount = baseTaxAmount, + }; + } + + private Option Map(Option status) + { + return status.Match( + Some: s => s switch + { + CivilStatus.Undefined => Option.None, + CivilStatus.Single => TariffType.Base, + CivilStatus.Married => TariffType.Married, + _ => Option.None + }, + None: () => Option.None); + } +} diff --git a/src/Application/Tax.Proprietary/Basis/Wealth/ZHBasisWealthTaxCalculator.cs b/src/Application/Tax.Proprietary/Basis/Wealth/ZHBasisWealthTaxCalculator.cs new file mode 100644 index 00000000..9082a8fa --- /dev/null +++ b/src/Application/Tax.Proprietary/Basis/Wealth/ZHBasisWealthTaxCalculator.cs @@ -0,0 +1,98 @@ +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using Application.Tax.Proprietary.Enums; +using Application.Tax.Proprietary.Repositories; +using Domain.Enums; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; +using FluentValidation; +using LanguageExt; +using PensionCoach.Tools.CommonTypes.Tax; + +namespace Application.Tax.Proprietary.Basis.Wealth; + +/// +/// Wealth tax calculator for ZH. +/// +/// +public class ZHBasisWealthTaxCalculator : IBasisWealthTaxCalculator +{ + private const int TaxTypeId = (int)TaxType.Wealth; + + private readonly IValidator taxPersonValidator; + private readonly ITaxTariffRepository tariffData; + + public ZHBasisWealthTaxCalculator( + IValidator taxPersonValidator, + ITaxTariffRepository tariffData) + { + this.taxPersonValidator = taxPersonValidator; + this.tariffData = tariffData; + } + + public Task> CalculateAsync( + int calculationYear, Canton canton, BasisTaxPerson person) + { + var validationResult = taxPersonValidator.Validate(person); + if (!validationResult.IsValid) + { + var errorMessageLine = string.Join(";", validationResult.Errors.Select(x => x.ErrorMessage)); + return Task.FromResult>($"validation failed: {errorMessageLine}"); + } + + var tariffItems = + tariffData.Get(new TaxFilterModel + { + Year = calculationYear, + Canton = canton.ToString(), + }) + .OrderBy(item => item.TaxAmount); + + return Map(person.CivilStatus) + + // get all income level candidate + .Map(typeId => tariffItems + .Where(item => item.TariffType == (int)typeId) + .Where(item => item.TaxType == TaxTypeId) + .Where(item => item.IncomeLevel <= person.TaxableAmount) + .OrderByDescending(item => item.IncomeLevel) + .DefaultIfEmpty(new TaxTariffModel()) + .First()) + + // calculate result + .Map(tariff => CalculateIncomeTax(person, tariff)) + .Match>( + Some: r => r, + None: () => "Tariff not available") + .AsTask(); + } + + private BasisTaxResult CalculateIncomeTax(BasisTaxPerson person, TaxTariffModel tariff) + { + var referenceTaxableIncome = + person.TaxableAmount - (person.TaxableAmount % tariff.IncomeIncrement); + + var incrementMultiplier = (referenceTaxableIncome - tariff.IncomeLevel) / tariff.IncomeIncrement; + + var baseTaxAmount = (incrementMultiplier * tariff.TaxTariffRatePercent) + tariff.TaxAmount; + + return new BasisTaxResult + { + DeterminingFactorTaxableAmount = referenceTaxableIncome, + TaxAmount = baseTaxAmount, + }; + } + + private Option Map(Option status) + { + return status.Match( + Some: s => s switch + { + CivilStatus.Undefined => Option.None, + CivilStatus.Single => TariffType.Base, + CivilStatus.Married => TariffType.Married, + _ => Option.None + }, + None: () => Option.None); + } +} diff --git a/src/Application/Tax.Proprietary/Contracts/IAggregatedBasisTaxCalculator.cs b/src/Application/Tax.Proprietary/Contracts/IAggregatedBasisTaxCalculator.cs new file mode 100644 index 00000000..bac2fc33 --- /dev/null +++ b/src/Application/Tax.Proprietary/Contracts/IAggregatedBasisTaxCalculator.cs @@ -0,0 +1,12 @@ +using Application.Tax.Proprietary.Models; +using Domain.Enums; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Tax.Proprietary.Contracts; + +public interface IAggregatedBasisTaxCalculator +{ + Task> CalculateAsync( + int calculationYear, Canton canton, TaxPerson person); +} diff --git a/src/Application/Tax.Proprietary/Contracts/IBasisIncomeTaxCalculator.cs b/src/Application/Tax.Proprietary/Contracts/IBasisIncomeTaxCalculator.cs new file mode 100644 index 00000000..9028c883 --- /dev/null +++ b/src/Application/Tax.Proprietary/Contracts/IBasisIncomeTaxCalculator.cs @@ -0,0 +1,12 @@ +using Application.Tax.Proprietary.Abstractions.Models; +using Domain.Enums; +using Domain.Models.Tax.Person; +using LanguageExt; + +namespace Application.Tax.Proprietary.Contracts; + +public interface IBasisIncomeTaxCalculator +{ + Task> CalculateAsync( + int calculationYear, Canton canton, BasisTaxPerson person); +} diff --git a/src/Application/Tax.Proprietary/Contracts/IBasisWealthTaxCalculator.cs b/src/Application/Tax.Proprietary/Contracts/IBasisWealthTaxCalculator.cs new file mode 100644 index 00000000..297a77be --- /dev/null +++ b/src/Application/Tax.Proprietary/Contracts/IBasisWealthTaxCalculator.cs @@ -0,0 +1,5 @@ +namespace Application.Tax.Proprietary.Contracts; + +public interface IBasisWealthTaxCalculator : IBasisIncomeTaxCalculator +{ +} diff --git a/src/Application/Tax.Proprietary/Contracts/ICapitalBenefitTaxCalculator.cs b/src/Application/Tax.Proprietary/Contracts/ICapitalBenefitTaxCalculator.cs new file mode 100644 index 00000000..6fc88df4 --- /dev/null +++ b/src/Application/Tax.Proprietary/Contracts/ICapitalBenefitTaxCalculator.cs @@ -0,0 +1,14 @@ +using Domain.Enums; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Tax.Proprietary.Contracts; + +public interface ICapitalBenefitTaxCalculator +{ + Task> CalculateAsync( + int calculationYear, + int taxId, + Canton canton, + CapitalBenefitTaxPerson person); +} diff --git a/src/Application/Tax.Proprietary/Contracts/IChurchTaxCalculator.cs b/src/Application/Tax.Proprietary/Contracts/IChurchTaxCalculator.cs new file mode 100644 index 00000000..327fb275 --- /dev/null +++ b/src/Application/Tax.Proprietary/Contracts/IChurchTaxCalculator.cs @@ -0,0 +1,35 @@ +using Application.Tax.Proprietary.Models; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; +using LanguageExt; + +namespace Application.Tax.Proprietary.Contracts; + +public interface IChurchTaxCalculator +{ + /// + /// Calculates the church tax for given year and municipality. + /// + /// The calculation year. + /// The municipality. + /// The person. + /// The tax result. + /// + Task> CalculateAsync( + int calculationYear, + int municipality, + ChurchTaxPerson person, + AggregatedBasisTaxResult taxResult); + + /// + /// Calculates the church tax with a given tax rate record. + /// + /// The person. + /// The tax rate entity. + /// The tax result. + /// + Task> CalculateAsync( + ChurchTaxPerson person, + TaxRateEntity taxRateEntity, + AggregatedBasisTaxResult taxResult); +} diff --git a/src/TaxCalculator.Abstractions/IDefaultBasisIncomeTaxCalculator.cs b/src/Application/Tax.Proprietary/Contracts/IDefaultBasisIncomeTaxCalculator.cs similarity index 56% rename from src/TaxCalculator.Abstractions/IDefaultBasisIncomeTaxCalculator.cs rename to src/Application/Tax.Proprietary/Contracts/IDefaultBasisIncomeTaxCalculator.cs index 5d48c4e6..7b1ea38d 100644 --- a/src/TaxCalculator.Abstractions/IDefaultBasisIncomeTaxCalculator.cs +++ b/src/Application/Tax.Proprietary/Contracts/IDefaultBasisIncomeTaxCalculator.cs @@ -1,4 +1,4 @@ -namespace PensionCoach.Tools.TaxCalculator.Abstractions; +namespace Application.Tax.Proprietary.Contracts; public interface IDefaultBasisIncomeTaxCalculator : IBasisIncomeTaxCalculator { } diff --git a/src/Application/Tax.Proprietary/Contracts/IFederalCapitalBenefitTaxCalculator.cs b/src/Application/Tax.Proprietary/Contracts/IFederalCapitalBenefitTaxCalculator.cs new file mode 100644 index 00000000..a6c0a15d --- /dev/null +++ b/src/Application/Tax.Proprietary/Contracts/IFederalCapitalBenefitTaxCalculator.cs @@ -0,0 +1,11 @@ +using Application.Tax.Proprietary.Abstractions.Models; +using Domain.Models.Tax.Person; +using LanguageExt; + +namespace Application.Tax.Proprietary.Contracts; + +public interface IFederalCapitalBenefitTaxCalculator +{ + Task> CalculateAsync( + int calculationYear, FederalTaxPerson person); +} diff --git a/src/Application/Tax.Proprietary/Contracts/IFederalTaxCalculator.cs b/src/Application/Tax.Proprietary/Contracts/IFederalTaxCalculator.cs new file mode 100644 index 00000000..f488cac2 --- /dev/null +++ b/src/Application/Tax.Proprietary/Contracts/IFederalTaxCalculator.cs @@ -0,0 +1,10 @@ +using Application.Tax.Proprietary.Abstractions.Models; +using Domain.Models.Tax.Person; +using LanguageExt; + +namespace Application.Tax.Proprietary.Contracts; + +public interface IFederalTaxCalculator +{ + Task> CalculateAsync(int calculationYear, FederalTaxPerson person); +} diff --git a/src/Application/Tax.Proprietary/Contracts/IIncomeTaxCalculator.cs b/src/Application/Tax.Proprietary/Contracts/IIncomeTaxCalculator.cs new file mode 100644 index 00000000..b969b18c --- /dev/null +++ b/src/Application/Tax.Proprietary/Contracts/IIncomeTaxCalculator.cs @@ -0,0 +1,12 @@ +using Application.Tax.Proprietary.Models; +using Domain.Enums; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Tax.Proprietary.Contracts; + +public interface IIncomeTaxCalculator +{ + Task> CalculateAsync( + int calculationYear, int municipalityId, Canton canton, TaxPerson person); +} diff --git a/src/Application/Tax.Proprietary/Contracts/IPollTaxCalculator.cs b/src/Application/Tax.Proprietary/Contracts/IPollTaxCalculator.cs new file mode 100644 index 00000000..4abeae4c --- /dev/null +++ b/src/Application/Tax.Proprietary/Contracts/IPollTaxCalculator.cs @@ -0,0 +1,22 @@ +using Application.Tax.Proprietary.Models; +using Domain.Enums; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; +using LanguageExt; + +namespace Application.Tax.Proprietary.Contracts; + +public interface IPollTaxCalculator +{ + Task> CalculateAsync( + int calculationYear, + int municipalityId, + Canton canton, + PollTaxPerson person); + + Task> CalculateAsync( + int calculationYear, + Canton canton, + PollTaxPerson person, + TaxRateEntity taxRateEntity); +} diff --git a/src/Application/Tax.Proprietary/Contracts/IStateTaxCalculator.cs b/src/Application/Tax.Proprietary/Contracts/IStateTaxCalculator.cs new file mode 100644 index 00000000..b9a6300e --- /dev/null +++ b/src/Application/Tax.Proprietary/Contracts/IStateTaxCalculator.cs @@ -0,0 +1,22 @@ +using Domain.Enums; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Tax.Proprietary.Contracts; + +public interface IStateTaxCalculator +{ + /// + /// Calculates state (canton) tax asynchronously. + /// + /// The calculation year. + /// The BFS municipality identifier. + /// The canton. + /// The person. + /// + Task> CalculateAsync( + int calculationYear, + int municipalityId, + Canton canton, + TaxPerson person); +} diff --git a/src/Application/Tax.Proprietary/Contracts/IWealthTaxCalculator.cs b/src/Application/Tax.Proprietary/Contracts/IWealthTaxCalculator.cs new file mode 100644 index 00000000..7950e2d5 --- /dev/null +++ b/src/Application/Tax.Proprietary/Contracts/IWealthTaxCalculator.cs @@ -0,0 +1,12 @@ +using Application.Tax.Proprietary.Models; +using Domain.Enums; +using Domain.Models.Tax; +using LanguageExt; + +namespace Application.Tax.Proprietary.Contracts; + +public interface IWealthTaxCalculator +{ + Task> CalculateAsync( + int calculationYear, int municipalityId, Canton canton, TaxPerson person); +} diff --git a/src/Application/Tax.Proprietary/Enums/TariffType.cs b/src/Application/Tax.Proprietary/Enums/TariffType.cs new file mode 100644 index 00000000..6655152c --- /dev/null +++ b/src/Application/Tax.Proprietary/Enums/TariffType.cs @@ -0,0 +1,8 @@ +namespace Application.Tax.Proprietary.Enums; + +public enum TariffType +{ + Undefined = 0, + Base = 1, + Married = 2 +} diff --git a/src/Application/Tax.Proprietary/Models/AggregatedBasisTaxResult.cs b/src/Application/Tax.Proprietary/Models/AggregatedBasisTaxResult.cs new file mode 100644 index 00000000..e1394904 --- /dev/null +++ b/src/Application/Tax.Proprietary/Models/AggregatedBasisTaxResult.cs @@ -0,0 +1,10 @@ +using Application.Tax.Proprietary.Abstractions.Models; + +namespace Application.Tax.Proprietary.Models; + +public class AggregatedBasisTaxResult +{ + public BasisTaxResult IncomeTax { get; set; } + public BasisTaxResult WealthTax { get; set; } + public decimal Total => IncomeTax.TaxAmount + WealthTax.TaxAmount; +} diff --git a/src/TaxCalculator.Abstractions/Models/MarginalTaxCurveResult.cs b/src/Application/Tax.Proprietary/Models/MarginalTaxCurveResult.cs similarity index 56% rename from src/TaxCalculator.Abstractions/Models/MarginalTaxCurveResult.cs rename to src/Application/Tax.Proprietary/Models/MarginalTaxCurveResult.cs index b1c80761..467b8cf1 100644 --- a/src/TaxCalculator.Abstractions/Models/MarginalTaxCurveResult.cs +++ b/src/Application/Tax.Proprietary/Models/MarginalTaxCurveResult.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; -using PensionCoach.Tools.CommonTypes.Tax; +using Domain.Models.Tax; -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models; +namespace Application.Tax.Proprietary.Models; public class MarginalTaxCurveResult { diff --git a/src/Application/Tax.Proprietary/Models/PollTaxResult.cs b/src/Application/Tax.Proprietary/Models/PollTaxResult.cs new file mode 100644 index 00000000..1eb53a52 --- /dev/null +++ b/src/Application/Tax.Proprietary/Models/PollTaxResult.cs @@ -0,0 +1,10 @@ +using LanguageExt; + +namespace Application.Tax.Proprietary.Models; + +public class PollTaxResult +{ + public Option CantonTaxAmount { get; set; } + + public Option MunicipalityTaxAmount { get; set; } +} diff --git a/src/Application/Tax.Proprietary/Models/SingleTaxResult.cs b/src/Application/Tax.Proprietary/Models/SingleTaxResult.cs new file mode 100644 index 00000000..edd92895 --- /dev/null +++ b/src/Application/Tax.Proprietary/Models/SingleTaxResult.cs @@ -0,0 +1,13 @@ +using Application.Tax.Proprietary.Abstractions.Models; + +namespace Application.Tax.Proprietary.Models; + +public class SingleTaxResult +{ + public BasisTaxResult BasisTaxAmount { get; set; } + public decimal CantonRate { get; set; } + public decimal MunicipalityRate { get; set; } + public decimal MunicipalityTaxAmount => MunicipalityRate / 100M * BasisTaxAmount.TaxAmount; + public decimal CantonTaxAmount => CantonRate / 100M * BasisTaxAmount.TaxAmount; + public decimal TotalTaxAmount => MunicipalityTaxAmount + CantonTaxAmount; +} diff --git a/src/TaxCalculator/Proprietary/ProprietaryAggregatedBasisTaxCalculator.cs b/src/Application/Tax.Proprietary/ProprietaryAggregatedBasisTaxCalculator.cs similarity index 70% rename from src/TaxCalculator/Proprietary/ProprietaryAggregatedBasisTaxCalculator.cs rename to src/Application/Tax.Proprietary/ProprietaryAggregatedBasisTaxCalculator.cs index 0f0bac02..fb8e095a 100644 --- a/src/TaxCalculator/Proprietary/ProprietaryAggregatedBasisTaxCalculator.cs +++ b/src/Application/Tax.Proprietary/ProprietaryAggregatedBasisTaxCalculator.cs @@ -1,14 +1,12 @@ -using System; -using System.Threading.Tasks; +using Application.Tax.Proprietary.Contracts; +using Application.Tax.Proprietary.Models; using AutoMapper; +using Domain.Enums; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -namespace PensionCoach.Tools.TaxCalculator.Proprietary; +namespace Application.Tax.Proprietary; public class ProprietaryAggregatedBasisTaxCalculator : IAggregatedBasisTaxCalculator { @@ -43,11 +41,11 @@ public async Task> CalculateAsync( await Task.WhenAll(incomeTaxResultTask, wealthTaxResultTask); return from income in incomeTaxResultTask.Result - from wealth in wealthTaxResultTask.Result - select new AggregatedBasisTaxResult - { - IncomeTax = income, - WealthTax = wealth, - }; + from wealth in wealthTaxResultTask.Result + select new AggregatedBasisTaxResult + { + IncomeTax = income, + WealthTax = wealth, + }; } } diff --git a/src/TaxCalculator/Proprietary/ProprietaryChurchTaxCalculator.cs b/src/Application/Tax.Proprietary/ProprietaryChurchTaxCalculator.cs similarity index 75% rename from src/TaxCalculator/Proprietary/ProprietaryChurchTaxCalculator.cs rename to src/Application/Tax.Proprietary/ProprietaryChurchTaxCalculator.cs index 8ef191b2..062d2363 100644 --- a/src/TaxCalculator/Proprietary/ProprietaryChurchTaxCalculator.cs +++ b/src/Application/Tax.Proprietary/ProprietaryChurchTaxCalculator.cs @@ -1,32 +1,29 @@ -using System; -using System.Linq; -using System.Threading.Tasks; +using Application.Tax.Proprietary.Contracts; +using Application.Tax.Proprietary.Models; +using Application.Tax.Proprietary.Repositories; +using Domain.Enums; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; using FluentValidation; using LanguageExt; -using Microsoft.EntityFrameworkCore; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -using Tax.Data; -using Tax.Data.Abstractions.Models; -namespace PensionCoach.Tools.TaxCalculator.Proprietary; +namespace Application.Tax.Proprietary; public class ProprietaryChurchTaxCalculator : IChurchTaxCalculator { private readonly IValidator churchTaxPersonValidator; private readonly IValidator taxResultValidator; - private readonly Func taxRateContext; + private readonly IStateTaxRateRepository stateTaxRateRepository; public ProprietaryChurchTaxCalculator( IValidator churchTaxPersonValidator, IValidator basisTaxResultValidator, - Func taxRateContext) + IStateTaxRateRepository stateTaxRateRepository) { + this.stateTaxRateRepository = stateTaxRateRepository; this.churchTaxPersonValidator = churchTaxPersonValidator; taxResultValidator = basisTaxResultValidator; - this.taxRateContext = taxRateContext; + this.stateTaxRateRepository = stateTaxRateRepository; } /// @@ -36,10 +33,8 @@ public Task> CalculateAsync( ChurchTaxPerson person, AggregatedBasisTaxResult taxResult) { - using var ctxt = taxRateContext(); - var taxRateEntity = ctxt.Rates.AsNoTracking() - .FirstOrDefault(item => item.Year == calculationYear - && item.BfsId == municipalityId); + var taxRateEntity = stateTaxRateRepository.TaxRates(calculationYear, municipalityId); + if (taxRateEntity == null) { Either msg = @@ -137,15 +132,15 @@ private decimal DetermineSplitFactor( { var splitFactor = (civilStatus, religiousGroupType, personReligiousGroupPartner) switch - { - (CivilStatus.Undefined, _, _) => 1.0M, - (CivilStatus.Single, ReligiousGroupType.Other, _) => 0.0M, - (CivilStatus.Single, _, _) => 1.0M, - (CivilStatus.Married, ReligiousGroupType.Other, ReligiousGroupType.Other) - => 0.0M, - (CivilStatus.Married, ReligiousGroupType.Other, _) => 1.0M, - _ => 0.5M - }; + { + (CivilStatus.Undefined, _, _) => 1.0M, + (CivilStatus.Single, ReligiousGroupType.Other, _) => 0.0M, + (CivilStatus.Single, _, _) => 1.0M, + (CivilStatus.Married, ReligiousGroupType.Other, ReligiousGroupType.Other) + => 0.0M, + (CivilStatus.Married, ReligiousGroupType.Other, _) => 1.0M, + _ => 0.5M + }; return splitFactor; } diff --git a/src/TaxCalculator/Proprietary/ProprietaryFederalCapitalBenefitTaxCalculator.cs b/src/Application/Tax.Proprietary/ProprietaryFederalCapitalBenefitTaxCalculator.cs similarity index 79% rename from src/TaxCalculator/Proprietary/ProprietaryFederalCapitalBenefitTaxCalculator.cs rename to src/Application/Tax.Proprietary/ProprietaryFederalCapitalBenefitTaxCalculator.cs index 4d125dd2..3d6023cb 100644 --- a/src/TaxCalculator/Proprietary/ProprietaryFederalCapitalBenefitTaxCalculator.cs +++ b/src/Application/Tax.Proprietary/ProprietaryFederalCapitalBenefitTaxCalculator.cs @@ -1,11 +1,10 @@ -using System.Threading.Tasks; +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; using AutoMapper; +using Domain.Models.Tax.Person; using LanguageExt; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -namespace PensionCoach.Tools.TaxCalculator.Proprietary; +namespace Application.Tax.Proprietary; public class ProprietaryFederalCapitalBenefitTaxCalculator : IFederalCapitalBenefitTaxCalculator { diff --git a/src/Application/Tax.Proprietary/ProprietaryFederalTaxCalculator.cs b/src/Application/Tax.Proprietary/ProprietaryFederalTaxCalculator.cs new file mode 100644 index 00000000..a3658e83 --- /dev/null +++ b/src/Application/Tax.Proprietary/ProprietaryFederalTaxCalculator.cs @@ -0,0 +1,83 @@ +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using Application.Tax.Proprietary.Enums; +using Application.Tax.Proprietary.Repositories; +using Domain.Enums; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; +using FluentValidation; +using FluentValidation.Results; +using LanguageExt; + +namespace Application.Tax.Proprietary; + +public class ProprietaryFederalTaxCalculator : IFederalTaxCalculator +{ + private readonly IValidator taxPersonValidator; + private readonly IFederalTaxRateRepository federalTaxRateRepository; + + public ProprietaryFederalTaxCalculator( + IValidator taxPersonValidator, + IFederalTaxRateRepository federalTaxRateRepository) + { + this.taxPersonValidator = taxPersonValidator; + this.federalTaxRateRepository = federalTaxRateRepository; + } + + /// + public Task> CalculateAsync(int calculationYear, FederalTaxPerson person) + { + Option validationResult = taxPersonValidator.Validate(person); + + return validationResult + .Where(r => !r.IsValid) + .Map>(r => + { + var errorMessageLine = string.Join(";", r.Errors.Select(x => x.ErrorMessage)); + return $"validation failed: {errorMessageLine}"; + }) + .IfNone(true) + .Bind(_ => Map(person.CivilStatus)) + + // get all income level candidate + .Map(typeId => federalTaxRateRepository + .TaxRates(calculationYear, typeId) + .ToList() + .Where(item => item.IncomeLevel <= person.TaxableAmount) + .OrderByDescending(item => item.IncomeLevel) + .DefaultIfEmpty(new FederalTaxTariffModel()) + .First()) + + // calculate result + .Map(tariff => CalculateTax(person, tariff)) + .AsTask(); + } + + private BasisTaxResult CalculateTax(FederalTaxPerson person, FederalTaxTariffModel tariff) + { + var referenceTaxableIncome = + person.TaxableAmount - person.TaxableAmount % tariff.IncomeIncrement; + + var incrementMultiplier = (referenceTaxableIncome - tariff.IncomeLevel) / tariff.IncomeIncrement; + + var baseTaxAmount = incrementMultiplier * tariff.TaxIncrement + tariff.TaxAmount; + + return new BasisTaxResult + { + DeterminingFactorTaxableAmount = referenceTaxableIncome, + TaxAmount = baseTaxAmount, + }; + } + + private Either Map(Option status) + { + return status.Match>( + Some: s => s switch + { + CivilStatus.Single => TariffType.Base, + CivilStatus.Married => TariffType.Married, + _ => TariffType.Undefined + }, + None: () => "Civil status unknown"); + } +} diff --git a/src/Application/Tax.Proprietary/ProprietaryFullCapitalBenefitTaxCalculator.cs b/src/Application/Tax.Proprietary/ProprietaryFullCapitalBenefitTaxCalculator.cs new file mode 100644 index 00000000..973f4589 --- /dev/null +++ b/src/Application/Tax.Proprietary/ProprietaryFullCapitalBenefitTaxCalculator.cs @@ -0,0 +1,72 @@ +using System.Text; +using Application.Tax.Contracts; +using Application.Tax.Proprietary.Contracts; +using AutoMapper; +using Domain.Enums; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; +using LanguageExt; + +namespace Application.Tax.Proprietary; + +public class ProprietaryFullCapitalBenefitTaxCalculator : IFullCapitalBenefitTaxCalculator +{ + private readonly Func capitalBenefitCalculatorFunc; + private readonly IFederalCapitalBenefitTaxCalculator federalCalculator; + private readonly IMapper mapper; + + public ProprietaryFullCapitalBenefitTaxCalculator( + Func capitalBenefitCalculatorFunc, + IFederalCapitalBenefitTaxCalculator federalCalculator, + IMapper mapper) + { + this.capitalBenefitCalculatorFunc = capitalBenefitCalculatorFunc; + this.federalCalculator = federalCalculator; + this.mapper = mapper; + } + + /// + public async Task> CalculateAsync( + int calculationYear, + MunicipalityModel municipality, + CapitalBenefitTaxPerson capitalBenefitTaxPerson, + bool withMaxAvailableCalculationYear) + { + var maxCalculationYear = withMaxAvailableCalculationYear + ? Math.Min(calculationYear, 2019) + : calculationYear; + + var capitalBenefitTaxResultTask = + capitalBenefitCalculatorFunc(municipality.Canton) + .CalculateAsync(maxCalculationYear, municipality.BfsNumber, municipality.Canton, capitalBenefitTaxPerson); + + var federalTaxPerson = mapper.Map(capitalBenefitTaxPerson); + + var federalTaxResultTask = federalCalculator.CalculateAsync(maxCalculationYear, federalTaxPerson); + + await Task.WhenAll(capitalBenefitTaxResultTask, federalTaxResultTask); + + var sb = new StringBuilder(); + + var stateTaxResult = await capitalBenefitTaxResultTask; + var federalTaxResult = await federalTaxResultTask; + + stateTaxResult.MapLeft(r => sb.AppendLine(r)); + federalTaxResult.MapLeft(r => sb.AppendLine(r)); + + var fullResult = + from s in stateTaxResult.ToOption() + from f in federalTaxResult.ToOption() + select new FullCapitalBenefitTaxResult + { + StateResult = s, + FederalResult = f, + }; + + return fullResult + .Match>( + Some: r => r, + None: () => sb.ToString()); + } +} diff --git a/src/Application/Tax.Proprietary/ProprietaryFullTaxCalculator.cs b/src/Application/Tax.Proprietary/ProprietaryFullTaxCalculator.cs new file mode 100644 index 00000000..e5605b3c --- /dev/null +++ b/src/Application/Tax.Proprietary/ProprietaryFullTaxCalculator.cs @@ -0,0 +1,61 @@ +using Application.Tax.Contracts; +using Application.Tax.Proprietary.Contracts; +using AutoMapper; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; +using LanguageExt; + +namespace Application.Tax.Proprietary; + +public class ProprietaryFullTaxCalculator : IFullWealthAndIncomeTaxCalculator +{ + private readonly IMapper mapper; + private readonly IStateTaxCalculator stateTaxCalculator; + private readonly IFederalTaxCalculator federalTaxCalculator; + + public ProprietaryFullTaxCalculator( + IMapper mapper, + IStateTaxCalculator stateTaxCalculator, + IFederalTaxCalculator federalTaxCalculator) + { + this.mapper = mapper; + this.stateTaxCalculator = stateTaxCalculator; + this.federalTaxCalculator = federalTaxCalculator; + } + + public async Task> CalculateAsync( + int calculationYear, + MunicipalityModel municipality, + TaxPerson person, + bool withMaxAvailableCalculationYear) + { + var maxCalculationYear = withMaxAvailableCalculationYear + ? Math.Min(calculationYear, 2019) + : calculationYear; + + var federalTaxPerson = mapper.Map(person); + + var stateTaxResultTask = stateTaxCalculator.CalculateAsync(maxCalculationYear, municipality.BfsNumber, municipality.Canton, person); + var federalTaxResultTask = federalTaxCalculator.CalculateAsync(maxCalculationYear, federalTaxPerson); + + await Task.WhenAll(stateTaxResultTask, federalTaxResultTask); + + var stateTaxResult = await stateTaxResultTask; + var federalTaxResult = await federalTaxResultTask; + + var fullResult = new FullTaxResult(); + + return stateTaxResult + .Bind(r => + { + fullResult.StateTaxResult = r; + return federalTaxResult; + }) + .Map(r => + { + fullResult.FederalTaxResult = r; + return fullResult; + }); + } +} diff --git a/src/TaxCalculator/Proprietary/ProprietaryIncomeTaxCalculator.cs b/src/Application/Tax.Proprietary/ProprietaryIncomeTaxCalculator.cs similarity index 68% rename from src/TaxCalculator/Proprietary/ProprietaryIncomeTaxCalculator.cs rename to src/Application/Tax.Proprietary/ProprietaryIncomeTaxCalculator.cs index 49390252..fc59b593 100644 --- a/src/TaxCalculator/Proprietary/ProprietaryIncomeTaxCalculator.cs +++ b/src/Application/Tax.Proprietary/ProprietaryIncomeTaxCalculator.cs @@ -1,34 +1,31 @@ -using System; -using System.Linq; -using System.Threading.Tasks; +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using Application.Tax.Proprietary.Models; +using Application.Tax.Proprietary.Repositories; using AutoMapper; +using Domain.Enums; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; using FluentValidation; using LanguageExt; -using Microsoft.EntityFrameworkCore; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -using Tax.Data; -namespace PensionCoach.Tools.TaxCalculator.Proprietary; +namespace Application.Tax.Proprietary; public class ProprietaryIncomeTaxCalculator : IIncomeTaxCalculator { private readonly IValidator taxPersonValidator; - private readonly Func rateDbContextFunc; + private readonly IStateTaxRateRepository stateTaxRateRepository; private readonly Func basisIncomeTaxCalculatorFunc; private readonly IMapper mapper; public ProprietaryIncomeTaxCalculator( IValidator taxPersonValidator, - Func rateDbContextFunc, + IStateTaxRateRepository stateTaxRateRepository, Func basisIncomeTaxCalculatorFunc, IMapper mapper) { this.taxPersonValidator = taxPersonValidator; - this.rateDbContextFunc = rateDbContextFunc; + this.stateTaxRateRepository = stateTaxRateRepository; this.basisIncomeTaxCalculatorFunc = basisIncomeTaxCalculatorFunc; this.mapper = mapper; } @@ -64,10 +61,7 @@ private SingleTaxResult CalculateIncomeTax( int municipalityId, BasisTaxResult basisTaxResult) { - using var dbContext = rateDbContextFunc(); - var taxRate = dbContext.Rates.AsNoTracking() - .Single(item => item.Year == calculationYear - && item.BfsId == municipalityId); + var taxRate = stateTaxRateRepository.TaxRates(calculationYear, municipalityId); return new SingleTaxResult { diff --git a/src/TaxCalculator/Proprietary/ProprietaryPollTaxCalculator.cs b/src/Application/Tax.Proprietary/ProprietaryPollTaxCalculator.cs similarity index 77% rename from src/TaxCalculator/Proprietary/ProprietaryPollTaxCalculator.cs rename to src/Application/Tax.Proprietary/ProprietaryPollTaxCalculator.cs index 02be9c38..87b2f89e 100644 --- a/src/TaxCalculator/Proprietary/ProprietaryPollTaxCalculator.cs +++ b/src/Application/Tax.Proprietary/ProprietaryPollTaxCalculator.cs @@ -1,18 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using Application.Tax.Proprietary.Contracts; +using Application.Tax.Proprietary.Models; +using Application.Tax.Proprietary.Repositories; +using Domain.Enums; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; using FluentValidation; using LanguageExt; -using Microsoft.EntityFrameworkCore; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -using Tax.Data; -using Tax.Data.Abstractions.Models; -namespace PensionCoach.Tools.TaxCalculator.Proprietary; +namespace Application.Tax.Proprietary; public class ProprietaryPollTaxCalculator : IPollTaxCalculator { @@ -24,13 +19,14 @@ public class ProprietaryPollTaxCalculator : IPollTaxCalculator }; private readonly IValidator personValidator; - private readonly Func dbContextFunc; + private readonly IStateTaxRateRepository stateTaxRateRepository; public ProprietaryPollTaxCalculator( - IValidator personValidator, Func dbContextFunc) + IValidator personValidator, + IStateTaxRateRepository stateTaxRateRepository) { this.personValidator = personValidator; - this.dbContextFunc = dbContextFunc; + this.stateTaxRateRepository = stateTaxRateRepository; } public Task> CalculateAsync( @@ -51,10 +47,7 @@ public Task> CalculateAsync( $"validation failed: {errorMessageLine}"); } - using var dbContext = dbContextFunc(); - Option taxRate = dbContext.Rates.AsNoTracking() - .FirstOrDefault(item => item.BfsId == municipalityId - && item.Year == calculationYear); + Option taxRate = stateTaxRateRepository.TaxRates(calculationYear, municipalityId); return (from nbrOfPolls in GetNumberOfPolls(person.CivilStatus) from rate in taxRate diff --git a/src/TaxCalculator/Proprietary/ProprietaryStateTaxCalculator.cs b/src/Application/Tax.Proprietary/ProprietaryStateTaxCalculator.cs similarity index 70% rename from src/TaxCalculator/Proprietary/ProprietaryStateTaxCalculator.cs rename to src/Application/Tax.Proprietary/ProprietaryStateTaxCalculator.cs index 47829d07..f1556dfb 100644 --- a/src/TaxCalculator/Proprietary/ProprietaryStateTaxCalculator.cs +++ b/src/Application/Tax.Proprietary/ProprietaryStateTaxCalculator.cs @@ -1,18 +1,12 @@ -using System; -using System.Linq; -using System.Threading.Tasks; +using Application.Tax.Proprietary.Contracts; +using Application.Tax.Proprietary.Repositories; using AutoMapper; +using Domain.Enums; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; using LanguageExt; -using Microsoft.EntityFrameworkCore; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -using Tax.Data; -using Tax.Data.Abstractions.Models; -namespace PensionCoach.Tools.TaxCalculator.Proprietary; +namespace Application.Tax.Proprietary; public class ProprietaryStateTaxCalculator : IStateTaxCalculator { @@ -20,20 +14,20 @@ public class ProprietaryStateTaxCalculator : IStateTaxCalculator private readonly IChurchTaxCalculator churchTaxCalculator; private readonly IPollTaxCalculator pollTaxCalculator; private readonly IMapper mapper; - private readonly Func dbContext; + private readonly IStateTaxRateRepository stateTaxRateRepository; public ProprietaryStateTaxCalculator( IAggregatedBasisTaxCalculator basisTaxCalculator, IChurchTaxCalculator churchTaxCalculator, IPollTaxCalculator pollTaxCalculator, IMapper mapper, - Func dbContext) + IStateTaxRateRepository stateTaxRateRepository) { + this.stateTaxRateRepository = stateTaxRateRepository; this.basisTaxCalculator = basisTaxCalculator; this.churchTaxCalculator = churchTaxCalculator; this.pollTaxCalculator = pollTaxCalculator; this.mapper = mapper; - this.dbContext = dbContext; } public async Task> CalculateAsync( @@ -42,7 +36,6 @@ public async Task> CalculateAsync( Canton canton, TaxPerson person) { - await using var ctxt = dbContext(); var aggregatedTaxResultTask = basisTaxCalculator.CalculateAsync(calculationYear, canton, person); @@ -62,9 +55,7 @@ public async Task> CalculateAsync( var pollTaxResult = await pollTaxResultTask; - Option taxRate = ctxt.Rates.AsNoTracking() - .FirstOrDefault(item => item.BfsId == municipalityId - && item.Year == calculationYear); + Option taxRate = stateTaxRateRepository.TaxRates(calculationYear, municipalityId); var stateTaxResult = new StateTaxResult(); @@ -85,9 +76,10 @@ public async Task> CalculateAsync( .Bind(r => { stateTaxResult.PollTaxAmount = - from cTax in r.CantonTaxAmount - from mTax in r.MunicipalityTaxAmount - select cTax + mTax; + (from cTax in r.CantonTaxAmount + from mTax in r.MunicipalityTaxAmount + select cTax + mTax) + .IfNone(0); return taxRate.ToEither("No tax rate found"); }) diff --git a/src/TaxCalculator/Proprietary/ProprietarySupportedTaxYears.cs b/src/Application/Tax.Proprietary/ProprietarySupportedTaxYears.cs similarity index 65% rename from src/TaxCalculator/Proprietary/ProprietarySupportedTaxYears.cs rename to src/Application/Tax.Proprietary/ProprietarySupportedTaxYears.cs index d6327813..fcf54b0e 100644 --- a/src/TaxCalculator/Proprietary/ProprietarySupportedTaxYears.cs +++ b/src/Application/Tax.Proprietary/ProprietarySupportedTaxYears.cs @@ -1,7 +1,6 @@ -using System.Linq; -using PensionCoach.Tools.TaxCalculator.Abstractions; +using Application.Features.FullTaxCalculation; -namespace PensionCoach.Tools.TaxCalculator.Proprietary; +namespace Application.Tax.Proprietary; public class ProprietarySupportedTaxYears : ITaxSupportedYearProvider { diff --git a/src/TaxCalculator/Proprietary/ProprietaryWealthTaxCalculator.cs b/src/Application/Tax.Proprietary/ProprietaryWealthTaxCalculator.cs similarity index 68% rename from src/TaxCalculator/Proprietary/ProprietaryWealthTaxCalculator.cs rename to src/Application/Tax.Proprietary/ProprietaryWealthTaxCalculator.cs index 7fe2963d..f5b80ee2 100644 --- a/src/TaxCalculator/Proprietary/ProprietaryWealthTaxCalculator.cs +++ b/src/Application/Tax.Proprietary/ProprietaryWealthTaxCalculator.cs @@ -1,34 +1,31 @@ -using System; -using System.Linq; -using System.Threading.Tasks; +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using Application.Tax.Proprietary.Models; +using Application.Tax.Proprietary.Repositories; using AutoMapper; +using Domain.Enums; +using Domain.Models.Tax; +using Domain.Models.Tax.Person; using FluentValidation; using LanguageExt; -using Microsoft.EntityFrameworkCore; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -using Tax.Data; -namespace PensionCoach.Tools.TaxCalculator.Proprietary; +namespace Application.Tax.Proprietary; public class ProprietaryWealthTaxCalculator : IWealthTaxCalculator { private readonly IValidator taxPersonValidator; - private readonly Func rateDbContextFunc; + private readonly IStateTaxRateRepository stateTaxRateRepository; private readonly Func basisWealthTaxCalculatorFunc; private readonly IMapper mapper; public ProprietaryWealthTaxCalculator( IValidator taxPersonValidator, - Func rateDbContextFunc, + IStateTaxRateRepository stateTaxRateRepository, Func basisWealthTaxCalculatorFunc, IMapper mapper) { this.taxPersonValidator = taxPersonValidator; - this.rateDbContextFunc = rateDbContextFunc; + this.stateTaxRateRepository = stateTaxRateRepository; this.basisWealthTaxCalculatorFunc = basisWealthTaxCalculatorFunc; this.mapper = mapper; } @@ -60,10 +57,7 @@ await basisWealthTaxCalculatorFunc(canton).CalculateAsync( private SingleTaxResult CalculateTax( int calculationYear, int municipalityId, BasisTaxResult basisTaxResult) { - using var dbContext = rateDbContextFunc(); - var taxRate = dbContext.Rates.AsNoTracking() - .Single(item => item.BfsId == municipalityId - && item.Year == calculationYear); + var taxRate = stateTaxRateRepository.TaxRates(calculationYear, municipalityId); return new SingleTaxResult { diff --git a/src/Application/Tax.Proprietary/Repositories/IFederalTaxRateRepository.cs b/src/Application/Tax.Proprietary/Repositories/IFederalTaxRateRepository.cs new file mode 100644 index 00000000..da9c4761 --- /dev/null +++ b/src/Application/Tax.Proprietary/Repositories/IFederalTaxRateRepository.cs @@ -0,0 +1,9 @@ +using Application.Tax.Proprietary.Enums; +using Domain.Models.Tax; + +namespace Application.Tax.Proprietary.Repositories; + +public interface IFederalTaxRateRepository +{ + IEnumerable TaxRates(int calculationYear, TariffType typeId); +} diff --git a/src/Application/Tax.Proprietary/Repositories/IStateTaxRateRepository.cs b/src/Application/Tax.Proprietary/Repositories/IStateTaxRateRepository.cs new file mode 100644 index 00000000..a15bfa9b --- /dev/null +++ b/src/Application/Tax.Proprietary/Repositories/IStateTaxRateRepository.cs @@ -0,0 +1,10 @@ +using Domain.Models.Tax; + +namespace Application.Tax.Proprietary.Repositories; + +public interface IStateTaxRateRepository +{ + TaxRateEntity TaxRates(int calculationYear, int municipalityId); + + IEnumerable TaxRates(); +} diff --git a/src/Application/Tax.Proprietary/Repositories/ITaxTariffRepository.cs b/src/Application/Tax.Proprietary/Repositories/ITaxTariffRepository.cs new file mode 100644 index 00000000..eb9d06c9 --- /dev/null +++ b/src/Application/Tax.Proprietary/Repositories/ITaxTariffRepository.cs @@ -0,0 +1,8 @@ +using Domain.Models.Tax; + +namespace Application.Tax.Proprietary.Repositories; + +public interface ITaxTariffRepository +{ + IReadOnlyCollection Get(TaxFilterModel filter); +} diff --git a/src/Application/Validators/AggregatedTaxResultValidator.cs b/src/Application/Validators/AggregatedTaxResultValidator.cs new file mode 100644 index 00000000..ae5fb79d --- /dev/null +++ b/src/Application/Validators/AggregatedTaxResultValidator.cs @@ -0,0 +1,14 @@ +using Application.Tax.Proprietary.Models; +using FluentValidation; + +namespace Application.Validators; + +/// +public class AggregatedTaxResultValidator : AbstractValidator +{ + public AggregatedTaxResultValidator() + { + RuleFor(x => x.IncomeTax).NotNull(); + RuleFor(x => x.WealthTax).NotNull(); + } +} diff --git a/src/Application/Validators/BasisTaxPersonValidator.cs b/src/Application/Validators/BasisTaxPersonValidator.cs new file mode 100644 index 00000000..ad8bde8a --- /dev/null +++ b/src/Application/Validators/BasisTaxPersonValidator.cs @@ -0,0 +1,17 @@ +using Domain.Models.Tax.Person; +using FluentValidation; + +namespace Application.Validators; + +public class BasisTaxPersonValidator : AbstractValidator +{ + private const string ValueMustNotBeNegative = "Value must not be negative"; + + public BasisTaxPersonValidator() + { + Include(new TaxPersonBasicValidator()); + RuleFor(p => p.TaxableAmount) + .GreaterThanOrEqualTo(decimal.Zero) + .WithMessage(ValueMustNotBeNegative); + } +} diff --git a/src/Application/Validators/BvgPersonValidator.cs b/src/Application/Validators/BvgPersonValidator.cs new file mode 100644 index 00000000..3df09061 --- /dev/null +++ b/src/Application/Validators/BvgPersonValidator.cs @@ -0,0 +1,14 @@ +using Domain.Enums; +using Domain.Models.Bvg; +using FluentValidation; + +namespace Application.Validators; + +public class BvgPersonValidator : AbstractValidator +{ + public BvgPersonValidator() + { + RuleFor(x => x.Gender).Must(x => x != Gender.Undefined); + RuleFor(x => x.PartTimeDegree).Must(x => x > decimal.Zero); + } +} diff --git a/src/Application/Validators/CantonValidator.cs b/src/Application/Validators/CantonValidator.cs new file mode 100644 index 00000000..58ed5edd --- /dev/null +++ b/src/Application/Validators/CantonValidator.cs @@ -0,0 +1,16 @@ +using Domain.Enums; +using FluentValidation; + +namespace Application.Validators; + +public class CantonValidator : AbstractValidator +{ + private static readonly Canton[] SupportedCantons = { Canton.ZH, Canton.SG, Canton.SO }; + + public CantonValidator() + { + RuleFor(canton => canton) + .Must(c => SupportedCantons.Contains(c)) + .WithMessage(c => $"Canton {c} is not yet supported"); + } +} diff --git a/src/Application/Validators/CapitalBenefitsTaxPersonValidator.cs b/src/Application/Validators/CapitalBenefitsTaxPersonValidator.cs new file mode 100644 index 00000000..2c5d07b8 --- /dev/null +++ b/src/Application/Validators/CapitalBenefitsTaxPersonValidator.cs @@ -0,0 +1,18 @@ +using Domain.Models.Tax; +using FluentValidation; + +namespace Application.Validators; + +public class CapitalBenefitsTaxPersonValidator : AbstractValidator +{ + private const string ValueMustNotBeNegative = "Value must not be negative"; + + public CapitalBenefitsTaxPersonValidator() + { + Include(new TaxPersonBasicValidator()); + + RuleFor(p => p.TaxableCapitalBenefits) + .GreaterThanOrEqualTo(decimal.Zero) + .WithMessage(ValueMustNotBeNegative); + } +} diff --git a/src/Application/Validators/ChurchTaxPersonValidator.cs b/src/Application/Validators/ChurchTaxPersonValidator.cs new file mode 100644 index 00000000..22de28ca --- /dev/null +++ b/src/Application/Validators/ChurchTaxPersonValidator.cs @@ -0,0 +1,13 @@ +using Domain.Models.Tax.Person; +using FluentValidation; + +namespace Application.Validators; + +/// +public class ChurchTaxPersonValidator : AbstractValidator +{ + public ChurchTaxPersonValidator() + { + Include(new TaxPersonBasicValidator()); + } +} diff --git a/src/Application/Validators/FederalTaxPersonValidator.cs b/src/Application/Validators/FederalTaxPersonValidator.cs new file mode 100644 index 00000000..e70200e4 --- /dev/null +++ b/src/Application/Validators/FederalTaxPersonValidator.cs @@ -0,0 +1,18 @@ +using Domain.Models.Tax.Person; +using FluentValidation; + +namespace Application.Validators; + +public class FederalTaxPersonValidator : AbstractValidator +{ + private const string ValueMustNotBeNegative = "Value must not be negative"; + + public FederalTaxPersonValidator() + { + Include(new TaxPersonBasicValidator()); + + RuleFor(p => p.TaxableAmount) + .GreaterThanOrEqualTo(decimal.Zero) + .WithMessage(ValueMustNotBeNegative); + } +} diff --git a/src/Application/Validators/PollTaxPersonValidator.cs b/src/Application/Validators/PollTaxPersonValidator.cs new file mode 100644 index 00000000..5a2aaed9 --- /dev/null +++ b/src/Application/Validators/PollTaxPersonValidator.cs @@ -0,0 +1,12 @@ +using Domain.Models.Tax.Person; +using FluentValidation; + +namespace Application.Validators; + +public class PollTaxPersonValidator : AbstractValidator +{ + public PollTaxPersonValidator() + { + Include(new TaxPersonBasicValidator()); + } +} diff --git a/src/Application/Validators/TaxPersonBasicValidator.cs b/src/Application/Validators/TaxPersonBasicValidator.cs new file mode 100644 index 00000000..c8efff5c --- /dev/null +++ b/src/Application/Validators/TaxPersonBasicValidator.cs @@ -0,0 +1,17 @@ +using Domain.Enums; +using Domain.Models.Tax; +using FluentValidation; + +namespace Application.Validators; + +public class TaxPersonBasicValidator : AbstractValidator +{ + public TaxPersonBasicValidator() + { + RuleFor(x => x.Name).NotNull().NotEmpty(); + + RuleFor(x => x.NumberOfChildren).GreaterThanOrEqualTo(0); + + RuleFor(x => x.CivilStatus).Must(x => x != CivilStatus.Undefined); + } +} diff --git a/src/Application/Validators/TaxPersonValidator.cs b/src/Application/Validators/TaxPersonValidator.cs new file mode 100644 index 00000000..4bbc53fe --- /dev/null +++ b/src/Application/Validators/TaxPersonValidator.cs @@ -0,0 +1,24 @@ +using Domain.Models.Tax; +using FluentValidation; + +namespace Application.Validators; + +public class TaxPersonValidator : AbstractValidator +{ + private const string ValueMustNotBeNegative = "Value must not be negative"; + + public TaxPersonValidator() + { + Include(new TaxPersonBasicValidator()); + + RuleFor(p => p.TaxableIncome) + .GreaterThanOrEqualTo(decimal.Zero) + .WithMessage(ValueMustNotBeNegative); + RuleFor(p => p.TaxableWealth) + .GreaterThanOrEqualTo(decimal.Zero) + .WithMessage(ValueMustNotBeNegative); + RuleFor(p => p.TaxableFederalIncome) + .GreaterThanOrEqualTo(decimal.Zero) + .WithMessage(ValueMustNotBeNegative); + } +} diff --git a/src/AspireHost/AspireHost.csproj b/src/AspireHost/AspireHost.csproj new file mode 100644 index 00000000..d17843ef --- /dev/null +++ b/src/AspireHost/AspireHost.csproj @@ -0,0 +1,28 @@ + + + + Exe + net8.0 + enable + enable + true + + + + + + + + + + + + + + + true + Never + + + + diff --git a/src/AspireHost/Program.cs b/src/AspireHost/Program.cs new file mode 100644 index 00000000..8ee813bb --- /dev/null +++ b/src/AspireHost/Program.cs @@ -0,0 +1,14 @@ +using Projects; + +var builder = DistributedApplication.CreateBuilder(args); + +var bvgRevision = builder.AddProject("bvgrevision"); +var apiService = builder.AddProject("apiservice"); + +builder + .AddProject("webfrontend") + .WithReference(bvgRevision) + .WithReference(apiService) + .WithExternalHttpEndpoints(); + +builder.Build().Run(); diff --git a/src/AspireHost/appsettings.Development.json b/src/AspireHost/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/src/AspireHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/AspireHost/appsettings.json b/src/AspireHost/appsettings.json new file mode 100644 index 00000000..31c092aa --- /dev/null +++ b/src/AspireHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/src/BlazorApp.Components.Tests/BlazorApp.Components.Tests.csproj b/src/BlazorApp.Components.Tests/BlazorApp.Components.Tests.csproj index a56d25ce..a46b247f 100644 --- a/src/BlazorApp.Components.Tests/BlazorApp.Components.Tests.csproj +++ b/src/BlazorApp.Components.Tests/BlazorApp.Components.Tests.csproj @@ -1,21 +1,21 @@  - net7.0 + net8.0 enable false - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/BlazorApp.Components.Tests/PersonServiceMockTests.cs b/src/BlazorApp.Components.Tests/PersonServiceMockTests.cs index 85c3ea2b..92aa3580 100644 --- a/src/BlazorApp.Components.Tests/PersonServiceMockTests.cs +++ b/src/BlazorApp.Components.Tests/PersonServiceMockTests.cs @@ -1,7 +1,7 @@ using BlazorApp.Services; using BlazorApp.ViewModels; using Bunit; -using PensionCoach.Tools.CommonTypes; +using Domain.Enums; namespace BlazorApp.Components.Tests; @@ -91,6 +91,7 @@ private PersonViewModel CreatePerson(Guid id) ReligiousGroupType = ReligiousGroupType.Protestant, TaxableIncome = 100_000, TaxableWealth = 500_000, + FinalRetirementPension = 30_000, FinalCapital3a = 150_000, FinalRetirementCapital = 500_000 }; diff --git a/src/BlazorApp.Components.Tests/PersonServiceWithBrowserStorageTests.cs b/src/BlazorApp.Components.Tests/PersonServiceWithBrowserStorageTests.cs index 15c8f2bc..9180757e 100644 --- a/src/BlazorApp.Components.Tests/PersonServiceWithBrowserStorageTests.cs +++ b/src/BlazorApp.Components.Tests/PersonServiceWithBrowserStorageTests.cs @@ -1,7 +1,7 @@ using BlazorApp.Services; using BlazorApp.ViewModels; using Bunit; -using PensionCoach.Tools.CommonTypes; +using Domain.Enums; namespace BlazorApp.Components.Tests; @@ -91,6 +91,7 @@ private PersonViewModel CreatePerson(Guid id) ReligiousGroupType = ReligiousGroupType.Protestant, TaxableIncome = 100_000, TaxableWealth = 500_000, + FinalRetirementPension = 30_000, FinalCapital3a = 150_000, FinalRetirementCapital = 500_000 }; diff --git a/src/BlazorApp.Services.Tests/BlazorApp.Services.Tests.csproj b/src/BlazorApp.Services.Tests/BlazorApp.Services.Tests.csproj index c42651d2..d1328313 100644 --- a/src/BlazorApp.Services.Tests/BlazorApp.Services.Tests.csproj +++ b/src/BlazorApp.Services.Tests/BlazorApp.Services.Tests.csproj @@ -1,21 +1,21 @@  - net7.0 + net8.0 enable false - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/BlazorApp.Services.Tests/TaxScenarioMockTests.cs b/src/BlazorApp.Services.Tests/TaxScenarioMockTests.cs index c9e8d7ad..ca7307ef 100644 --- a/src/BlazorApp.Services.Tests/TaxScenarioMockTests.cs +++ b/src/BlazorApp.Services.Tests/TaxScenarioMockTests.cs @@ -1,56 +1,51 @@ -using BlazorApp.Services.Mock; -using PensionCoach.Tools.CommonTypes; +using Application.Features.TaxScenarios.Models; +using BlazorApp.Services.Mock; +using Domain.Enums; +using Domain.Models.Tax; using PensionCoach.Tools.CommonTypes.MultiPeriod; -using PensionCoach.Tools.TaxComparison; using Snapshooter.Xunit; -namespace BlazorApp.Services.Tests +namespace BlazorApp.Services.Tests; + +public class TaxScenarioMockTests { - public class TaxScenarioMockTests - { - private readonly ITaxScenarioService service; + private readonly ITaxScenarioService service = new MockTaxComparisonService(); - public TaxScenarioMockTests() - { - service = new MockTaxComparisonService(); - } + [Fact(DisplayName = "Mock Tax Scenario Purchase")] + public async Task Calculate_Tax_Scenario_Purchase_With_Mocked_Service() + { + // given + int calculationYear = 2022; + int yearOfCapitalBenefitWithdrawal = 2032; - [Fact(DisplayName = "Mock Tax Scenario Purchase")] - public async Task Calculate_Tax_Scenario_Purchase_With_Mocked_Service() + var request = new CapitalBenefitTransferInComparerRequest { - // given - int calculationYear = 2022; - int yearOfCapitalBenefitWithdrawal = 2032; + Name = "Test Multi-Period Calculator", + CalculationYear = calculationYear, + BfsMunicipalityId = 261, + CivilStatus = CivilStatus.Married, + ReligiousGroup = ReligiousGroupType.Other, + PartnerReligiousGroup = ReligiousGroupType.Other, + TaxableIncome = 100_000, + TaxableFederalIncome = 100_000, + TaxableWealth = 500_000, - var request = new CapitalBenefitTransferInComparerRequest + WithCapitalBenefitTaxation = true, + TransferIns = new List { - Name = "Test Multi-Period Calculator", - CalculationYear = calculationYear, - BfsMunicipalityId = 261, - CivilStatus = CivilStatus.Married, - ReligiousGroup = ReligiousGroupType.Other, - PartnerReligiousGroup = ReligiousGroupType.Other, - TaxableIncome = 100_000, - TaxableFederalIncome = 100_000, - TaxableWealth = 500_000, - - WithCapitalBenefitTaxation = true, - TransferIns = new List - { - new(10_000, new DateTime(calculationYear, 1, 1)) - }, - Withdrawals = new List - { - new(1, new DateTime(yearOfCapitalBenefitWithdrawal, 1, 1)) - }, - CapitalBenefitsBeforeWithdrawal = 400_000, - NetWealthReturn = 0.02M - }; + new(10_000, new DateTime(calculationYear, 1, 1)) + }, + Withdrawals = new List + { + new(1, new DateTime(yearOfCapitalBenefitWithdrawal, 1, 1)) + }, + CapitalBenefitsBeforeWithdrawal = 400_000, + NetWealthReturn = 0.02M + }; - // when - CapitalBenefitsTransferInResponse result = await service.CalculateAsync(request); + // when + ScenarioCalculationResponse result = await service.CalculateAsync(request); - Snapshot.Match(result); - } + Snapshot.Match(result); } } diff --git a/src/BlazorApp/App.razor b/src/BlazorApp/App.razor index 136e4f3e..d8dce59d 100644 --- a/src/BlazorApp/App.razor +++ b/src/BlazorApp/App.razor @@ -1,4 +1,4 @@ - + @@ -7,4 +7,4 @@

Sorry, there's nothing at this address.

-
+
\ No newline at end of file diff --git a/src/BlazorApp/BlazorApp - Backup.csproj b/src/BlazorApp/BlazorApp - Backup.csproj new file mode 100644 index 00000000..5a295cc2 --- /dev/null +++ b/src/BlazorApp/BlazorApp - Backup.csproj @@ -0,0 +1,83 @@ + + + net8.0 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + PensionComponents.resx + + + True + True + Resource.resx + + + + + + PublicResXFileCodeGenerator + + + PublicResXFileCodeGenerator + PensionComponents.Designer.cs + + + PublicResXFileCodeGenerator + + + PublicResXFileCodeGenerator + Resource.Designer.cs + + + + + + + + + <_ContentIncludedByDefault Remove="Features\BvgRevision21\BvgRevision21Comparison.razor" /> + <_ContentIncludedByDefault Remove="MyComponents\JsonPrettyFormat.razor" /> + + + + + + + + + + + + diff --git a/src/BlazorApp/BlazorApp.csproj b/src/BlazorApp/BlazorApp.csproj index d21416d4..03941b0e 100644 --- a/src/BlazorApp/BlazorApp.csproj +++ b/src/BlazorApp/BlazorApp.csproj @@ -1,29 +1,28 @@  - - - net7.0 - true - - + + net8.0 + true + - - - - - - - - - + + + + + + + + + + + - @@ -65,7 +64,17 @@ + <_ContentIncludedByDefault Remove="Features\BvgRevision21\BvgPersonDetails.razor" /> + <_ContentIncludedByDefault Remove="Features\BvgRevision21\BvgRevision21Comparison.razor" /> <_ContentIncludedByDefault Remove="MyComponents\JsonPrettyFormat.razor" /> + + + + + + + + diff --git a/src/BlazorApp/Features/BvgRevision21/BvgPersonDetails.razor b/src/BlazorApp/Features/BvgRevision21/BvgPersonDetails.razor new file mode 100644 index 00000000..b701d54f --- /dev/null +++ b/src/BlazorApp/Features/BvgRevision21/BvgPersonDetails.razor @@ -0,0 +1,28 @@ +@using System.Globalization +@using BlazorApp.Features.BvgRevision21.Models + +@inject IStringLocalizer Localizer + + + + + + + + + + + + + + + + + + + + + +@code { + [Parameter] public BvgPersonViewModel Person { get; set; } +} \ No newline at end of file diff --git a/src/BlazorApp/Features/BvgRevision21/BvgRevision21Comparison.razor b/src/BlazorApp/Features/BvgRevision21/BvgRevision21Comparison.razor new file mode 100644 index 00000000..d7666d00 --- /dev/null +++ b/src/BlazorApp/Features/BvgRevision21/BvgRevision21Comparison.razor @@ -0,0 +1,120 @@ +@page "/SocialSecurity/BvgRevision21Comparison" + +@using Application.Bvg +@using BlazorApp.Features.BvgRevision21.Models +@using Domain.Models.Bvg +@using LanguageExt +@using Microsoft.Extensions.Logging + +@inject ILogger Logger +@inject IPersonService PersonService +@inject BvgCalculator BvgCalculator + + + + + + Vergleich mit BVG Reform 21 + + + + + + + + + + Die BVG-Reform 21 zielt darauf ab, die Finanzierung der 2. Säule zu stärken, + das Leistungsniveau insgesamt zu erhalten und die Absicherung von Teilzeitbeschäftigten – insbesondere von Frauen + – zu verbessern. Sie umfasst Massnahmen wie die Senkung des Umwandlungssatzes und die Verstärkung des Sparprozesses. + +
+ Link zur offiziellen Dokumentation +
+ + + +
+
+ + + + + + Neben Angaben zur Person wie Geschlecht und Geburtsdatum, braucht es zusätzliche Werte aus dem Vorsorgeausweis, + um die voraussichtlichen BVG-Altersleistungen (wie Alters-, Invaliden- und Partnerrente und das Altersguthaben zum Zeitpunkt der Pensionierung) zu berechnen: + - AHV-Jahreslohn + - das aktuelle Altersguthaben + - das voraussichtliche Altersguthaben der eigenen Pensionskasse + + + + + + + +
+
+ + Berechne + +
+
+ +@if (bvgResult != null) +{ + + + + Resultat + + + @bvgResult.FinalRetirementCapitalWithoutInterest + + + @bvgResult.FinalRetirementCapital + + + +} + +@code { + private BvgPersonViewModel selectedPerson; + private IReadOnlyCollection persons; + private BvgCalculationResult bvgResult; + + protected override async Task OnInitializedAsync() + { + persons = (await PersonService.GetPersonsAsync()).ToList(); + } + + private void OnSubmit() + { + int endOfYear = DateTime.Now.Year; + + BvgPerson bvgPerson = new() + { + DateOfBirth = selectedPerson.DateOfBirth ?? DateTime.MaxValue, + Gender = selectedPerson.Gender, + ReportedSalary = 100000, + PartTimeDegree = decimal.One + }; + + Either result = BvgCalculator.Calculate(endOfYear, selectedPerson.BvgRetirementCapitalEndOfYear, bvgPerson); + + result.IfRight(r => bvgResult = r); + } + + private void HandleSelectPerson(Guid personId) + { + selectedPerson = persons.Where(item => item.Id == personId) + .Select(p => new BvgPersonViewModel + { + Name = p. Name, + DateOfBirth = p.DateOfBirth, + Gender = p.Gender, + FinalRetirementCapital = p.FinalRetirementCapital + }) + .Single(); + } +} \ No newline at end of file diff --git a/src/BlazorApp/Features/BvgRevision21/Models/BvgPersonViewModel.cs b/src/BlazorApp/Features/BvgRevision21/Models/BvgPersonViewModel.cs new file mode 100644 index 00000000..be2d20da --- /dev/null +++ b/src/BlazorApp/Features/BvgRevision21/Models/BvgPersonViewModel.cs @@ -0,0 +1,22 @@ +using System; +using Domain.Enums; +using System.ComponentModel.DataAnnotations; + +namespace BlazorApp.Features.BvgRevision21.Models; + +public class BvgPersonViewModel +{ + public Guid Id { get; set; } = Guid.NewGuid(); + + [Required] + [StringLength(25, ErrorMessage = "Name ist zu lang.", ErrorMessageResourceName = "error.person.name")] + public string Name { get; set; } + + public DateTime? DateOfBirth { get; set; } + + public Gender Gender { get; set; } + + public decimal BvgRetirementCapitalEndOfYear { get; set; } + + public decimal FinalRetirementCapital { get; set; } +} diff --git a/src/BlazorApp/MyComponents/CantonSelector.razor b/src/BlazorApp/MyComponents/CantonSelector.razor index bd0190b5..ce4e0afa 100644 --- a/src/BlazorApp/MyComponents/CantonSelector.razor +++ b/src/BlazorApp/MyComponents/CantonSelector.razor @@ -1,5 +1,4 @@ -@using PensionCoach.Tools.CommonTypes -@using PensionCoach.Tools.CommonTypes.Tax +@using Domain.Enums @foreach (var m in cantons) diff --git a/src/BlazorApp/MyComponents/CheckSettings/CheckSettingsViewer.razor b/src/BlazorApp/MyComponents/CheckSettings/CheckSettingsViewer.razor index 9a184e08..33cbc9ca 100644 --- a/src/BlazorApp/MyComponents/CheckSettings/CheckSettingsViewer.razor +++ b/src/BlazorApp/MyComponents/CheckSettings/CheckSettingsViewer.razor @@ -22,7 +22,7 @@ } - + @@ -50,31 +50,33 @@ - - - Backend - - - - - @foreach (var key in backendConfiguration) - { - - - @(key.Key + ":") - - +@if (backendConfiguration.Count > 0) +{ + + + Backend + + + + @foreach (var key in backendConfiguration) + { + + + @(key.Key + ":") + + - - - @(key.Value) - - - } + + + @(key.Value) + + + } + + + +} - - - @code { private bool healthCheckStatus; @@ -87,4 +89,5 @@ frontendConfiguration = await SettingsService.GetFrontendConfigurationAsync(); backendConfiguration = await SettingsService.GetBackendConfigurationAsync(); } + } \ No newline at end of file diff --git a/src/BlazorApp/MyComponents/Person/PersonDetails.razor b/src/BlazorApp/MyComponents/CheckSettings/Person/PersonDetails.razor similarity index 90% rename from src/BlazorApp/MyComponents/Person/PersonDetails.razor rename to src/BlazorApp/MyComponents/CheckSettings/Person/PersonDetails.razor index 51b97e73..e788e7a1 100644 --- a/src/BlazorApp/MyComponents/Person/PersonDetails.razor +++ b/src/BlazorApp/MyComponents/CheckSettings/Person/PersonDetails.razor @@ -1,6 +1,6 @@ -@using System.Globalization -@using PensionCoach.Tools.CommonTypes -@using PensionCoach.Tools.CommonTypes.Tax +@using Domain.Models.Tax +@using Domain.Enums +@using System.Globalization @inject IStringLocalizer Localizer @@ -46,6 +46,7 @@ + diff --git a/src/BlazorApp/MyComponents/Person/PersonEdit.razor b/src/BlazorApp/MyComponents/CheckSettings/Person/PersonEdit.razor similarity index 100% rename from src/BlazorApp/MyComponents/Person/PersonEdit.razor rename to src/BlazorApp/MyComponents/CheckSettings/Person/PersonEdit.razor diff --git a/src/BlazorApp/MyComponents/Person/PersonSelector.razor b/src/BlazorApp/MyComponents/CheckSettings/Person/PersonSelector.razor similarity index 53% rename from src/BlazorApp/MyComponents/Person/PersonSelector.razor rename to src/BlazorApp/MyComponents/CheckSettings/Person/PersonSelector.razor index ceed07d3..171b5984 100644 --- a/src/BlazorApp/MyComponents/Person/PersonSelector.razor +++ b/src/BlazorApp/MyComponents/CheckSettings/Person/PersonSelector.razor @@ -1,22 +1,6 @@ -@using PensionCoach.Tools.CommonTypes +@inject IStringLocalizer Localizer -@inject IStringLocalizer Localizer - - - - + @Localizer["person.name"] @Localizer["person.residence"] @@ -26,7 +10,7 @@ @context.Name @context.MunicipalityName - @context.DateOfBirth + @(context.DateOfBirth.HasValue ? DateOnly.FromDateTime(context.DateOfBirth.Value) : "-") @@ -50,22 +34,4 @@ await OnSelect.InvokeAsync(SelectedPerson.Id); } - - private string SelectedRowClassFunc(PersonViewModel element, int rowNumber) - { - if (selectedRowNumber == rowNumber) - { - selectedRowNumber = -1; - return string.Empty; - } - - if (mudTable.SelectedItem == null || !mudTable.SelectedItem.Equals(element)) - { - return string.Empty; - } - - selectedRowNumber = rowNumber; - - return "selected"; - } } diff --git a/src/BlazorApp/MyComponents/Person/PersonView.razor b/src/BlazorApp/MyComponents/CheckSettings/Person/PersonView.razor similarity index 85% rename from src/BlazorApp/MyComponents/Person/PersonView.razor rename to src/BlazorApp/MyComponents/CheckSettings/Person/PersonView.razor index 26a0b2b6..d6faaccc 100644 --- a/src/BlazorApp/MyComponents/Person/PersonView.razor +++ b/src/BlazorApp/MyComponents/CheckSettings/Person/PersonView.razor @@ -1,9 +1,4 @@ -@using PensionCoach.Tools.CommonTypes -@using Microsoft.Extensions.Localization -@using BlazorApp.Resources -@using System.Globalization - -@inject IStringLocalizer Localizer +@inject IStringLocalizer Localizer diff --git a/src/BlazorApp/MyComponents/CivilStatusSelector.razor b/src/BlazorApp/MyComponents/CivilStatusSelector.razor index 87d3a190..d8c55522 100644 --- a/src/BlazorApp/MyComponents/CivilStatusSelector.razor +++ b/src/BlazorApp/MyComponents/CivilStatusSelector.razor @@ -1,7 +1,4 @@ -@using Microsoft.AspNetCore.Components -@using Microsoft.Extensions.Localization -@using PensionCoach.Tools.CommonTypes -@using BlazorApp.Resources +@using Domain.Enums @inject IStringLocalizer Localizer diff --git a/src/BlazorApp/MyComponents/ClearAccountActionDefinitionForm.razor b/src/BlazorApp/MyComponents/ClearAccountActionDefinitionForm.razor index 36005d7a..bd3ee652 100644 --- a/src/BlazorApp/MyComponents/ClearAccountActionDefinitionForm.razor +++ b/src/BlazorApp/MyComponents/ClearAccountActionDefinitionForm.razor @@ -1,10 +1,8 @@ -@using Microsoft.AspNetCore.Components -@using PensionCoach.Tools.CommonTypes -@using PensionCoach.Tools.CommonTypes.MultiPeriod -@using PensionCoach.Tools.CommonTypes.MultiPeriod.Actions -@using PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions -@using PensionCoach.Tools.CommonTypes.Tax +@using PensionCoach.Tools.CommonTypes.Tax @using System.Globalization +@using Domain.Enums +@using Domain.Models.MultiPeriod +@using Domain.Models.MultiPeriod.Definitions @inject IStringLocalizer Localizer @@ -14,7 +12,7 @@ - + diff --git a/src/BlazorApp/MyComponents/ComputedProgressBar.razor b/src/BlazorApp/MyComponents/ComputedProgressBar.razor index 6fcebed2..4f21e0c0 100644 --- a/src/BlazorApp/MyComponents/ComputedProgressBar.razor +++ b/src/BlazorApp/MyComponents/ComputedProgressBar.razor @@ -1,7 +1,5 @@ -@using Microsoft.AspNetCore.Components - - - + + @Value% diff --git a/src/BlazorApp/MyComponents/DefaultTaxPerson.cs b/src/BlazorApp/MyComponents/DefaultTaxPerson.cs deleted file mode 100644 index e20c78be..00000000 --- a/src/BlazorApp/MyComponents/DefaultTaxPerson.cs +++ /dev/null @@ -1,20 +0,0 @@ -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; - -namespace BlazorApp.MyComponents -{ - public static class DefaultTaxPerson - { - public static TaxPerson GetDefaultValues() => new() - { - Name = "Toni", - CivilStatus = CivilStatus.Single, - NumberOfChildren = 0, - ReligiousGroupType = ReligiousGroupType.Protestant, - PartnerReligiousGroupType = null, - TaxableIncome = 100_000, - TaxableFederalIncome = 100_000, - TaxableWealth = 500_000 - }; - } -} diff --git a/src/BlazorApp/MyComponents/GenderSelector.razor b/src/BlazorApp/MyComponents/GenderSelector.razor index 8095a06b..dd2cedaa 100644 --- a/src/BlazorApp/MyComponents/GenderSelector.razor +++ b/src/BlazorApp/MyComponents/GenderSelector.razor @@ -1,6 +1,4 @@ -@using Microsoft.AspNetCore.Components -@using Microsoft.Extensions.Localization -@using PensionCoach.Tools.CommonTypes +@using Domain.Enums @inject IStringLocalizer Localizer @@ -9,7 +7,7 @@ { @if (DisplayInGrid) { - @GetViewModel().Name + @GetViewModel().Name } else { diff --git a/src/BlazorApp/MyComponents/GenericCashFlowDefinitionForm.razor b/src/BlazorApp/MyComponents/GenericCashFlowDefinitionForm.razor index 163e331a..228c463a 100644 --- a/src/BlazorApp/MyComponents/GenericCashFlowDefinitionForm.razor +++ b/src/BlazorApp/MyComponents/GenericCashFlowDefinitionForm.razor @@ -1,6 +1,5 @@ -@using PensionCoach.Tools.CommonTypes.MultiPeriod -@using PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions -@using System.Globalization +@using System.Globalization +@using Domain.Models.MultiPeriod.Definitions @@ -10,7 +9,7 @@ - + diff --git a/src/BlazorApp/MyComponents/InvestmentDefinitionForm.razor b/src/BlazorApp/MyComponents/InvestmentDefinitionForm.razor index 915774e3..bf3f700c 100644 --- a/src/BlazorApp/MyComponents/InvestmentDefinitionForm.razor +++ b/src/BlazorApp/MyComponents/InvestmentDefinitionForm.razor @@ -1,5 +1,4 @@ -@using BlazorApp.ViewModels -@using System.Globalization +@using System.Globalization diff --git a/src/BlazorApp/MyComponents/JsonPrettyFormat.razor b/src/BlazorApp/MyComponents/JsonPrettyFormat.razor index 6f6d99d3..99230db1 100644 --- a/src/BlazorApp/MyComponents/JsonPrettyFormat.razor +++ b/src/BlazorApp/MyComponents/JsonPrettyFormat.razor @@ -1,5 +1,4 @@ -@using BlazorApp.Services -@using System.Text.Json +@using System.Text.Json @using System.Text.Encodings.Web + + + + +
+ + + + +
+
+ +
+ + An error has occurred. This application may no longer respond until reloaded. + + + An unhandled exception has occurred. See browser dev tools for details. + + Reload + 🗙 +
+ + + + + + diff --git a/src/BvgCalculator.Abstractions/BvgCalculator.Abstractions.csproj b/src/BvgCalculator.Abstractions/BvgCalculator.Abstractions.csproj deleted file mode 100644 index 6e948412..00000000 --- a/src/BvgCalculator.Abstractions/BvgCalculator.Abstractions.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - net7.0 - PensionCoach.Tools.BvgCalculator - PensionCoach.Tools.BvgCalculator.Abstractions - - - - - - - - - - - - diff --git a/src/BvgCalculator.Abstractions/IBvgCalculator.cs b/src/BvgCalculator.Abstractions/IBvgCalculator.cs deleted file mode 100644 index 0367a48b..00000000 --- a/src/BvgCalculator.Abstractions/IBvgCalculator.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.BvgCalculator.Models; - -namespace PensionCoach.Tools.BvgCalculator -{ - public interface IBvgCalculator - { - Task> CalculateAsync( - PredecessorRetirementCapital predecessorCapital, DateTime dateOfProcess, BvgPerson person); - } -} diff --git a/src/BvgCalculator.Abstractions/IBvgRetirementCreditsTable.cs b/src/BvgCalculator.Abstractions/IBvgRetirementCreditsTable.cs deleted file mode 100644 index 2d312315..00000000 --- a/src/BvgCalculator.Abstractions/IBvgRetirementCreditsTable.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace PensionCoach.Tools.BvgCalculator -{ - public interface IBvgRetirementCredits - { - decimal GetRateInPercentage(int bvgAge); - decimal GetRate(int bvgAge); - } -} diff --git a/src/BvgCalculator.Abstractions/Models/PredecessorRetirementCapital.cs b/src/BvgCalculator.Abstractions/Models/PredecessorRetirementCapital.cs deleted file mode 100644 index 47099436..00000000 --- a/src/BvgCalculator.Abstractions/Models/PredecessorRetirementCapital.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; - -namespace PensionCoach.Tools.BvgCalculator.Models -{ - public class PredecessorRetirementCapital - { - /// - /// Gets or sets the process date. - /// - /// - /// The process date. - /// - public DateTime DateOfProcess { get; set; } - - /// - /// Gets or sets the begin of year amount. - /// - /// - /// The begin of year amount. - /// - public decimal BeginOfYearAmount { get; set; } - - /// - /// Gets or sets the end of year amount. - /// - /// - /// The end of year amount. - /// - public decimal EndOfYearAmount { get; set; } - - /// - /// Gets or sets the current amount. - /// - /// - /// The current amount. - /// - public decimal CurrentAmount { get; set; } - } -} diff --git a/src/BvgCalculator.Abstractions/Models/RetirementCapital.cs b/src/BvgCalculator.Abstractions/Models/RetirementCapital.cs deleted file mode 100644 index f8e8b35a..00000000 --- a/src/BvgCalculator.Abstractions/Models/RetirementCapital.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; - -namespace PensionCoach.Tools.BvgCalculator.Models -{ - public class RetirementCapital - { - /// - /// Date of validity - /// - public DateTime Date { get; } - - /// - /// Mandatory retirement assets BVG portion - /// - public decimal Value { get; } - - /// - /// Gets the value without interest BVG by raw retirement credits. - /// - /// - /// The value without interest BVG by raw retirement credits. - /// - public decimal ValueWithoutInterest { get; } - - public RetirementCapital() - { - } - - public RetirementCapital(DateTime date, decimal value, decimal valueWithoutInterest) - { - Date = date; - Value = value; - ValueWithoutInterest = valueWithoutInterest; - } - } -} diff --git a/src/BvgCalculator.Abstractions/Models/RetirementCredit.cs b/src/BvgCalculator.Abstractions/Models/RetirementCredit.cs deleted file mode 100644 index b3628e2f..00000000 --- a/src/BvgCalculator.Abstractions/Models/RetirementCredit.cs +++ /dev/null @@ -1,57 +0,0 @@ -using PensionCoach.Tools.CommonUtils; - -namespace PensionCoach.Tools.BvgCalculator.Models -{ - /// - /// Holds retirement credit - /// - public class RetirementCredit - { - /// - /// Gets the age. - /// - /// - /// The age. - /// - public int Age { get; set; } - - /// - /// BVG portion - /// - public decimal AmountRaw { get; set; } - - /// - /// Gets the amount rounded by 10 (up to 0.1 CHF). - /// - /// - /// The amount rounded10. - /// - public decimal AmountRounded10 => MathUtils.Round10(AmountRaw); - - /// - /// Gets the amount rounded by 60 (up to 0.60 CHF). - /// - /// - /// The amount rounded60. - /// - public decimal AmountRounded60 => MathUtils.Round60(AmountRaw); - - /// - /// Gets the amount rounded up to 100. - /// - /// - /// The amount rounded100. - /// - public decimal AmountRounded100 => MathUtils.Round(AmountRaw); - - public RetirementCredit() - { - } - - public RetirementCredit(decimal amountRaw, int age) - { - AmountRaw = amountRaw; - Age = age; - } - } -} diff --git a/src/BvgCalculator.Integration.Tests/BvgCalculator.Integration.Tests.csproj b/src/BvgCalculator.Integration.Tests/BvgCalculator.Integration.Tests.csproj index 585dbb05..95b68da0 100644 --- a/src/BvgCalculator.Integration.Tests/BvgCalculator.Integration.Tests.csproj +++ b/src/BvgCalculator.Integration.Tests/BvgCalculator.Integration.Tests.csproj @@ -1,22 +1,22 @@  - net7.0 + net8.0 false - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/BvgCalculator.Integration.Tests/BvgCalculatorIntegrationTests.cs b/src/BvgCalculator.Integration.Tests/BvgCalculatorIntegrationTests.cs index 6ece0c38..54797e64 100644 --- a/src/BvgCalculator.Integration.Tests/BvgCalculatorIntegrationTests.cs +++ b/src/BvgCalculator.Integration.Tests/BvgCalculatorIntegrationTests.cs @@ -2,82 +2,77 @@ using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; +using Domain.Enums; +using Domain.Models.Bvg; using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; -using PensionCoach.Tools.BvgCalculator.Models; -using PensionCoach.Tools.CommonTypes; +using PensionCoach.Tools.CommonTypes.Bvg; using Snapshooter.Xunit; using TaxCalculator.WebApi; -using TaxCalculator.WebApi.Models.Bvg; using Xunit; -namespace BvgCalculator.Integration.Tests +namespace BvgCalculator.Integration.Tests; + +[Trait("BVG", "Integration")] +public class BvgCalculatorIntegrationTests : IClassFixture> { - [Trait("BVG", "Integration")] - public class BvgCalculatorIntegrationTests : IClassFixture> - { - private readonly HttpClient client; + private readonly HttpClient client; - public BvgCalculatorIntegrationTests(WebApplicationFactory factory) - { - client = factory.CreateDefaultClient(new Uri("http://localhost/api/calculator/bvg/")); - } + public BvgCalculatorIntegrationTests(WebApplicationFactory factory) + { + client = factory.CreateDefaultClient(new Uri("http://localhost/api/calculator/bvg/")); + } - [Fact(DisplayName = "Default")] - public async Task ShouldCalculateSuccessfully() - { - var request = GetBvgRequest(); + [Fact(DisplayName = "Default")] + public async Task ShouldCalculateSuccessfully() + { + var request = GetBvgRequest(); - var response = - await client.PostAsJsonAsync("benefits", request); + var response = await client.PostAsJsonAsync("benefits", request); - response.EnsureSuccessStatusCode(); + response.EnsureSuccessStatusCode(); - var result = await response.Content.ReadAsStringAsync(); + var result = await response.Content.ReadAsStringAsync(); - Snapshot.Match(result); - } + Snapshot.Match(result); + } - [Theory(DisplayName = "Pension")] - [InlineData(100_000, 0, 8966, "2020-01-01", "1974-08-31", 14698)] - [InlineData(207154, 127506.90, 137091.90, "2021-01-01", "1969-03-17", 19925)] - public async Task ShouldCalculateSuccessfullyPension( - decimal salary, - decimal retirementCapitalBeginOfYear, - decimal retirementCapitalEndOfYear, - string dateOfCalculationStr, - string dateOfBirthStr, - decimal expectedPension) - { - var request = GetBvgRequest(); - request.Salary = salary; - request.RetirementCapitalBeginOfYear = retirementCapitalBeginOfYear; - request.RetirementCapitalEndOfYear = retirementCapitalEndOfYear; - request.DateOfCalculation = DateTime.Parse(dateOfCalculationStr); - request.DateOfBirth = DateTime.Parse(dateOfBirthStr); + [Theory(DisplayName = "Pension")] + [InlineData(100_000, 8966, 2020, "1974-08-31", 14698)] + [InlineData(207154, 137091.90, 2021, "1969-03-17", 19925)] + public async Task ShouldCalculateSuccessfullyPension( + decimal salary, + decimal retirementCapitalEndOfYear, + int calculationYear, + string dateOfBirthStr, + decimal expectedPension) + { + var request = GetBvgRequest(); + request.Salary = salary; + request.RetirementCapitalEndOfYear = retirementCapitalEndOfYear; + request.CalculationYear = calculationYear; + request.DateOfBirth = DateTime.Parse(dateOfBirthStr); - var response = - await client.PostAsJsonAsync("benefits", request); + var response = + await client.PostAsJsonAsync("benefits", request); - response.EnsureSuccessStatusCode(); + response.EnsureSuccessStatusCode(); - BvgCalculationResult result = await response.Content.ReadFromJsonAsync(); + BvgCalculationResult result = await response.Content.ReadFromJsonAsync(); - result.RetirementPension.Should().Be(expectedPension); - } + result.RetirementPension.Should().Be(expectedPension); + } - private static BvgCalculationRequest GetBvgRequest() + private static BvgCalculationRequest GetBvgRequest() + { + return new BvgCalculationRequest { - return new BvgCalculationRequest - { - Name = "Test BVG", - Gender = Gender.Male, - DateOfBirth = new DateTime(1974,8, 31), - DateOfCalculation = new DateTime(2020, 1,1), - RetirementCapitalBeginOfYear = 168_000, - RetirementCapitalEndOfYear = 176_000, - Salary = 45000 - }; - } + Name = "Test BVG", + Gender = Gender.Male, + DateOfBirth = new DateTime(1974,8, 31), + CalculationYear = 2020, + RetirementCapitalEndOfYear = 176_000, + Salary = 45000 + }; } } diff --git a/src/BvgCalculator.Tests/BvgCalculator.Tests.csproj b/src/BvgCalculator.Tests/BvgCalculator.Tests.csproj index 81366635..36eac402 100644 --- a/src/BvgCalculator.Tests/BvgCalculator.Tests.csproj +++ b/src/BvgCalculator.Tests/BvgCalculator.Tests.csproj @@ -1,28 +1,28 @@  - net7.0 + net8.0 false - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/BvgCalculator.Tests/BvgCalculatorFixture.cs b/src/BvgCalculator.Tests/BvgCalculatorFixture.cs index a45c9eb2..18f7b720 100644 --- a/src/BvgCalculator.Tests/BvgCalculatorFixture.cs +++ b/src/BvgCalculator.Tests/BvgCalculatorFixture.cs @@ -1,69 +1,52 @@ using System; using System.Threading.Tasks; +using Application.Bvg; +using Application.Extensions; +using Domain.Enums; +using Domain.Models.Bvg; using LanguageExt; using Microsoft.Extensions.DependencyInjection; -using PensionCoach.Tools.BvgCalculator; -using PensionCoach.Tools.BvgCalculator.Models; -using PensionCoach.Tools.CommonTypes; -namespace BvgCalculator.Tests -{ - public class BvgCalculatorFixture - { - private readonly IBvgCalculator _calculator; +namespace BvgCalculator.Tests; - public BvgCalculatorFixture() - { - ServiceCollection serviceCollection = new ServiceCollection(); - serviceCollection.AddBvgCalculators(); - ServiceProvider provider = serviceCollection.BuildServiceProvider(); +public class BvgCalculatorFixture where T : IBvgCalculator +{ + private readonly T _calculator; - _calculator = provider.GetRequiredService(); - } + public BvgCalculatorFixture() + { + ServiceCollection serviceCollection = new ServiceCollection(); + serviceCollection.AddBvgCalculators(); + ServiceProvider provider = serviceCollection.BuildServiceProvider(); - internal BvgPerson GetCurrentPersonDetails(DateTime birthdate, decimal salary, decimal partTimeDegree) - { - return new BvgPerson - { - DateOfBirth = birthdate, - Gender = Gender.Male, - PartTimeDegree = partTimeDegree, - ReportedSalary = salary, - DisabilityDegree = 0, - }; - } + _calculator = provider.GetRequiredService(); + } - public BvgPerson GetTestPerson( - DateTime birthdate, decimal reportedSalary = 100_000, decimal partTimeDegree = 1) + internal BvgPerson GetCurrentPersonDetails(DateTime birthdate, decimal salary, decimal partTimeDegree) + { + return new BvgPerson { - return GetCurrentPersonDetails(birthdate, reportedSalary, partTimeDegree); - } + DateOfBirth = birthdate, + Gender = Gender.Male, + PartTimeDegree = partTimeDegree, + ReportedSalary = salary, + DisabilityDegree = 0, + }; + } - public BvgPerson GetDefaultPerson() - { - BvgPerson personDetails = new BvgPerson - { - DateOfBirth = new DateTime(1974, 8, 31), - Gender = Gender.Male, - PartTimeDegree = 1.0M, - ReportedSalary = 100000, - }; + public T Calculator() + { + return _calculator; + } - return personDetails; - } + public BvgPerson GetTestPerson( + DateTime birthdate, decimal reportedSalary = 100_000, decimal partTimeDegree = 1) + { + return GetCurrentPersonDetails(birthdate, reportedSalary, partTimeDegree); + } - public Task> GetBvgBenefitsAsync( - decimal currentRetirementCapital, BvgPerson person, DateTime processDate) - { - var predecessorCapital = new PredecessorRetirementCapital - { - DateOfProcess = processDate, - BeginOfYearAmount = 0, - CurrentAmount = 0, - EndOfYearAmount = currentRetirementCapital, - }; - - return _calculator.CalculateAsync(predecessorCapital, processDate, person); - } + public Either GetBvgBenefits(decimal currentRetirementCapital, BvgPerson person, DateTime processDate) + { + return _calculator.Calculate(processDate.Year, currentRetirementCapital, person); } } diff --git a/src/BvgCalculator.Tests/BvgCalculatorTests.cs b/src/BvgCalculator.Tests/BvgCalculatorTests.cs index 9d87343a..03d24d91 100644 --- a/src/BvgCalculator.Tests/BvgCalculatorTests.cs +++ b/src/BvgCalculator.Tests/BvgCalculatorTests.cs @@ -1,184 +1,268 @@ using System; using System.Collections.Generic; -using System.Threading.Tasks; +using Application.Bvg.Models; +using Domain.Enums; +using Domain.Models.Bvg; using FluentAssertions; using LanguageExt; using Newtonsoft.Json; -using PensionCoach.Tools.BvgCalculator.Models; -using PensionCoach.Tools.CommonTypes; using Snapshooter.Xunit; using Xunit; using Xunit.Abstractions; -namespace BvgCalculator.Tests +namespace BvgCalculator.Tests; + +[Trait("BVG", "Calculator")] +public class BvgCalculatorTests : IClassFixture> { - [Trait("BVG", "Calculator")] - public class BvgCalculatorTests : IClassFixture + private readonly BvgCalculatorFixture _fixture; + private readonly ITestOutputHelper _outputHelper; + + public BvgCalculatorTests(BvgCalculatorFixture fixture, ITestOutputHelper outputHelper) + { + _fixture = fixture; + _outputHelper = outputHelper; + } + + [Fact(DisplayName = "BVG Result When Retiring")] + public void ShouldCalculateResultWhenRetiring() + { + // given + DateTime processDate = new DateTime(2019, 1, 1); + DateTime birthdate = new DateTime(1954, 3, 13); + + BvgPerson person = _fixture.GetTestPerson(birthdate); + person.ReportedSalary = 85000M; + person.PartTimeDegree = 0.8M; + + // when + Either response = _fixture.GetBvgBenefits(0, person, processDate); + + BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.RetirementCapitalSequence.Should().NotBeNullOrEmpty(); + + Snapshot.Match(result); + } + + [Fact(DisplayName = "Default BVG Result")] + public void ShouldCalculateDefaultResult() { - private readonly BvgCalculatorFixture _fixture; - private readonly ITestOutputHelper _outputHelper; + // given + DateTime processDate = new DateTime(2019, 1, 1); + DateTime birthdate = new DateTime(1974, 8, 31); + + BvgPerson person = _fixture.GetTestPerson(birthdate); - public BvgCalculatorTests(BvgCalculatorFixture fixture, ITestOutputHelper outputHelper) + // when + var response = _fixture.GetBvgBenefits(0, person, processDate); + + BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.RetirementCapitalSequence.Should().NotBeNullOrEmpty(); + + Snapshot.Match(result); + } + + [Fact(DisplayName = "Calculate Benefits")] + public void ShouldReturnBenefitsCalculationResult() + { + // given + BvgCalculationResult expectedResult = new BvgCalculationResult { - _fixture = fixture; - _outputHelper = outputHelper; - } + DateOfRetirement = new DateTime(2039, 9, 1), + EffectiveSalary = 100000, + InsuredSalary = 60435M, + RetirementCredit = 9065.25M, + RetirementCreditFactor = 0.15M, + RetirementCapitalEndOfYear = 9065.0M, + FinalRetirementCapital = 227286, + FinalRetirementCapitalWithoutInterest = 206687, + PartnerPension = 8433, + OrphanPension = 2811, + ChildPensionForDisabled = 2811, + DisabilityPension = 14055, + RetirementPension = 15455 + }; + + DateTime processDate = new DateTime(2019, 1, 1); + DateTime birthdate = new DateTime(1974, 8, 31); + + BvgPerson person = _fixture.GetTestPerson(birthdate); + + // when + var response = _fixture.GetBvgBenefits(9065, person, processDate); + + BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.RetirementCapitalSequence.Should().NotBeNullOrEmpty(); + result.RetirementCreditSequence.Should().NotBeNullOrEmpty(); + + result.Should().BeEquivalentTo(expectedResult, + o => o.Excluding(obj => obj.RetirementCapitalSequence) + .Excluding(obj => obj.RetirementCreditSequence)); + + _outputHelper.WriteLine(JsonConvert.SerializeObject(result)); + } - [Fact(DisplayName = "BVG Result When Retiring")] - public async Task ShouldCalculateResultWhenRetiring() + [Fact(DisplayName = "Benefits If Below Salary Threshold")] + public void ShouldReturnBenefitsIfBelowSalaryThreshold() + { + // given + BvgCalculationResult expectedResult = new BvgCalculationResult { - // given - DateTime processDate = new DateTime(2019, 1, 1); - DateTime birthdate = new DateTime(1954, 3, 13); + DateOfRetirement = new DateTime(2039, 9, 1), + EffectiveSalary = 20000M, + InsuredSalary = 0M, + RetirementCredit = 0M, + RetirementCreditFactor = 0.15M, + }; - BvgPerson person = _fixture.GetTestPerson(birthdate); - person.ReportedSalary = 85000M; - person.PartTimeDegree = 0.8M; + DateTime processDate = new DateTime(2019, 1, 1); - // when - Either response = - await _fixture.GetBvgBenefitsAsync(0, person, processDate); + BvgPerson person = _fixture.GetCurrentPersonDetails(new DateTime(1974, 8, 31), 20_000, 1M); - BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); + // when + var response = _fixture.GetBvgBenefits(0, person, processDate); - // then - result.RetirementCapitalSequence.Should().NotBeNullOrEmpty(); + BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); - Snapshot.Match(result); - } + // then + result.RetirementCapitalSequence.Should().NotBeNullOrEmpty(); + result.RetirementCreditSequence.Should().NotBeNullOrEmpty(); - [Fact(DisplayName = "Default BVG Result")] - public async Task ShouldCalculateDefaultResult() - { - // given - DateTime processDate = new DateTime(2019, 1, 1); - DateTime birthdate = new DateTime(1974, 8, 31); + result.Should().BeEquivalentTo(expectedResult, options => options.Excluding(o => o.RetirementCapitalSequence) + .Excluding(o => o.RetirementCreditSequence)); - BvgPerson person = _fixture.GetTestPerson(birthdate); + _outputHelper.WriteLine(JsonConvert.SerializeObject(result)); + } - // when - var response = - await _fixture.GetBvgBenefitsAsync(0, person, processDate); + [Theory(DisplayName = "BVG Benefits")] + [MemberData(nameof(GetTestData))] + public void Calculate_Bvg_Benefits( + string dateOfProcessString, + decimal salary, + string dateOfBirthString, + int genderCode, + decimal currentRetirementCapital, + decimal expectedRetirementPension) + { + DateTime dateOfProcess = DateTime.Parse(dateOfProcessString); + DateTime dateOfBirth = DateTime.Parse(dateOfBirthString); + Gender gender = (Gender)genderCode; - BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); + BvgPerson person = _fixture.GetCurrentPersonDetails(dateOfBirth, salary, 1M); + person.Gender = gender; - // then - result.RetirementCapitalSequence.Should().NotBeNullOrEmpty(); + var response = _fixture.GetBvgBenefits(currentRetirementCapital, person, dateOfProcess); - Snapshot.Match(result); - } + BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); - [Fact(DisplayName = "Calculate Benefits")] - public async Task ShouldReturnBenefitsCalculationResult() - { - // given - BvgCalculationResult expectedResult = new BvgCalculationResult - { - DateOfRetirement = new DateTime(2039, 9, 1), - EffectiveSalary = 100000, - InsuredSalary = 60435M, - RetirementCredit = 9065.25M, - RetirementCreditFactor = 0.15M, - RetirementCapitalEndOfYear = 9065.0M, - FinalRetirementCapital = 227286, - FinalRetirementCapitalWithoutInterest = 206687, - PartnerPension = 8433, - OrphanPension = 2811, - ChildPensionForDisabled = 2811, - DisabilityPension = 14055, - RetirementPension = 15455 - }; - - DateTime processDate = new DateTime(2019, 1, 1); - DateTime birthdate = new DateTime(1974, 8, 31); - - BvgPerson person = _fixture.GetTestPerson(birthdate); - - // when - var response = - await _fixture.GetBvgBenefitsAsync(9065, person, processDate); - - BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); - - // then - result.RetirementCapitalSequence.Should().NotBeNullOrEmpty(); - result.RetirementCreditSequence.Should().NotBeNullOrEmpty(); - - result.Should().BeEquivalentTo(expectedResult, - o => o.Excluding(obj => obj.RetirementCapitalSequence) - .Excluding(obj => obj.RetirementCreditSequence)); - - _outputHelper.WriteLine(JsonConvert.SerializeObject(result)); - } - - [Fact(DisplayName = "Benefits If Below Salary Threshold")] - public async Task ShouldReturnBenefitsIfBelowSalaryThreshold() - { - // given - BvgCalculationResult expectedResult = new BvgCalculationResult - { - DateOfRetirement = new DateTime(2039, 9, 1), - EffectiveSalary = 20000M, - InsuredSalary = 0M, - RetirementCredit = 0M, - RetirementCreditFactor = 0.15M, - }; - - DateTime processDate = new DateTime(2019, 1, 1); - - BvgPerson person = _fixture.GetCurrentPersonDetails(new DateTime(1974, 8, 31), 20_000, 1M); - - // when - var response = - await _fixture.GetBvgBenefitsAsync(0, person, processDate); - - BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); - - // then - result.RetirementCapitalSequence.Should().NotBeNullOrEmpty(); - result.RetirementCreditSequence.Should().NotBeNullOrEmpty(); - - result.Should().BeEquivalentTo(expectedResult, options => options.Excluding(o => o.RetirementCapitalSequence) - .Excluding(o => o.RetirementCreditSequence)); - - _outputHelper.WriteLine(JsonConvert.SerializeObject(result)); - } - - [Theory(DisplayName = "BVG Benefits")] - [MemberData(nameof(GetTestData))] - public async Task Calculate_Bvg_Benefits( - string dateOfProcessString, - decimal salary, - string dateOfBirthString, - int genderCode, - decimal currentRetirementCapital, - decimal expectedRetirementPension) - { - DateTime dateOfProcess = DateTime.Parse(dateOfProcessString); - DateTime dateOfBirth = DateTime.Parse(dateOfBirthString); - Gender gender = (Gender)genderCode; + // then + result.RetirementPension.Should().Be(expectedRetirementPension); + } - BvgPerson person = _fixture.GetCurrentPersonDetails(dateOfBirth, salary, 1M); - person.Gender = gender; + [Theory(DisplayName = "Insured Salary")] + [InlineData(2024, 100_000, "1969-03-17", 1, 62475)] + [InlineData(2024, 20_000, "1969-03-17", 1, 0)] + [InlineData(2024, 18_000, "1969-03-17", 1, 0)] + public void Calculate_Insured_Salary( + int processingYear, + decimal effectiveSalary, + string dateOfBirthString, + int genderCode, + decimal expectedInsuredSalary) + { + DateTime dateOfBirth = DateTime.Parse(dateOfBirthString); + Gender gender = (Gender)genderCode; - var response = - await _fixture.GetBvgBenefitsAsync(currentRetirementCapital, person, dateOfProcess); + BvgPerson person = _fixture.GetCurrentPersonDetails(dateOfBirth, effectiveSalary, 1M); + person.Gender = gender; - BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); + Either response = _fixture.Calculator().InsuredSalary(processingYear, person); - // then - result.RetirementPension.Should().Be(expectedRetirementPension); - } + decimal result = response.IfLeft(err => throw new ApplicationException(err)); - public static IEnumerable GetTestData() - { - yield return new object[] - { "2017-01-01", - 100_000, - "1969-03-17", - Gender.Male, - 107202, - 20610 }; - } + // then + result.Should().Be(expectedInsuredSalary); + } + + [Fact(DisplayName = "Salary Array for BVG Maximum")] + public void Calculate_Insured_Salaries_For_BVG_Maximum() + { + // given + int processingYear = 2024; + DateTime dateOfBirth = new(1969, 12, 17); + Gender gender = Gender.Male; + decimal reportedSalary = 100_000; + + BvgPerson person = _fixture.GetCurrentPersonDetails(dateOfBirth, reportedSalary, 1M); + person.Gender = gender; + + Either response = _fixture.Calculator().InsuredSalaries(processingYear, person); + + BvgTimeSeriesPoint[] result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.Should().NotBeNullOrEmpty(); + Snapshot.Match(result); + } + + [Fact(DisplayName = "Retirement Credits Array")] + public void Calculate_Retirement_Credits_Array() + { + // given + int processingYear = 2024; + DateTime dateOfBirth = new(1969, 12, 17); + Gender gender = Gender.Male; + decimal reportedSalary = 100_000; + + BvgPerson person = _fixture.GetCurrentPersonDetails(dateOfBirth, reportedSalary, 1M); + person.Gender = gender; + + Either response = _fixture.Calculator().RetirementCredits(processingYear, person); + + BvgTimeSeriesPoint[] result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.Should().NotBeNullOrEmpty(); + Snapshot.Match(result); + } + + [Fact(DisplayName = "Retirement Credit Factors Array")] + public void Calculate_Retirement_Credit_Factors_Array() + { + // given + int processingYear = 2024; + DateTime dateOfBirth = new(1969, 12, 17); + Gender gender = Gender.Male; + decimal reportedSalary = 100_000; + + BvgPerson person = _fixture.GetCurrentPersonDetails(dateOfBirth, reportedSalary, 1M); + person.Gender = gender; + + Either response = _fixture.Calculator().RetirementCreditFactors(processingYear, person); + + BvgTimeSeriesPoint[] result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.Should().NotBeNullOrEmpty(); + Snapshot.Match(result); + } + + public static IEnumerable GetTestData() + { + yield return + [ + "2017-01-01", + 100_000, + "1969-03-17", + Gender.Male, + 107202, + 20610 + ]; } } diff --git a/src/BvgCalculator.Tests/BvgDateTests.cs b/src/BvgCalculator.Tests/BvgDateTests.cs new file mode 100644 index 00000000..da5e5881 --- /dev/null +++ b/src/BvgCalculator.Tests/BvgDateTests.cs @@ -0,0 +1,118 @@ +using System; +using Application.Bvg.Models; +using Xunit; + +namespace BvgCalculator.Tests; + +[Trait("BVG Tests", "BVG Date")] +public class BvgDateTests +{ + [Fact(DisplayName = "BvgDate Constructor Sets Correct Values")] + public void BvgDate_Constructor_Sets_Correct_Values() + { + // Arrange + DateTime dateTime = new DateTime(2022, 1, 1); + bool isEndOfDay = true; + + // Act + BvgDate bvgDate = new BvgDate(dateTime, true); + + // Assert + Assert.Equal(dateTime, bvgDate.DateTime); + Assert.Equal(isEndOfDay, bvgDate.IsEndOfDay); + } + + [Fact(DisplayName = "BvgDate ToDateTime Returns Correct Value")] + public void BvgDate_ToDateTime_Returns_Correct_Value() + { + // Arrange + DateTime dateTime = new DateTime(2022, 1, 1); + BvgDate bvgDate = new BvgDate(dateTime, true); + + // Act + DateTime result = bvgDate.ToDateTime(); + + // Assert + Assert.Equal(dateTime.AddDays(1), result); + } + + [Fact(DisplayName = "BvgDate Implicit Conversion Returns Correct Value")] + public void BvgDate_Implicit_Conversion_Returns_Correct_Value() + { + // Arrange + DateTime dateTime = new DateTime(2022, 1, 1); + + // Act + BvgDate bvgDate = dateTime; + + // Assert + Assert.Equal(dateTime, bvgDate.DateTime); + Assert.False(bvgDate.IsEndOfDay); + } + + [Theory(DisplayName = "BvgDate Less Than Operator")] + [InlineData("2022-01-01", "2022-01-01", true, true, false)] + [InlineData("2022-01-01", "2022-01-01", true, false, false)] + [InlineData("2022-01-01", "2022-01-01", false, true, true)] + [InlineData("2022-01-01", "2022-01-02", true, false, true)] + [InlineData("2022-01-01", "2022-01-02", false, true, true)] + [InlineData("2022-01-02", "2022-01-01", true, false, false)] + [InlineData("2022-01-02", "2022-01-01", false, true, false)] + public void BvgDate_Less_Than_Operator_Return_Correct_Value( + string leftDate, string rightDate, bool leftIsEndOfDay, bool rightIsEndOfDay, bool expected) + { + // Arrange + DateTime leftDateTime = DateTime.Parse(leftDate); + DateTime rightDateTime = DateTime.Parse(rightDate); + BvgDate left = new BvgDate(leftDateTime, leftIsEndOfDay); + BvgDate right = new BvgDate(rightDateTime, rightIsEndOfDay); + + // Act + bool result = left < right; + + // Assert + Assert.Equal(expected, result); + } + + [Theory(DisplayName = "BvgDate Less Or Equal Than Operator")] + [InlineData("2022-01-01", "2022-01-01", true, true, true)] + [InlineData("2022-01-01", "2022-01-01", true, false, false)] + [InlineData("2022-01-01", "2022-01-01", false, true, true)] + [InlineData("2022-01-01", "2022-01-01", false, false, true)] + [InlineData("2021-12-31", "2022-01-01", true, false, true)] + public void BvgDate_Less_Than_Or_Equal_Operator_Return_Correct_Value( + string leftDate, string rightDate, bool leftIsEndOfDay, bool rightIsEndOfDay, bool expected) + { + // Arrange + DateTime leftDateTime = DateTime.Parse(leftDate); + DateTime rightDateTime = DateTime.Parse(rightDate); + BvgDate left = new BvgDate(leftDateTime, leftIsEndOfDay); + BvgDate right = new BvgDate(rightDateTime, rightIsEndOfDay); + + // Act + bool result = left <= right; + + // Assert + Assert.Equal(expected, result); + } + + [Theory(DisplayName = "BvgDate Equal Operator")] + [InlineData("2022-01-01", "2022-01-01", true, true, true)] + [InlineData("2022-01-01", "2022-01-01", false, false, true)] + [InlineData("2021-12-31", "2022-01-01", true, false, false)] + public void BvgDate_Equal_Operator_Return_Correct_Value( + string leftDate, string rightDate, bool leftIsEndOfDay, bool rightIsEndOfDay, bool expected) + { + // Arrange + DateTime leftDateTime = DateTime.Parse(leftDate); + DateTime rightDateTime = DateTime.Parse(rightDate); + BvgDate left = new BvgDate(leftDateTime, leftIsEndOfDay); + BvgDate right = new BvgDate(rightDateTime, rightIsEndOfDay); + + // Act + bool result = left == right; + + // Assert + Assert.Equal(expected, result); + } +} diff --git a/src/BvgCalculator.Tests/BvgInterestRateTests.cs b/src/BvgCalculator.Tests/BvgInterestRateTests.cs index 3c787f82..140a98c5 100644 --- a/src/BvgCalculator.Tests/BvgInterestRateTests.cs +++ b/src/BvgCalculator.Tests/BvgInterestRateTests.cs @@ -1,26 +1,25 @@ -using FluentAssertions; -using PensionCoach.Tools.BvgCalculator; +using Application.Bvg; +using FluentAssertions; using Xunit; -namespace BvgCalculator.Tests +namespace BvgCalculator.Tests; + +[Trait("BVG", "Interest Rate")] +public class BvgInterestRateTests { - [Trait("BVG", "Interest Rate")] - public class BvgInterestRateTests + [Theory(DisplayName = "Retirement Credit Rate")] + [InlineData(2015, 0.0175)] + [InlineData(1970, 0)] + [InlineData(1985, 0.04)] + [InlineData(2017, 0.01)] + public void ShouldReturnRetirementCreditRate(int year, decimal expectedRate) { - [Theory(DisplayName = "Retirement Credit Rate")] - [InlineData(2015, 0.0175)] - [InlineData(1970, 0)] - [InlineData(1985, 0.04)] - [InlineData(2017, 0.01)] - public void ShouldReturnRetirementCreditRate(int year, decimal expectedRate) - { - // given + // given - // when - decimal result = Bvg.GetInterestRate(year); + // when + decimal result = Bvg.GetInterestRate(year); - // then - result.Should().Be(expectedRate); - } + // then + result.Should().Be(expectedRate); } } diff --git a/src/BvgCalculator.Tests/BvgRetirementCreditTableTests.cs b/src/BvgCalculator.Tests/BvgRetirementCreditTableTests.cs index 1f84621a..724ffbec 100644 --- a/src/BvgCalculator.Tests/BvgRetirementCreditTableTests.cs +++ b/src/BvgCalculator.Tests/BvgRetirementCreditTableTests.cs @@ -1,27 +1,26 @@ -using FluentAssertions; -using PensionCoach.Tools.BvgCalculator; +using Application.Bvg; +using FluentAssertions; using Xunit; -namespace BvgCalculator.Tests +namespace BvgCalculator.Tests; + +[Trait("BVG", "Retirement Credit Table")] +public class BvgRetirementCreditTableTests : IClassFixture> { - [Trait("BVG", "Retirement Credit Table")] - public class BvgRetirementCreditTableTests : IClassFixture + [Theory(DisplayName = "Retirement Credit Rate")] + [InlineData(30, 0.07)] + [InlineData(65, 0.18)] + [InlineData(35, 0.10)] + [InlineData(24, 0.0)] + public void ShouldReturnRetirementCreditRate(int age, decimal expectedRate) { - [Theory(DisplayName = "Retirement Credit Rate")] - [InlineData(30, 0.07)] - [InlineData(65, 0.18)] - [InlineData(35, 0.10)] - [InlineData(24, 0.0)] - public void ShouldReturnRetirementCreditRate(int age, decimal expectedRate) - { - // given + // given - // when - BvgRetirementCreditsTable creditTable = new BvgRetirementCreditsTable(); - decimal result = creditTable.GetRateInPercentage(age); + // when + BvgRetirementCreditsTable creditTable = new BvgRetirementCreditsTable(); + decimal result = creditTable.GetRateInPercentage(age); - // then - result.Should().Be(expectedRate); - } + // then + result.Should().Be(expectedRate); } } diff --git a/src/BvgCalculator.Tests/BvgRetirementDateCalculatorTests.cs b/src/BvgCalculator.Tests/BvgRetirementDateCalculatorTests.cs new file mode 100644 index 00000000..cd8c8c98 --- /dev/null +++ b/src/BvgCalculator.Tests/BvgRetirementDateCalculatorTests.cs @@ -0,0 +1,36 @@ +using System; +using Application.Bvg; +using Domain.Enums; +using FluentAssertions; +using Xunit; + +namespace BvgCalculator.Tests; + +[Trait("BVG", "Retirement Date Calculator")] +public class BvgRetirementDateCalculatorTests(BvgRetirementDateCalculator fixture) : IClassFixture +{ + [Theory(DisplayName = "Retirement Dates")] + [InlineData(1, "1960-12-31", "2025-01-01")] + [InlineData(2, "1961-01-01", "2026-02-01")] + [InlineData(1, "1961-01-01", "2025-05-01")] + [InlineData(2, "1962-01-01", "2027-02-01")] + [InlineData(1, "1962-01-01", "2026-08-01")] + [InlineData(2, "1963-01-01", "2028-02-01")] + [InlineData(1, "1963-01-01", "2027-11-01")] + [InlineData(2, "1969-03-17", "2034-04-01")] + [InlineData(1, "1969-03-17", "2034-04-01")] + [InlineData(1, "1955-03-17", "2019-04-01")] + public void Return_Retirement_Date(int genderCode, string birthdateAsString, string expectedRetirementDateAsString) + { + // given + DateTime expectedDateOfRetirement = DateTime.Parse(expectedRetirementDateAsString); + DateTime birthdate = DateTime.Parse(birthdateAsString); + Gender gender = (Gender)genderCode; + + // when + DateTime result = fixture.DateOfRetirement(gender, birthdate); + + // then + result.Should().Be(expectedDateOfRetirement); + } +} diff --git a/src/BvgCalculator.Tests/BvgRevisionCalculatorTests.cs b/src/BvgCalculator.Tests/BvgRevisionCalculatorTests.cs new file mode 100644 index 00000000..b2171a02 --- /dev/null +++ b/src/BvgCalculator.Tests/BvgRevisionCalculatorTests.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections.Generic; +using Application.Bvg.Models; +using Domain.Enums; +using Domain.Models.Bvg; +using FluentAssertions; +using LanguageExt; +using Snapshooter.Xunit; +using Xunit; + +namespace BvgCalculator.Tests; + +[Trait("BVG", "Calculator")] +public class BvgRevisionCalculatorTests : IClassFixture> +{ + private readonly BvgCalculatorFixture _fixture; + + public BvgRevisionCalculatorTests(BvgCalculatorFixture fixture) + { + _fixture = fixture; + } + + [Fact(DisplayName = "BVG Result When Retiring")] + public void ShouldCalculateResultWhenRetiring() + { + // given + DateTime processDate = new DateTime(2024, 1, 1); + DateTime birthdate = new DateTime(1959, 3, 13); + decimal retirementCapitalEndOfYear = 2667.375M; + + BvgPerson person = _fixture.GetTestPerson(birthdate); + person.ReportedSalary = 85000M; + person.PartTimeDegree = 0.8M; + + // when + Either response = _fixture.GetBvgBenefits(retirementCapitalEndOfYear, person, processDate); + + BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.RetirementCapitalSequence.Should().NotBeNullOrEmpty(); + + Snapshot.Match(result); + } + + [Fact(DisplayName = "Default BVG Result")] + public void ShouldCalculateDefaultResult() + { + // given + DateTime processDate = new DateTime(2024, 1, 1); + DateTime birthdate = new DateTime(1969, 12, 17); + decimal retirementCapitalEndOfYear = 11245.5M; + + BvgPerson person = _fixture.GetTestPerson(birthdate); + + // when + var response = _fixture.GetBvgBenefits(retirementCapitalEndOfYear, person, processDate); + + BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.RetirementCapitalSequence.Should().NotBeNullOrEmpty(); + + Snapshot.Match(result); + } + + [Fact(DisplayName = "AHV Transition Generation")] + public void Calculate_Ahv_Transition_Generation() + { + // given + DateTime processDate = new DateTime(2024, 1, 1); + DateTime birthdate = new DateTime(1962, 3, 17); + decimal retirementCapitalEndOfYear = 11245.5M; + + BvgPerson person = _fixture.GetTestPerson(birthdate); + person.Gender = Gender.Female; + + // when + var response = _fixture.GetBvgBenefits(retirementCapitalEndOfYear, person, processDate); + + BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.RetirementCapitalSequence.Should().NotBeNullOrEmpty(); + + Snapshot.Match(result); + } + + [Fact(DisplayName = "Calculate Benefits")] + public void ShouldReturnBenefitsCalculationResult() + { + // given + DateTime processDate = new DateTime(2024, 1, 1); + DateTime birthdate = new DateTime(1974, 8, 31); + decimal retirementCapitalEndOfYear = 9371.25M; + + BvgPerson person = _fixture.GetTestPerson(birthdate); + + // when + var response = _fixture.GetBvgBenefits(retirementCapitalEndOfYear, person, processDate); + + BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + Snapshot.Match(result); + } + + [Fact(DisplayName = "Benefits If Below Salary Threshold")] + public void ShouldReturnBenefitsIfBelowSalaryThreshold() + { + // given + DateTime processDate = new DateTime(2024, 1, 1); + + BvgPerson person = _fixture.GetCurrentPersonDetails(new DateTime(1969, 3, 17), 20_000, 1M); + + // when + var response = _fixture.GetBvgBenefits(0, person, processDate); + + BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.RetirementCapitalSequence.Should().NotBeNullOrEmpty(); + result.RetirementCreditSequence.Should().NotBeNullOrEmpty(); + + Snapshot.Match(result); + } + + [Theory(DisplayName = "BVG Benefits")] + [MemberData(nameof(GetTestData))] + public void Calculate_Bvg_Benefits( + string dateOfProcessString, + decimal salary, + string dateOfBirthString, + int genderCode, + decimal currentRetirementCapital, + decimal expectedRetirementPension) + { + DateTime dateOfProcess = DateTime.Parse(dateOfProcessString); + DateTime dateOfBirth = DateTime.Parse(dateOfBirthString); + Gender gender = (Gender)genderCode; + + BvgPerson person = _fixture.GetCurrentPersonDetails(dateOfBirth, salary, 1M); + person.Gender = gender; + + var response = _fixture.GetBvgBenefits(currentRetirementCapital, person, dateOfProcess); + + BvgCalculationResult result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.RetirementPension.Should().BeApproximately(expectedRetirementPension, 1M); + } + + [Theory(DisplayName = "Insured Salary")] + [InlineData(2024, 100_000, "1969-03-17", 1, 62475)] + [InlineData(2024, 20_000, "1969-03-17", 1, 0)] + [InlineData(2024, 18_000, "1969-03-17", 1, 0)] + [InlineData(2026, 100_000, "1969-03-17", 1, 70560)] + [InlineData(2026, 20_000, "1969-03-17", 1, 16000)] + [InlineData(2026, 18_000, "1969-03-17", 1, 0)] + public void Calculate_Insured_Salary( + int processingYear, + decimal effectiveSalary, + string dateOfBirthString, + int genderCode, + decimal expectedInsuredSalary) + { + DateTime dateOfBirth = DateTime.Parse(dateOfBirthString); + Gender gender = (Gender)genderCode; + + BvgPerson person = _fixture.GetCurrentPersonDetails(dateOfBirth, effectiveSalary, 1M); + person.Gender = gender; + + Either response = _fixture.Calculator().InsuredSalary(processingYear, person); + + decimal result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.Should().Be(expectedInsuredSalary); + } + + [Fact(DisplayName = "Salary Array for BVG Maximum")] + public void Calculate_Insured_Salaries_For_BVG_Maximum() + { + // given + int processingYear = 2024; + DateTime dateOfBirth = new(1969, 12, 17); + Gender gender = Gender.Male; + decimal reportedSalary = 100_000; + + BvgPerson person = _fixture.GetCurrentPersonDetails(dateOfBirth, reportedSalary, 1M); + person.Gender = gender; + + Either response = _fixture.Calculator().InsuredSalaries(processingYear, person); + + BvgTimeSeriesPoint[] result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.Should().NotBeNullOrEmpty(); + Snapshot.Match(result); + } + + [Fact(DisplayName = "Salary Array For Between Thresholds")] + public void Calculate_Insured_Salaries_Between_Threshold() + { + // given + int processingYear = 2024; + DateTime dateOfBirth = new(1974, 8, 31); + Gender gender = Gender.Female; + decimal reportedSalary = 20_000; + + BvgPerson person = _fixture.GetCurrentPersonDetails(dateOfBirth, reportedSalary, 1M); + person.Gender = gender; + + Either response = _fixture.Calculator().InsuredSalaries(processingYear, person); + + BvgTimeSeriesPoint[] result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.Should().NotBeNullOrEmpty(); + Snapshot.Match(result); + } + + [Fact(DisplayName = "Retirement Credits Array")] + public void Calculate_Retirement_Credits_Array() + { + // given + int processingYear = 2024; + DateTime dateOfBirth = new(1969, 12, 17); + Gender gender = Gender.Male; + decimal reportedSalary = 100_000; + + BvgPerson person = _fixture.GetCurrentPersonDetails(dateOfBirth, reportedSalary, 1M); + person.Gender = gender; + + Either response = _fixture.Calculator().RetirementCredits(processingYear, person); + + BvgTimeSeriesPoint[] result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.Should().NotBeNullOrEmpty(); + Snapshot.Match(result); + } + + [Fact(DisplayName = "Retirement Credit Factors Array")] + public void Calculate_Retirement_Credit_Factors_Array() + { + // given + int processingYear = 2024; + DateTime dateOfBirth = new(1969, 12, 17); + Gender gender = Gender.Male; + decimal reportedSalary = 100_000; + + BvgPerson person = _fixture.GetCurrentPersonDetails(dateOfBirth, reportedSalary, 1M); + person.Gender = gender; + + Either response = _fixture.Calculator().RetirementCreditFactors(processingYear, person); + + BvgTimeSeriesPoint[] result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.Should().NotBeNullOrEmpty(); + Snapshot.Match(result); + } + + [Fact(DisplayName = "Retirement Credit Factors Array Young")] + public void Calculate_Retirement_Credit_Factors_Array_Young() + { + // given + int processingYear = 2024; + DateTime dateOfBirth = new(2002, 3, 17); + Gender gender = Gender.Male; + decimal reportedSalary = 50_000; + + BvgPerson person = _fixture.GetCurrentPersonDetails(dateOfBirth, reportedSalary, 1M); + person.Gender = gender; + + Either response = _fixture.Calculator().RetirementCreditFactors(processingYear, person); + + BvgTimeSeriesPoint[] result = response.IfLeft(err => throw new ApplicationException(err)); + + // then + result.Should().NotBeNullOrEmpty(); + Snapshot.Match(result); + } + + public static IEnumerable GetTestData() + { + yield return + [ + "2024-01-01", + 100_000, + "1969-03-17", + Gender.Male, + 11245.5, + 6622 + ]; + } +} diff --git a/src/BvgCalculator.Tests/PensionSupplementCalculatorTests.cs b/src/BvgCalculator.Tests/PensionSupplementCalculatorTests.cs new file mode 100644 index 00000000..5ebe603a --- /dev/null +++ b/src/BvgCalculator.Tests/PensionSupplementCalculatorTests.cs @@ -0,0 +1,39 @@ +using System; +using System.Globalization; +using Application.Bvg; +using Xunit; +using Xunit.Abstractions; + +namespace BvgCalculator.Tests; + +[Trait("BVG", "Calculator")] +public class PensionSupplementCalculatorTests(ITestOutputHelper outputHelper) +{ + private readonly IPensionSupplementCalculator pensionSupplementCalculator = new BvgRevisionPensionSupplementCalculator(); + + [Theory(DisplayName = "Rentenzuschlag")] + [InlineData("1960-12-31", 0, 0)] + [InlineData("1960-12-31", 220500, 0)] + [InlineData("1961-01-01", 441000, 0)] + [InlineData("1961-01-01", 100000, 2400)] + [InlineData("1961-01-01", 500000, 0)] + [InlineData("1969-01-01", 220500, 1800)] + [InlineData("1969-01-01", 325239, 945)] + [InlineData("1973-01-01", 220500, 1200)] + [InlineData("1973-01-01", 325239, 630)] + [InlineData("1977-01-01", 100000, 0)] + public void Calculate_Pension_Supplement( + string birthdateAsString, decimal finalRetirementCapitalTotal, decimal expectedPensionSupplement) + { + // given + DateTime birthdate = DateTime.Parse(birthdateAsString, CultureInfo.InvariantCulture); + + // when + decimal result = pensionSupplementCalculator.CalculatePensionSupplement(birthdate, finalRetirementCapitalTotal); + + outputHelper.WriteLine($"Calculated pension: {result}"); + + // assert the result with expected pension supplement within a tolerance of 1 + Assert.Equal(expectedPensionSupplement, result, 1); + } +} diff --git a/src/BvgCalculator.Tests/Projection/BvgSavingsProjectionTests.cs b/src/BvgCalculator.Tests/Projection/BvgSavingsProjectionTests.cs new file mode 100644 index 00000000..e8bf2a24 --- /dev/null +++ b/src/BvgCalculator.Tests/Projection/BvgSavingsProjectionTests.cs @@ -0,0 +1,62 @@ +using System; +using Application.Bvg; +using Application.Extensions; +using Domain.Models.Bvg; +using Snapshooter.Xunit; +using Xunit; + +namespace BvgCalculator.Tests.Projection; + +[Trait("Savings Process Projection Calculator", "BVG Revision")] +public class BvgSavingsProjectionTests +{ + const decimal ProjectionInterestRate = 0.0125m; + readonly DateTime startOfBvgRevision = new(2026, 1, 1); + + [Fact(DisplayName = "Single Savings Process Projection Table")] + public void SavingsProcessTable_WithSampleInputs_ReturnsExpectedResults() + { + // Arrange + DateTime dateOfBirth = new(1969, 3, 17); + + DateTime dateOfRetirement = new(2034, 4, 1); + TechnicalAge retirementAge = (65, 0); + TechnicalAge finalAge = (65, 0); + var yearOfBeginSavingsProcess = 2025; + decimal beginOfRetirementCapital = 11245.5M; + + ISavingsProcessProjectionCalculator calculator = new SingleSavingsProcessProjectionCalculator(); + + // Act + var actualResult = calculator.ProjectionTable( + ProjectionInterestRate, + dateOfRetirement, + dateOfRetirement, + retirementAge, + finalAge, + yearOfBeginSavingsProcess, + beginOfRetirementCapital, + GetRetirementCredit(dateOfBirth)); + + // Assert + Assert.NotNull(actualResult); + Snapshot.Match(actualResult); + } + + private Func GetRetirementCredit(DateTime dateOfBirth) + { + var diff = startOfBvgRevision.Subtract(dateOfBirth.GetBirthdateTechnical()); + + // calculate the number of years and months from diff + var years = diff.Days / 365; + var months = diff.Days % 365 / 30; + + return technicalAge => + { + var beforeRevision = 11245.5M; + var revision = 9878.4M; + + return technicalAge <= (years, months) ? beforeRevision : revision; + }; + } +} diff --git a/src/BvgCalculator.Tests/Projection/PensionPlanSavingsProjectionTests.cs b/src/BvgCalculator.Tests/Projection/PensionPlanSavingsProjectionTests.cs new file mode 100644 index 00000000..0421745f --- /dev/null +++ b/src/BvgCalculator.Tests/Projection/PensionPlanSavingsProjectionTests.cs @@ -0,0 +1,72 @@ +using System; +using System.Globalization; +using System.Linq; +using Application.Bvg; +using Application.Bvg.Models; +using Domain.Enums; +using Domain.Models.Bvg; +using Xunit; +using Xunit.Abstractions; + +namespace BvgCalculator.Tests.Projection; + +[Trait("Pension Plan", "Savings Projection Calculator")] +public class PensionPlanSavingsProjectionTests(ITestOutputHelper outputHelper) +{ + const decimal Deviation = 10; + + private readonly SingleSavingsProcessProjectionCalculator calculator = new(); + private readonly BvgRetirementDateCalculator retirementDateCalculator = new(); + + [Theory] + [InlineData("1968-06-12", 1, 5090, 0.0125, 2024, 32136, 45344)] + [InlineData("1968-06-12", 1, 5090, 0.02, 2024, 75642.55, 100788)] + [InlineData("1969-03-17", 2, 192967, 0.01, 2024, 721065.05, 1171484)] + public void ProjectionTableTheory( + string dateOfBirthAsString, + int genderCode, + decimal insuredSalary, + decimal projectionInterestRate, + int yearOfBeginProjection, + decimal beginOfRetirementCapital, + decimal expectedFinalRetirementCapital) + { + // Arrange + Gender gender = (Gender)genderCode; + DateTime dateOfBirth = DateTime.Parse(dateOfBirthAsString, CultureInfo.InvariantCulture); + DateTime dateOfRetirement = retirementDateCalculator.DateOfRetirement(gender, dateOfBirth); + TechnicalAge retirementAge = retirementDateCalculator.RetirementAge(gender, dateOfBirth); + TechnicalAge finalAge = retirementAge; + + // Act + RetirementSavingsProcessResult[] projections = calculator.ProjectionTable( + projectionInterestRate, + dateOfRetirement, + dateOfRetirement, + retirementAge, + finalAge, + yearOfBeginProjection, + beginOfRetirementCapital, + RetirementCreditSelector(insuredSalary)); + + decimal? result = projections.SingleOrDefault(item => item.IsRetirementDate)?.RetirementCapital; + + // Assert + Assert.NotNull(result); + Assert.InRange(result.Value, expectedFinalRetirementCapital - Deviation, expectedFinalRetirementCapital + Deviation); + outputHelper.WriteLine($"Expected vs Actual: {expectedFinalRetirementCapital}, Actual: {result}"); + } + + private Func RetirementCreditSelector(decimal insuredSalary) + { + decimal retirementCreditFactor = 0.18M; + return technicalAge => + { + return technicalAge switch + { + _ when technicalAge <= (65, 0) => insuredSalary * retirementCreditFactor, + _ => 0 + }; + }; + } +} diff --git a/src/BvgCalculator.Tests/Projection/__snapshots__/BvgSavingsProjectionTests.SavingsProcessTable_WithSampleInputs_ReturnsExpectedResults.snap b/src/BvgCalculator.Tests/Projection/__snapshots__/BvgSavingsProjectionTests.SavingsProcessTable_WithSampleInputs_ReturnsExpectedResults.snap new file mode 100644 index 00000000..258515ac --- /dev/null +++ b/src/BvgCalculator.Tests/Projection/__snapshots__/BvgSavingsProjectionTests.SavingsProcessTable_WithSampleInputs_ReturnsExpectedResults.snap @@ -0,0 +1,1906 @@ +[ + { + "DateOfCalculation": "2025-01-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 55, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 11245.5, + "RetirementCapitalWithoutInterest": 11245.5, + "RetirementCapital": 11245.5, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-02-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 55, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 937.1249999999999999999999996, + "RetirementCapitalWithoutInterest": 12182.625000000000000000000000, + "RetirementCapital": 12194.339062500000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-03-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 55, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1874.2500000000000000000000004, + "RetirementCapitalWithoutInterest": 13119.750000000000000000000000, + "RetirementCapital": 13143.178125000000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-04-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2811.375, + "RetirementCapitalWithoutInterest": 14056.875, + "RetirementCapital": 14092.0171875, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2025-05-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3748.4999999999999999999999996, + "RetirementCapitalWithoutInterest": 14994.000000000000000000000000, + "RetirementCapital": 15040.856250000000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-06-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4685.6250000000000000000000004, + "RetirementCapitalWithoutInterest": 15931.125000000000000000000000, + "RetirementCapital": 15989.695312500000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-07-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5622.75, + "RetirementCapitalWithoutInterest": 16868.25, + "RetirementCapital": 16938.534375, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-08-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6559.8749999999999999999999996, + "RetirementCapitalWithoutInterest": 17805.375000000000000000000000, + "RetirementCapital": 17887.373437500000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-09-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7497.0000000000000000000000004, + "RetirementCapitalWithoutInterest": 18742.500000000000000000000000, + "RetirementCapital": 18836.212500000000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-10-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8434.125, + "RetirementCapitalWithoutInterest": 19679.625, + "RetirementCapital": 19785.0515625, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-11-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9371.250000000000000000000000, + "RetirementCapitalWithoutInterest": 20616.750000000000000000000000, + "RetirementCapital": 20733.890625000000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-12-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 10308.375000000000000000000000, + "RetirementCapitalWithoutInterest": 21553.875000000000000000000000, + "RetirementCapital": 21682.729687500000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-01-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 56, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 11245.5, + "RetirementCapitalWithoutInterest": 22491.0, + "RetirementCapital": 22631.56875, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-02-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 56, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 937.1249999999999999999999996, + "RetirementCapitalWithoutInterest": 23428.125000000000000000000000, + "RetirementCapital": 23592.268300781250000000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-03-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 56, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 24137.400000000000000000000000, + "RetirementCapital": 24325.117851562499999999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-04-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 24960.600, + "RetirementCapital": 25171.89240234375, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2026-05-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 25783.800000000000000000000000, + "RetirementCapital": 26018.666953125000000000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-06-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 26607.000000000000000000000000, + "RetirementCapital": 26865.441503906249999999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-07-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 27430.20, + "RetirementCapital": 27712.2160546875, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-08-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 28253.400000000000000000000000, + "RetirementCapital": 28558.990605468750000000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-09-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 29076.600000000000000000000000, + "RetirementCapital": 29405.765156249999999999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-10-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 29899.800, + "RetirementCapital": 30252.53970703125, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-11-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 30723.000000000000000000000000, + "RetirementCapital": 31099.314257812500000000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-12-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 31546.200000000000000000000000, + "RetirementCapital": 31946.088808593749999999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-01-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 57, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 32369.4, + "RetirementCapital": 32792.863359375, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-02-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 57, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 33192.600000000000000000000000, + "RetirementCapital": 33650.222592041015625000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-03-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 57, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 34015.800000000000000000000000, + "RetirementCapital": 34507.581824707031249999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-04-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 34839.000, + "RetirementCapital": 35364.941057373046875, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2027-05-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 35662.200000000000000000000000, + "RetirementCapital": 36222.300290039062500000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-06-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 36485.400000000000000000000000, + "RetirementCapital": 37079.659522705078124999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-07-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 37308.60, + "RetirementCapital": 37937.01875537109375, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-08-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 38131.800000000000000000000000, + "RetirementCapital": 38794.377988037109375000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-09-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 38955.000000000000000000000000, + "RetirementCapital": 39651.737220703124999999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-10-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 39778.200, + "RetirementCapital": 40509.096453369140625, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-11-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 40601.400000000000000000000000, + "RetirementCapital": 41366.455686035156250000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-12-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 41424.600000000000000000000000, + "RetirementCapital": 42223.814918701171874999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-01-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 58, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 42247.8, + "RetirementCapital": 43081.1741513671875, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-02-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 58, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 43071.000000000000000000000000, + "RetirementCapital": 43949.250374441528320312500001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-03-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 58, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 43894.200000000000000000000000, + "RetirementCapital": 44817.326597515869140624999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-04-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 44717.400, + "RetirementCapital": 45685.4028205902099609375, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2028-05-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 45540.600000000000000000000000, + "RetirementCapital": 46553.479043664550781250000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-06-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 46363.800000000000000000000000, + "RetirementCapital": 47421.555266738891601562499999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-07-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 47187.00, + "RetirementCapital": 48289.631489813232421875, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-08-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 48010.200000000000000000000000, + "RetirementCapital": 49157.707712887573242187500001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-09-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 48833.400000000000000000000000, + "RetirementCapital": 50025.783935961914062499999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-10-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 49656.600, + "RetirementCapital": 50893.8601590362548828125, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-11-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 50479.800000000000000000000000, + "RetirementCapital": 51761.936382110595703125000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-12-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 51303.000000000000000000000000, + "RetirementCapital": 52630.012605184936523437499999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-01-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 59, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 52126.2, + "RetirementCapital": 53498.08882825927734375, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-02-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 59, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 52949.400000000000000000000000, + "RetirementCapital": 54377.016004122047424316406252, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-03-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 59, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 53772.600000000000000000000000, + "RetirementCapital": 55255.943179984817504882812498, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-04-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 54595.800, + "RetirementCapital": 56134.87035584758758544921875, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2029-05-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 55419.000000000000000000000000, + "RetirementCapital": 57013.797531710357666015625002, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-06-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 56242.200000000000000000000000, + "RetirementCapital": 57892.724707573127746582031248, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-07-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 57065.40, + "RetirementCapital": 58771.6518834358978271484375, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-08-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 57888.600000000000000000000000, + "RetirementCapital": 59650.579059298667907714843752, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-09-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 58711.800000000000000000000000, + "RetirementCapital": 60529.506235161437988281249998, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-10-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 59535.000, + "RetirementCapital": 61408.43341102420806884765625, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-11-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 60358.200000000000000000000000, + "RetirementCapital": 62287.360586886978149414062502, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-12-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 61181.400000000000000000000000, + "RetirementCapital": 63166.287762749748229980468748, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-01-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 60, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 62004.6, + "RetirementCapital": 64045.214938612518310546875, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-02-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 60, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 62827.800000000000000000000000, + "RetirementCapital": 64935.128704173573017120361330, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-03-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 60, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 63651.000000000000000000000000, + "RetirementCapital": 65825.042469734627723693847654, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-04-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 64474.200, + "RetirementCapital": 66714.956235295682430267333984, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2030-05-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 65297.400000000000000000000000, + "RetirementCapital": 67604.870000856737136840820315, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-06-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 66120.600000000000000000000000, + "RetirementCapital": 68494.783766417791843414306638, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-07-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 66943.80, + "RetirementCapital": 69384.697531978846549987792969, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-08-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 67767.000000000000000000000000, + "RetirementCapital": 70274.611297539901256561279299, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-09-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 68590.200000000000000000000000, + "RetirementCapital": 71164.525063100955963134765623, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-10-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 69413.400, + "RetirementCapital": 72054.438828662010669708251953, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-11-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 70236.600000000000000000000000, + "RetirementCapital": 72944.352594223065376281738283, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-12-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 71059.800000000000000000000000, + "RetirementCapital": 73834.266359784120082855224607, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-01-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 61, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 71883.0, + "RetirementCapital": 74724.180125345174789428710938, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-02-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 61, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 72706.200000000000000000000000, + "RetirementCapital": 75625.217812975742679834365848, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-03-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 61, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 73529.400000000000000000000000, + "RetirementCapital": 76526.255500606310570240020750, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-04-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 74352.600, + "RetirementCapital": 77427.293188236878460645675660, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2031-05-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 75175.800000000000000000000000, + "RetirementCapital": 78328.330875867446351051330569, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-06-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 75999.000000000000000000000000, + "RetirementCapital": 79229.36856349801424145698547, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-07-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 76822.20, + "RetirementCapital": 80130.40625112858213186264038, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-08-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 77645.400000000000000000000000, + "RetirementCapital": 81031.44393875915002226829529, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-09-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 78468.600000000000000000000000, + "RetirementCapital": 81932.48162638971791267395019, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-10-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 79291.800, + "RetirementCapital": 82833.51931402028580307960510, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-11-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 80115.00000000000000000000000, + "RetirementCapital": 83734.55700165085369348526001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-12-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 80938.20000000000000000000000, + "RetirementCapital": 84635.59468928142158389091492, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-01-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 62, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 81761.4, + "RetirementCapital": 85536.63237691198947429656982, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-02-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 62, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 82584.60000000000000000000000, + "RetirementCapital": 86448.93303563793946333229542, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-03-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 62, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 83407.80000000000000000000000, + "RetirementCapital": 87361.23369436388945236802100, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-04-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 84231.000, + "RetirementCapital": 88273.53435308983944140374660, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2032-05-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 85054.20000000000000000000000, + "RetirementCapital": 89185.83501181578943043947220, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-06-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 85877.40000000000000000000000, + "RetirementCapital": 90098.13567054173941947519778, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-07-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 86700.60, + "RetirementCapital": 91010.43632926768940851092338, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-08-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 87523.80000000000000000000000, + "RetirementCapital": 91922.73698799363939754664898, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-09-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 88347.00000000000000000000000, + "RetirementCapital": 92835.03764671958938658237457, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-10-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 89170.200, + "RetirementCapital": 93747.33830544553937561810016, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-11-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 89993.40000000000000000000000, + "RetirementCapital": 94659.63896417148936465382576, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-12-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 90816.60000000000000000000000, + "RetirementCapital": 95571.93962289743935368955135, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-01-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 63, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 91639.8, + "RetirementCapital": 96484.24028162338934272527694, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-02-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 63, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 92463.00000000000000000000000, + "RetirementCapital": 97407.94469858341370662394911, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-03-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 63, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 93286.20000000000000000000000, + "RetirementCapital": 98331.64911554343807052262126, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-04-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 94109.400, + "RetirementCapital": 99255.35353250346243442129343, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2033-05-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 94932.60000000000000000000000, + "RetirementCapital": 100179.05794946348679831996560, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-06-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 95755.80000000000000000000000, + "RetirementCapital": 101102.76236642351116221863775, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-07-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 96579.00, + "RetirementCapital": 102026.46678338353552611730992, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-08-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 97402.20000000000000000000000, + "RetirementCapital": 102950.17120034355989001598209, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-09-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 98225.40000000000000000000000, + "RetirementCapital": 103873.87561730358425391465424, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-10-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 99048.600, + "RetirementCapital": 104797.58003426360861781332641, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-11-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 99871.80000000000000000000000, + "RetirementCapital": 105721.28445122363298171199858, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-12-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 100695.00000000000000000000000, + "RetirementCapital": 106644.98886818365734561067074, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-01-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 64, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 101518.2, + "RetirementCapital": 107568.69328514368170950934290, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-02-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 64, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 102341.40000000000000000000000, + "RetirementCapital": 108503.94400731570637795674847, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-03-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 64, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 103164.60000000000000000000000, + "RetirementCapital": 109439.19472948773104640415403, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-04-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 65, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 103987.800, + "RetirementCapital": 110374.44545165975571485155960, + "IsRetirementDate": true, + "IsEndOfSavings": true, + "IsFullYear": false, + "IsFullAge": true + } +] diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.Calculate_Insured_Salaries_For_BVG_Maximum.snap b/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.Calculate_Insured_Salaries_For_BVG_Maximum.snap new file mode 100644 index 00000000..ae0db67f --- /dev/null +++ b/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.Calculate_Insured_Salaries_For_BVG_Maximum.snap @@ -0,0 +1,98 @@ +[ + { + "Age": { + "Years": 54, + "Months": 0 + }, + "Date": "2024-01-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 55, + "Months": 0 + }, + "Date": "2025-01-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 56, + "Months": 0 + }, + "Date": "2026-01-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 57, + "Months": 0 + }, + "Date": "2027-01-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 58, + "Months": 0 + }, + "Date": "2028-01-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 59, + "Months": 0 + }, + "Date": "2029-01-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 60, + "Months": 0 + }, + "Date": "2030-01-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 61, + "Months": 0 + }, + "Date": "2031-01-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 62, + "Months": 0 + }, + "Date": "2032-01-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 63, + "Months": 0 + }, + "Date": "2033-01-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 64, + "Months": 0 + }, + "Date": "2034-01-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 65, + "Months": 0 + }, + "Date": "2035-01-01T00:00:00", + "Value": 62475.00 + } +] diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.Calculate_Retirement_Credit_Factors_Array.snap b/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.Calculate_Retirement_Credit_Factors_Array.snap new file mode 100644 index 00000000..46be10a7 --- /dev/null +++ b/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.Calculate_Retirement_Credit_Factors_Array.snap @@ -0,0 +1,370 @@ +[ + { + "Age": { + "Years": 24, + "Months": 0 + }, + "Date": "1994-01-01T00:00:00", + "Value": 0.07 + }, + { + "Age": { + "Years": 25, + "Months": 0 + }, + "Date": "1995-01-01T00:00:00", + "Value": 0.07 + }, + { + "Age": { + "Years": 26, + "Months": 0 + }, + "Date": "1996-01-01T00:00:00", + "Value": 0.07 + }, + { + "Age": { + "Years": 27, + "Months": 0 + }, + "Date": "1997-01-01T00:00:00", + "Value": 0.07 + }, + { + "Age": { + "Years": 28, + "Months": 0 + }, + "Date": "1998-01-01T00:00:00", + "Value": 0.07 + }, + { + "Age": { + "Years": 29, + "Months": 0 + }, + "Date": "1999-01-01T00:00:00", + "Value": 0.07 + }, + { + "Age": { + "Years": 30, + "Months": 0 + }, + "Date": "2000-01-01T00:00:00", + "Value": 0.07 + }, + { + "Age": { + "Years": 31, + "Months": 0 + }, + "Date": "2001-01-01T00:00:00", + "Value": 0.07 + }, + { + "Age": { + "Years": 32, + "Months": 0 + }, + "Date": "2002-01-01T00:00:00", + "Value": 0.07 + }, + { + "Age": { + "Years": 33, + "Months": 0 + }, + "Date": "2003-01-01T00:00:00", + "Value": 0.07 + }, + { + "Age": { + "Years": 34, + "Months": 0 + }, + "Date": "2004-01-01T00:00:00", + "Value": 0.10 + }, + { + "Age": { + "Years": 35, + "Months": 0 + }, + "Date": "2005-01-01T00:00:00", + "Value": 0.10 + }, + { + "Age": { + "Years": 36, + "Months": 0 + }, + "Date": "2006-01-01T00:00:00", + "Value": 0.10 + }, + { + "Age": { + "Years": 37, + "Months": 0 + }, + "Date": "2007-01-01T00:00:00", + "Value": 0.10 + }, + { + "Age": { + "Years": 38, + "Months": 0 + }, + "Date": "2008-01-01T00:00:00", + "Value": 0.10 + }, + { + "Age": { + "Years": 39, + "Months": 0 + }, + "Date": "2009-01-01T00:00:00", + "Value": 0.10 + }, + { + "Age": { + "Years": 40, + "Months": 0 + }, + "Date": "2010-01-01T00:00:00", + "Value": 0.10 + }, + { + "Age": { + "Years": 41, + "Months": 0 + }, + "Date": "2011-01-01T00:00:00", + "Value": 0.10 + }, + { + "Age": { + "Years": 42, + "Months": 0 + }, + "Date": "2012-01-01T00:00:00", + "Value": 0.10 + }, + { + "Age": { + "Years": 43, + "Months": 0 + }, + "Date": "2013-01-01T00:00:00", + "Value": 0.10 + }, + { + "Age": { + "Years": 44, + "Months": 0 + }, + "Date": "2014-01-01T00:00:00", + "Value": 0.15 + }, + { + "Age": { + "Years": 45, + "Months": 0 + }, + "Date": "2015-01-01T00:00:00", + "Value": 0.15 + }, + { + "Age": { + "Years": 46, + "Months": 0 + }, + "Date": "2016-01-01T00:00:00", + "Value": 0.15 + }, + { + "Age": { + "Years": 47, + "Months": 0 + }, + "Date": "2017-01-01T00:00:00", + "Value": 0.15 + }, + { + "Age": { + "Years": 48, + "Months": 0 + }, + "Date": "2018-01-01T00:00:00", + "Value": 0.15 + }, + { + "Age": { + "Years": 49, + "Months": 0 + }, + "Date": "2019-01-01T00:00:00", + "Value": 0.15 + }, + { + "Age": { + "Years": 50, + "Months": 0 + }, + "Date": "2020-01-01T00:00:00", + "Value": 0.15 + }, + { + "Age": { + "Years": 51, + "Months": 0 + }, + "Date": "2021-01-01T00:00:00", + "Value": 0.15 + }, + { + "Age": { + "Years": 52, + "Months": 0 + }, + "Date": "2022-01-01T00:00:00", + "Value": 0.15 + }, + { + "Age": { + "Years": 53, + "Months": 0 + }, + "Date": "2023-01-01T00:00:00", + "Value": 0.15 + }, + { + "Age": { + "Years": 54, + "Months": 0 + }, + "Date": "2024-01-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 55, + "Months": 0 + }, + "Date": "2025-01-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 56, + "Months": 0 + }, + "Date": "2026-01-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 57, + "Months": 0 + }, + "Date": "2027-01-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 58, + "Months": 0 + }, + "Date": "2028-01-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 59, + "Months": 0 + }, + "Date": "2029-01-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 60, + "Months": 0 + }, + "Date": "2030-01-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 61, + "Months": 0 + }, + "Date": "2031-01-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 62, + "Months": 0 + }, + "Date": "2032-01-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 63, + "Months": 0 + }, + "Date": "2033-01-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 64, + "Months": 0 + }, + "Date": "2034-01-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 65, + "Months": 0 + }, + "Date": "2035-01-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 66, + "Months": 0 + }, + "Date": "2036-01-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 67, + "Months": 0 + }, + "Date": "2037-01-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 68, + "Months": 0 + }, + "Date": "2038-01-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 69, + "Months": 0 + }, + "Date": "2039-01-01T00:00:00", + "Value": 0.0 + } +] diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.Calculate_Retirement_Credits_Array.snap b/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.Calculate_Retirement_Credits_Array.snap new file mode 100644 index 00000000..d330a2c8 --- /dev/null +++ b/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.Calculate_Retirement_Credits_Array.snap @@ -0,0 +1,98 @@ +[ + { + "Age": { + "Years": 54, + "Months": 0 + }, + "Date": "2024-01-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 55, + "Months": 0 + }, + "Date": "2025-01-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 56, + "Months": 0 + }, + "Date": "2026-01-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 57, + "Months": 0 + }, + "Date": "2027-01-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 58, + "Months": 0 + }, + "Date": "2028-01-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 59, + "Months": 0 + }, + "Date": "2029-01-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 60, + "Months": 0 + }, + "Date": "2030-01-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 61, + "Months": 0 + }, + "Date": "2031-01-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 62, + "Months": 0 + }, + "Date": "2032-01-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 63, + "Months": 0 + }, + "Date": "2033-01-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 64, + "Months": 0 + }, + "Date": "2034-01-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 65, + "Months": 0 + }, + "Date": "2035-01-01T00:00:00", + "Value": 0.00 + } +] diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.ShouldCalculateDefaultResult.snap b/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.ShouldCalculateDefaultResult.snap index 726af218..ffae91ce 100644 --- a/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.ShouldCalculateDefaultResult.snap +++ b/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.ShouldCalculateDefaultResult.snap @@ -15,150 +15,87 @@ "RetirementCreditSequence": [ { "Age": 45, - "AmountRaw": 9065.2500, - "AmountRounded10": 9065.30, - "AmountRounded60": 9065.40, - "AmountRounded100": 9065.0 + "AmountRaw": 9065.2500 }, { "Age": 46, - "AmountRaw": 9065.2500, - "AmountRounded10": 9065.30, - "AmountRounded60": 9065.40, - "AmountRounded100": 9065.0 + "AmountRaw": 9065.2500 }, { "Age": 47, - "AmountRaw": 9065.2500, - "AmountRounded10": 9065.30, - "AmountRounded60": 9065.40, - "AmountRounded100": 9065.0 + "AmountRaw": 9065.2500 }, { "Age": 48, - "AmountRaw": 9065.2500, - "AmountRounded10": 9065.30, - "AmountRounded60": 9065.40, - "AmountRounded100": 9065.0 + "AmountRaw": 9065.2500 }, { "Age": 49, - "AmountRaw": 9065.2500, - "AmountRounded10": 9065.30, - "AmountRounded60": 9065.40, - "AmountRounded100": 9065.0 + "AmountRaw": 9065.2500 }, { "Age": 50, - "AmountRaw": 9065.2500, - "AmountRounded10": 9065.30, - "AmountRounded60": 9065.40, - "AmountRounded100": 9065.0 + "AmountRaw": 9065.2500 }, { "Age": 51, - "AmountRaw": 9065.2500, - "AmountRounded10": 9065.30, - "AmountRounded60": 9065.40, - "AmountRounded100": 9065.0 + "AmountRaw": 9065.2500 }, { "Age": 52, - "AmountRaw": 9065.2500, - "AmountRounded10": 9065.30, - "AmountRounded60": 9065.40, - "AmountRounded100": 9065.0 + "AmountRaw": 9065.2500 }, { "Age": 53, - "AmountRaw": 9065.2500, - "AmountRounded10": 9065.30, - "AmountRounded60": 9065.40, - "AmountRounded100": 9065.0 + "AmountRaw": 9065.2500 }, { "Age": 54, - "AmountRaw": 9065.2500, - "AmountRounded10": 9065.30, - "AmountRounded60": 9065.40, - "AmountRounded100": 9065.0 + "AmountRaw": 9065.2500 }, { "Age": 55, - "AmountRaw": 10878.3000, - "AmountRounded10": 10878.30, - "AmountRounded60": 10878.60, - "AmountRounded100": 10878.0 + "AmountRaw": 10878.3000 }, { "Age": 56, - "AmountRaw": 10878.3000, - "AmountRounded10": 10878.30, - "AmountRounded60": 10878.60, - "AmountRounded100": 10878.0 + "AmountRaw": 10878.3000 }, { "Age": 57, - "AmountRaw": 10878.3000, - "AmountRounded10": 10878.30, - "AmountRounded60": 10878.60, - "AmountRounded100": 10878.0 + "AmountRaw": 10878.3000 }, { "Age": 58, - "AmountRaw": 10878.3000, - "AmountRounded10": 10878.30, - "AmountRounded60": 10878.60, - "AmountRounded100": 10878.0 + "AmountRaw": 10878.3000 }, { "Age": 59, - "AmountRaw": 10878.3000, - "AmountRounded10": 10878.30, - "AmountRounded60": 10878.60, - "AmountRounded100": 10878.0 + "AmountRaw": 10878.3000 }, { "Age": 60, - "AmountRaw": 10878.3000, - "AmountRounded10": 10878.30, - "AmountRounded60": 10878.60, - "AmountRounded100": 10878.0 + "AmountRaw": 10878.3000 }, { "Age": 61, - "AmountRaw": 10878.3000, - "AmountRounded10": 10878.30, - "AmountRounded60": 10878.60, - "AmountRounded100": 10878.0 + "AmountRaw": 10878.3000 }, { "Age": 62, - "AmountRaw": 10878.3000, - "AmountRounded10": 10878.30, - "AmountRounded60": 10878.60, - "AmountRounded100": 10878.0 + "AmountRaw": 10878.3000 }, { "Age": 63, - "AmountRaw": 10878.3000, - "AmountRounded10": 10878.30, - "AmountRounded60": 10878.60, - "AmountRounded100": 10878.0 + "AmountRaw": 10878.3000 }, { "Age": 64, - "AmountRaw": 10878.3000, - "AmountRounded10": 10878.30, - "AmountRounded60": 10878.60, - "AmountRounded100": 10878.0 + "AmountRaw": 10878.3000 }, { "Age": 65, - "AmountRaw": 10878.3000, - "AmountRounded10": 10878.30, - "AmountRounded60": 10878.60, - "AmountRounded100": 10878.0 + "AmountRaw": 10878.3000 } ], "RetirementCapitalSequence": [ diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.ShouldCalculateResultWhenRetiring.snap b/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.ShouldCalculateResultWhenRetiring.snap index 99a783ba..d701c335 100644 --- a/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.ShouldCalculateResultWhenRetiring.snap +++ b/src/BvgCalculator.Tests/__snapshots__/BvgCalculatorTests.ShouldCalculateResultWhenRetiring.snap @@ -15,10 +15,7 @@ "RetirementCreditSequence": [ { "Age": 65, - "AmountRaw": 10820.7000, - "AmountRounded10": 10820.70, - "AmountRounded60": 10821.00, - "AmountRounded100": 10821.0 + "AmountRaw": 10820.7000 } ], "RetirementCapitalSequence": [ diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Ahv_Transition_Generation.snap b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Ahv_Transition_Generation.snap new file mode 100644 index 00000000..c74d5973 --- /dev/null +++ b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Ahv_Transition_Generation.snap @@ -0,0 +1,265 @@ +{ + "DateOfRetirement": "2026-10-01T00:00:00", + "EffectiveSalary": 100000.0, + "InsuredSalary": 62475.00, + "RetirementCredit": 11245.5000, + "RetirementCreditFactor": 0.18, + "RetirementCapitalEndOfYear": 11245.5, + "FinalRetirementCapital": 30252.53970703125, + "FinalRetirementCapitalWithoutInterest": 29899.800000, + "RetirementPension": 1815.1523824218750, + "DisabilityPension": 1793.98800000, + "PartnerPension": 1076.392800000, + "OrphanPension": 358.797600000, + "ChildPensionForDisabled": 358.797600000, + "RetirementCreditSequence": [ + { + "Age": 61, + "AmountRaw": 11245.5000 + }, + { + "Age": 61, + "AmountRaw": 11245.5000 + }, + { + "Age": 61, + "AmountRaw": 11245.5000 + }, + { + "Age": 62, + "AmountRaw": 11245.5000 + }, + { + "Age": 62, + "AmountRaw": 11245.5000 + }, + { + "Age": 62, + "AmountRaw": 11245.5000 + }, + { + "Age": 62, + "AmountRaw": 11245.5000 + }, + { + "Age": 62, + "AmountRaw": 11245.5000 + }, + { + "Age": 62, + "AmountRaw": 11245.5000 + }, + { + "Age": 62, + "AmountRaw": 11245.5000 + }, + { + "Age": 62, + "AmountRaw": 11245.5000 + }, + { + "Age": 62, + "AmountRaw": 11245.5000 + }, + { + "Age": 62, + "AmountRaw": 11245.5000 + }, + { + "Age": 62, + "AmountRaw": 11245.5000 + }, + { + "Age": 62, + "AmountRaw": 11245.5000 + }, + { + "Age": 63, + "AmountRaw": 11245.5000 + }, + { + "Age": 63, + "AmountRaw": 11245.5000 + }, + { + "Age": 63, + "AmountRaw": 11245.5000 + }, + { + "Age": 63, + "AmountRaw": 11245.5000 + }, + { + "Age": 63, + "AmountRaw": 11245.5000 + }, + { + "Age": 63, + "AmountRaw": 11245.5000 + }, + { + "Age": 63, + "AmountRaw": 11245.5000 + }, + { + "Age": 63, + "AmountRaw": 11245.5000 + }, + { + "Age": 63, + "AmountRaw": 11245.5000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + } + ], + "RetirementCapitalSequence": [ + { + "Date": "2025-01-01T00:00:00", + "Value": 11245.5, + "ValueWithoutInterest": 11245.5 + }, + { + "Date": "2025-02-01T00:00:00", + "Value": 12194.339062500000000000000000, + "ValueWithoutInterest": 12182.625000000000000000000000 + }, + { + "Date": "2025-03-01T00:00:00", + "Value": 13143.178125000000000000000000, + "ValueWithoutInterest": 13119.750000000000000000000000 + }, + { + "Date": "2025-04-01T00:00:00", + "Value": 14092.0171875, + "ValueWithoutInterest": 14056.875000 + }, + { + "Date": "2025-05-01T00:00:00", + "Value": 15040.856250000000000000000000, + "ValueWithoutInterest": 14994.000000000000000000000000 + }, + { + "Date": "2025-06-01T00:00:00", + "Value": 15989.695312500000000000000000, + "ValueWithoutInterest": 15931.125000000000000000000000 + }, + { + "Date": "2025-07-01T00:00:00", + "Value": 16938.534375, + "ValueWithoutInterest": 16868.25000 + }, + { + "Date": "2025-08-01T00:00:00", + "Value": 17887.373437500000000000000000, + "ValueWithoutInterest": 17805.375000000000000000000000 + }, + { + "Date": "2025-09-01T00:00:00", + "Value": 18836.212500000000000000000000, + "ValueWithoutInterest": 18742.500000000000000000000000 + }, + { + "Date": "2025-10-01T00:00:00", + "Value": 19785.0515625, + "ValueWithoutInterest": 19679.625000 + }, + { + "Date": "2025-11-01T00:00:00", + "Value": 20733.890625000000000000000000, + "ValueWithoutInterest": 20616.750000000000000000000000 + }, + { + "Date": "2025-12-01T00:00:00", + "Value": 21682.729687500000000000000000, + "ValueWithoutInterest": 21553.875000000000000000000000 + }, + { + "Date": "2026-01-01T00:00:00", + "Value": 22631.56875, + "ValueWithoutInterest": 22491.0000 + }, + { + "Date": "2026-02-01T00:00:00", + "Value": 23478.343300781250000000000001, + "ValueWithoutInterest": 23314.200000000000000000000000 + }, + { + "Date": "2026-03-01T00:00:00", + "Value": 24325.117851562499999999999999, + "ValueWithoutInterest": 24137.400000000000000000000000 + }, + { + "Date": "2026-04-01T00:00:00", + "Value": 25171.89240234375, + "ValueWithoutInterest": 24960.600000 + }, + { + "Date": "2026-05-01T00:00:00", + "Value": 26018.666953125000000000000001, + "ValueWithoutInterest": 25783.800000000000000000000000 + }, + { + "Date": "2026-06-01T00:00:00", + "Value": 26865.441503906249999999999999, + "ValueWithoutInterest": 26607.000000000000000000000000 + }, + { + "Date": "2026-07-01T00:00:00", + "Value": 27712.2160546875, + "ValueWithoutInterest": 27430.20000 + }, + { + "Date": "2026-08-01T00:00:00", + "Value": 28558.990605468750000000000001, + "ValueWithoutInterest": 28253.400000000000000000000000 + }, + { + "Date": "2026-09-01T00:00:00", + "Value": 29405.765156249999999999999999, + "ValueWithoutInterest": 29076.600000000000000000000000 + }, + { + "Date": "2026-10-01T00:00:00", + "Value": 30252.53970703125, + "ValueWithoutInterest": 29899.800000 + } + ] +} diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Insured_Salaries_Between_Threshold.snap b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Insured_Salaries_Between_Threshold.snap new file mode 100644 index 00000000..2bc4b3ef --- /dev/null +++ b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Insured_Salaries_Between_Threshold.snap @@ -0,0 +1,1514 @@ +[ + { + "Age": { + "Years": 49, + "Months": 4 + }, + "Date": "2024-01-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 49, + "Months": 5 + }, + "Date": "2024-02-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 49, + "Months": 6 + }, + "Date": "2024-03-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 49, + "Months": 7 + }, + "Date": "2024-04-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 49, + "Months": 8 + }, + "Date": "2024-05-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 49, + "Months": 9 + }, + "Date": "2024-06-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 49, + "Months": 10 + }, + "Date": "2024-07-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 49, + "Months": 11 + }, + "Date": "2024-08-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 50, + "Months": 0 + }, + "Date": "2024-09-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 50, + "Months": 1 + }, + "Date": "2024-10-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 50, + "Months": 2 + }, + "Date": "2024-11-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 50, + "Months": 3 + }, + "Date": "2024-12-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 50, + "Months": 4 + }, + "Date": "2025-01-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 50, + "Months": 5 + }, + "Date": "2025-02-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 50, + "Months": 6 + }, + "Date": "2025-03-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 50, + "Months": 7 + }, + "Date": "2025-04-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 50, + "Months": 8 + }, + "Date": "2025-05-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 50, + "Months": 9 + }, + "Date": "2025-06-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 50, + "Months": 10 + }, + "Date": "2025-07-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 50, + "Months": 11 + }, + "Date": "2025-08-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 51, + "Months": 0 + }, + "Date": "2025-09-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 51, + "Months": 1 + }, + "Date": "2025-10-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 51, + "Months": 2 + }, + "Date": "2025-11-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 51, + "Months": 3 + }, + "Date": "2025-12-01T00:00:00", + "Value": 0.00 + }, + { + "Age": { + "Years": 51, + "Months": 4 + }, + "Date": "2026-01-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 51, + "Months": 5 + }, + "Date": "2026-02-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 51, + "Months": 6 + }, + "Date": "2026-03-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 51, + "Months": 7 + }, + "Date": "2026-04-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 51, + "Months": 8 + }, + "Date": "2026-05-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 51, + "Months": 9 + }, + "Date": "2026-06-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 51, + "Months": 10 + }, + "Date": "2026-07-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 51, + "Months": 11 + }, + "Date": "2026-08-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 52, + "Months": 0 + }, + "Date": "2026-09-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 52, + "Months": 1 + }, + "Date": "2026-10-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 52, + "Months": 2 + }, + "Date": "2026-11-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 52, + "Months": 3 + }, + "Date": "2026-12-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 52, + "Months": 4 + }, + "Date": "2027-01-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 52, + "Months": 5 + }, + "Date": "2027-02-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 52, + "Months": 6 + }, + "Date": "2027-03-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 52, + "Months": 7 + }, + "Date": "2027-04-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 52, + "Months": 8 + }, + "Date": "2027-05-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 52, + "Months": 9 + }, + "Date": "2027-06-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 52, + "Months": 10 + }, + "Date": "2027-07-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 52, + "Months": 11 + }, + "Date": "2027-08-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 53, + "Months": 0 + }, + "Date": "2027-09-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 53, + "Months": 1 + }, + "Date": "2027-10-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 53, + "Months": 2 + }, + "Date": "2027-11-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 53, + "Months": 3 + }, + "Date": "2027-12-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 53, + "Months": 4 + }, + "Date": "2028-01-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 53, + "Months": 5 + }, + "Date": "2028-02-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 53, + "Months": 6 + }, + "Date": "2028-03-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 53, + "Months": 7 + }, + "Date": "2028-04-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 53, + "Months": 8 + }, + "Date": "2028-05-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 53, + "Months": 9 + }, + "Date": "2028-06-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 53, + "Months": 10 + }, + "Date": "2028-07-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 53, + "Months": 11 + }, + "Date": "2028-08-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 54, + "Months": 0 + }, + "Date": "2028-09-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 54, + "Months": 1 + }, + "Date": "2028-10-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 54, + "Months": 2 + }, + "Date": "2028-11-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 54, + "Months": 3 + }, + "Date": "2028-12-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 54, + "Months": 4 + }, + "Date": "2029-01-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 54, + "Months": 5 + }, + "Date": "2029-02-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 54, + "Months": 6 + }, + "Date": "2029-03-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 54, + "Months": 7 + }, + "Date": "2029-04-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 54, + "Months": 8 + }, + "Date": "2029-05-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 54, + "Months": 9 + }, + "Date": "2029-06-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 54, + "Months": 10 + }, + "Date": "2029-07-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 54, + "Months": 11 + }, + "Date": "2029-08-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 55, + "Months": 0 + }, + "Date": "2029-09-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 55, + "Months": 1 + }, + "Date": "2029-10-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 55, + "Months": 2 + }, + "Date": "2029-11-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 55, + "Months": 3 + }, + "Date": "2029-12-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 55, + "Months": 4 + }, + "Date": "2030-01-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 55, + "Months": 5 + }, + "Date": "2030-02-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 55, + "Months": 6 + }, + "Date": "2030-03-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 55, + "Months": 7 + }, + "Date": "2030-04-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 55, + "Months": 8 + }, + "Date": "2030-05-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 55, + "Months": 9 + }, + "Date": "2030-06-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 55, + "Months": 10 + }, + "Date": "2030-07-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 55, + "Months": 11 + }, + "Date": "2030-08-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 56, + "Months": 0 + }, + "Date": "2030-09-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 56, + "Months": 1 + }, + "Date": "2030-10-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 56, + "Months": 2 + }, + "Date": "2030-11-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 56, + "Months": 3 + }, + "Date": "2030-12-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 56, + "Months": 4 + }, + "Date": "2031-01-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 56, + "Months": 5 + }, + "Date": "2031-02-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 56, + "Months": 6 + }, + "Date": "2031-03-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 56, + "Months": 7 + }, + "Date": "2031-04-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 56, + "Months": 8 + }, + "Date": "2031-05-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 56, + "Months": 9 + }, + "Date": "2031-06-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 56, + "Months": 10 + }, + "Date": "2031-07-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 56, + "Months": 11 + }, + "Date": "2031-08-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 57, + "Months": 0 + }, + "Date": "2031-09-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 57, + "Months": 1 + }, + "Date": "2031-10-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 57, + "Months": 2 + }, + "Date": "2031-11-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 57, + "Months": 3 + }, + "Date": "2031-12-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 57, + "Months": 4 + }, + "Date": "2032-01-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 57, + "Months": 5 + }, + "Date": "2032-02-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 57, + "Months": 6 + }, + "Date": "2032-03-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 57, + "Months": 7 + }, + "Date": "2032-04-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 57, + "Months": 8 + }, + "Date": "2032-05-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 57, + "Months": 9 + }, + "Date": "2032-06-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 57, + "Months": 10 + }, + "Date": "2032-07-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 57, + "Months": 11 + }, + "Date": "2032-08-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 58, + "Months": 0 + }, + "Date": "2032-09-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 58, + "Months": 1 + }, + "Date": "2032-10-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 58, + "Months": 2 + }, + "Date": "2032-11-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 58, + "Months": 3 + }, + "Date": "2032-12-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 58, + "Months": 4 + }, + "Date": "2033-01-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 58, + "Months": 5 + }, + "Date": "2033-02-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 58, + "Months": 6 + }, + "Date": "2033-03-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 58, + "Months": 7 + }, + "Date": "2033-04-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 58, + "Months": 8 + }, + "Date": "2033-05-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 58, + "Months": 9 + }, + "Date": "2033-06-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 58, + "Months": 10 + }, + "Date": "2033-07-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 58, + "Months": 11 + }, + "Date": "2033-08-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 59, + "Months": 0 + }, + "Date": "2033-09-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 59, + "Months": 1 + }, + "Date": "2033-10-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 59, + "Months": 2 + }, + "Date": "2033-11-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 59, + "Months": 3 + }, + "Date": "2033-12-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 59, + "Months": 4 + }, + "Date": "2034-01-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 59, + "Months": 5 + }, + "Date": "2034-02-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 59, + "Months": 6 + }, + "Date": "2034-03-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 59, + "Months": 7 + }, + "Date": "2034-04-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 59, + "Months": 8 + }, + "Date": "2034-05-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 59, + "Months": 9 + }, + "Date": "2034-06-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 59, + "Months": 10 + }, + "Date": "2034-07-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 59, + "Months": 11 + }, + "Date": "2034-08-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 60, + "Months": 0 + }, + "Date": "2034-09-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 60, + "Months": 1 + }, + "Date": "2034-10-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 60, + "Months": 2 + }, + "Date": "2034-11-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 60, + "Months": 3 + }, + "Date": "2034-12-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 60, + "Months": 4 + }, + "Date": "2035-01-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 60, + "Months": 5 + }, + "Date": "2035-02-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 60, + "Months": 6 + }, + "Date": "2035-03-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 60, + "Months": 7 + }, + "Date": "2035-04-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 60, + "Months": 8 + }, + "Date": "2035-05-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 60, + "Months": 9 + }, + "Date": "2035-06-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 60, + "Months": 10 + }, + "Date": "2035-07-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 60, + "Months": 11 + }, + "Date": "2035-08-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 61, + "Months": 0 + }, + "Date": "2035-09-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 61, + "Months": 1 + }, + "Date": "2035-10-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 61, + "Months": 2 + }, + "Date": "2035-11-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 61, + "Months": 3 + }, + "Date": "2035-12-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 61, + "Months": 4 + }, + "Date": "2036-01-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 61, + "Months": 5 + }, + "Date": "2036-02-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 61, + "Months": 6 + }, + "Date": "2036-03-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 61, + "Months": 7 + }, + "Date": "2036-04-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 61, + "Months": 8 + }, + "Date": "2036-05-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 61, + "Months": 9 + }, + "Date": "2036-06-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 61, + "Months": 10 + }, + "Date": "2036-07-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 61, + "Months": 11 + }, + "Date": "2036-08-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 62, + "Months": 0 + }, + "Date": "2036-09-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 62, + "Months": 1 + }, + "Date": "2036-10-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 62, + "Months": 2 + }, + "Date": "2036-11-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 62, + "Months": 3 + }, + "Date": "2036-12-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 62, + "Months": 4 + }, + "Date": "2037-01-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 62, + "Months": 5 + }, + "Date": "2037-02-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 62, + "Months": 6 + }, + "Date": "2037-03-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 62, + "Months": 7 + }, + "Date": "2037-04-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 62, + "Months": 8 + }, + "Date": "2037-05-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 62, + "Months": 9 + }, + "Date": "2037-06-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 62, + "Months": 10 + }, + "Date": "2037-07-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 62, + "Months": 11 + }, + "Date": "2037-08-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 63, + "Months": 0 + }, + "Date": "2037-09-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 63, + "Months": 1 + }, + "Date": "2037-10-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 63, + "Months": 2 + }, + "Date": "2037-11-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 63, + "Months": 3 + }, + "Date": "2037-12-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 63, + "Months": 4 + }, + "Date": "2038-01-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 63, + "Months": 5 + }, + "Date": "2038-02-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 63, + "Months": 6 + }, + "Date": "2038-03-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 63, + "Months": 7 + }, + "Date": "2038-04-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 63, + "Months": 8 + }, + "Date": "2038-05-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 63, + "Months": 9 + }, + "Date": "2038-06-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 63, + "Months": 10 + }, + "Date": "2038-07-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 63, + "Months": 11 + }, + "Date": "2038-08-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 64, + "Months": 0 + }, + "Date": "2038-09-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 64, + "Months": 1 + }, + "Date": "2038-10-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 64, + "Months": 2 + }, + "Date": "2038-11-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 64, + "Months": 3 + }, + "Date": "2038-12-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 64, + "Months": 4 + }, + "Date": "2039-01-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 64, + "Months": 5 + }, + "Date": "2039-02-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 64, + "Months": 6 + }, + "Date": "2039-03-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 64, + "Months": 7 + }, + "Date": "2039-04-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 64, + "Months": 8 + }, + "Date": "2039-05-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 64, + "Months": 9 + }, + "Date": "2039-06-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 64, + "Months": 10 + }, + "Date": "2039-07-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 64, + "Months": 11 + }, + "Date": "2039-08-01T00:00:00", + "Value": 16000.00 + }, + { + "Age": { + "Years": 65, + "Months": 0 + }, + "Date": "2039-09-01T00:00:00", + "Value": 16000.00 + } +] diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Insured_Salaries_For_BVG_Maximum.snap b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Insured_Salaries_For_BVG_Maximum.snap new file mode 100644 index 00000000..9d3b38ed --- /dev/null +++ b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Insured_Salaries_For_BVG_Maximum.snap @@ -0,0 +1,1066 @@ +[ + { + "Age": { + "Years": 54, + "Months": 0 + }, + "Date": "2024-01-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 54, + "Months": 1 + }, + "Date": "2024-02-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 54, + "Months": 2 + }, + "Date": "2024-03-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 54, + "Months": 3 + }, + "Date": "2024-04-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 54, + "Months": 4 + }, + "Date": "2024-05-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 54, + "Months": 5 + }, + "Date": "2024-06-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 54, + "Months": 6 + }, + "Date": "2024-07-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 54, + "Months": 7 + }, + "Date": "2024-08-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 54, + "Months": 8 + }, + "Date": "2024-09-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 54, + "Months": 9 + }, + "Date": "2024-10-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 54, + "Months": 10 + }, + "Date": "2024-11-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 54, + "Months": 11 + }, + "Date": "2024-12-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 55, + "Months": 0 + }, + "Date": "2025-01-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 55, + "Months": 1 + }, + "Date": "2025-02-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 55, + "Months": 2 + }, + "Date": "2025-03-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 55, + "Months": 3 + }, + "Date": "2025-04-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 55, + "Months": 4 + }, + "Date": "2025-05-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 55, + "Months": 5 + }, + "Date": "2025-06-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 55, + "Months": 6 + }, + "Date": "2025-07-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 55, + "Months": 7 + }, + "Date": "2025-08-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 55, + "Months": 8 + }, + "Date": "2025-09-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 55, + "Months": 9 + }, + "Date": "2025-10-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 55, + "Months": 10 + }, + "Date": "2025-11-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 55, + "Months": 11 + }, + "Date": "2025-12-01T00:00:00", + "Value": 62475.00 + }, + { + "Age": { + "Years": 56, + "Months": 0 + }, + "Date": "2026-01-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 56, + "Months": 1 + }, + "Date": "2026-02-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 56, + "Months": 2 + }, + "Date": "2026-03-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 56, + "Months": 3 + }, + "Date": "2026-04-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 56, + "Months": 4 + }, + "Date": "2026-05-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 56, + "Months": 5 + }, + "Date": "2026-06-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 56, + "Months": 6 + }, + "Date": "2026-07-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 56, + "Months": 7 + }, + "Date": "2026-08-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 56, + "Months": 8 + }, + "Date": "2026-09-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 56, + "Months": 9 + }, + "Date": "2026-10-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 56, + "Months": 10 + }, + "Date": "2026-11-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 56, + "Months": 11 + }, + "Date": "2026-12-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 57, + "Months": 0 + }, + "Date": "2027-01-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 57, + "Months": 1 + }, + "Date": "2027-02-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 57, + "Months": 2 + }, + "Date": "2027-03-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 57, + "Months": 3 + }, + "Date": "2027-04-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 57, + "Months": 4 + }, + "Date": "2027-05-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 57, + "Months": 5 + }, + "Date": "2027-06-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 57, + "Months": 6 + }, + "Date": "2027-07-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 57, + "Months": 7 + }, + "Date": "2027-08-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 57, + "Months": 8 + }, + "Date": "2027-09-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 57, + "Months": 9 + }, + "Date": "2027-10-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 57, + "Months": 10 + }, + "Date": "2027-11-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 57, + "Months": 11 + }, + "Date": "2027-12-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 58, + "Months": 0 + }, + "Date": "2028-01-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 58, + "Months": 1 + }, + "Date": "2028-02-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 58, + "Months": 2 + }, + "Date": "2028-03-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 58, + "Months": 3 + }, + "Date": "2028-04-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 58, + "Months": 4 + }, + "Date": "2028-05-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 58, + "Months": 5 + }, + "Date": "2028-06-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 58, + "Months": 6 + }, + "Date": "2028-07-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 58, + "Months": 7 + }, + "Date": "2028-08-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 58, + "Months": 8 + }, + "Date": "2028-09-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 58, + "Months": 9 + }, + "Date": "2028-10-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 58, + "Months": 10 + }, + "Date": "2028-11-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 58, + "Months": 11 + }, + "Date": "2028-12-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 59, + "Months": 0 + }, + "Date": "2029-01-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 59, + "Months": 1 + }, + "Date": "2029-02-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 59, + "Months": 2 + }, + "Date": "2029-03-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 59, + "Months": 3 + }, + "Date": "2029-04-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 59, + "Months": 4 + }, + "Date": "2029-05-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 59, + "Months": 5 + }, + "Date": "2029-06-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 59, + "Months": 6 + }, + "Date": "2029-07-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 59, + "Months": 7 + }, + "Date": "2029-08-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 59, + "Months": 8 + }, + "Date": "2029-09-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 59, + "Months": 9 + }, + "Date": "2029-10-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 59, + "Months": 10 + }, + "Date": "2029-11-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 59, + "Months": 11 + }, + "Date": "2029-12-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 60, + "Months": 0 + }, + "Date": "2030-01-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 60, + "Months": 1 + }, + "Date": "2030-02-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 60, + "Months": 2 + }, + "Date": "2030-03-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 60, + "Months": 3 + }, + "Date": "2030-04-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 60, + "Months": 4 + }, + "Date": "2030-05-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 60, + "Months": 5 + }, + "Date": "2030-06-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 60, + "Months": 6 + }, + "Date": "2030-07-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 60, + "Months": 7 + }, + "Date": "2030-08-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 60, + "Months": 8 + }, + "Date": "2030-09-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 60, + "Months": 9 + }, + "Date": "2030-10-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 60, + "Months": 10 + }, + "Date": "2030-11-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 60, + "Months": 11 + }, + "Date": "2030-12-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 61, + "Months": 0 + }, + "Date": "2031-01-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 61, + "Months": 1 + }, + "Date": "2031-02-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 61, + "Months": 2 + }, + "Date": "2031-03-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 61, + "Months": 3 + }, + "Date": "2031-04-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 61, + "Months": 4 + }, + "Date": "2031-05-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 61, + "Months": 5 + }, + "Date": "2031-06-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 61, + "Months": 6 + }, + "Date": "2031-07-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 61, + "Months": 7 + }, + "Date": "2031-08-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 61, + "Months": 8 + }, + "Date": "2031-09-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 61, + "Months": 9 + }, + "Date": "2031-10-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 61, + "Months": 10 + }, + "Date": "2031-11-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 61, + "Months": 11 + }, + "Date": "2031-12-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 62, + "Months": 0 + }, + "Date": "2032-01-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 62, + "Months": 1 + }, + "Date": "2032-02-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 62, + "Months": 2 + }, + "Date": "2032-03-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 62, + "Months": 3 + }, + "Date": "2032-04-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 62, + "Months": 4 + }, + "Date": "2032-05-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 62, + "Months": 5 + }, + "Date": "2032-06-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 62, + "Months": 6 + }, + "Date": "2032-07-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 62, + "Months": 7 + }, + "Date": "2032-08-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 62, + "Months": 8 + }, + "Date": "2032-09-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 62, + "Months": 9 + }, + "Date": "2032-10-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 62, + "Months": 10 + }, + "Date": "2032-11-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 62, + "Months": 11 + }, + "Date": "2032-12-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 63, + "Months": 0 + }, + "Date": "2033-01-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 63, + "Months": 1 + }, + "Date": "2033-02-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 63, + "Months": 2 + }, + "Date": "2033-03-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 63, + "Months": 3 + }, + "Date": "2033-04-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 63, + "Months": 4 + }, + "Date": "2033-05-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 63, + "Months": 5 + }, + "Date": "2033-06-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 63, + "Months": 6 + }, + "Date": "2033-07-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 63, + "Months": 7 + }, + "Date": "2033-08-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 63, + "Months": 8 + }, + "Date": "2033-09-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 63, + "Months": 9 + }, + "Date": "2033-10-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 63, + "Months": 10 + }, + "Date": "2033-11-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 63, + "Months": 11 + }, + "Date": "2033-12-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 64, + "Months": 0 + }, + "Date": "2034-01-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 64, + "Months": 1 + }, + "Date": "2034-02-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 64, + "Months": 2 + }, + "Date": "2034-03-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 64, + "Months": 3 + }, + "Date": "2034-04-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 64, + "Months": 4 + }, + "Date": "2034-05-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 64, + "Months": 5 + }, + "Date": "2034-06-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 64, + "Months": 6 + }, + "Date": "2034-07-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 64, + "Months": 7 + }, + "Date": "2034-08-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 64, + "Months": 8 + }, + "Date": "2034-09-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 64, + "Months": 9 + }, + "Date": "2034-10-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 64, + "Months": 10 + }, + "Date": "2034-11-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 64, + "Months": 11 + }, + "Date": "2034-12-01T00:00:00", + "Value": 70560.00 + }, + { + "Age": { + "Years": 65, + "Months": 0 + }, + "Date": "2035-01-01T00:00:00", + "Value": 70560.00 + } +] diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Retirement_Credit_Factors_Array.snap b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Retirement_Credit_Factors_Array.snap new file mode 100644 index 00000000..8b605653 --- /dev/null +++ b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Retirement_Credit_Factors_Array.snap @@ -0,0 +1,1066 @@ +[ + { + "Age": { + "Years": 54, + "Months": 0 + }, + "Date": "2024-01-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 54, + "Months": 1 + }, + "Date": "2024-02-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 54, + "Months": 2 + }, + "Date": "2024-03-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 54, + "Months": 3 + }, + "Date": "2024-04-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 54, + "Months": 4 + }, + "Date": "2024-05-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 54, + "Months": 5 + }, + "Date": "2024-06-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 54, + "Months": 6 + }, + "Date": "2024-07-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 54, + "Months": 7 + }, + "Date": "2024-08-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 54, + "Months": 8 + }, + "Date": "2024-09-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 54, + "Months": 9 + }, + "Date": "2024-10-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 54, + "Months": 10 + }, + "Date": "2024-11-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 54, + "Months": 11 + }, + "Date": "2024-12-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 55, + "Months": 0 + }, + "Date": "2025-01-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 55, + "Months": 1 + }, + "Date": "2025-02-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 55, + "Months": 2 + }, + "Date": "2025-03-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 55, + "Months": 3 + }, + "Date": "2025-04-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 55, + "Months": 4 + }, + "Date": "2025-05-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 55, + "Months": 5 + }, + "Date": "2025-06-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 55, + "Months": 6 + }, + "Date": "2025-07-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 55, + "Months": 7 + }, + "Date": "2025-08-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 55, + "Months": 8 + }, + "Date": "2025-09-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 55, + "Months": 9 + }, + "Date": "2025-10-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 55, + "Months": 10 + }, + "Date": "2025-11-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 55, + "Months": 11 + }, + "Date": "2025-12-01T00:00:00", + "Value": 0.18 + }, + { + "Age": { + "Years": 56, + "Months": 0 + }, + "Date": "2026-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 1 + }, + "Date": "2026-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 2 + }, + "Date": "2026-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 3 + }, + "Date": "2026-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 4 + }, + "Date": "2026-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 5 + }, + "Date": "2026-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 6 + }, + "Date": "2026-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 7 + }, + "Date": "2026-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 8 + }, + "Date": "2026-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 9 + }, + "Date": "2026-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 10 + }, + "Date": "2026-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 11 + }, + "Date": "2026-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 0 + }, + "Date": "2027-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 1 + }, + "Date": "2027-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 2 + }, + "Date": "2027-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 3 + }, + "Date": "2027-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 4 + }, + "Date": "2027-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 5 + }, + "Date": "2027-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 6 + }, + "Date": "2027-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 7 + }, + "Date": "2027-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 8 + }, + "Date": "2027-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 9 + }, + "Date": "2027-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 10 + }, + "Date": "2027-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 11 + }, + "Date": "2027-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 0 + }, + "Date": "2028-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 1 + }, + "Date": "2028-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 2 + }, + "Date": "2028-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 3 + }, + "Date": "2028-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 4 + }, + "Date": "2028-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 5 + }, + "Date": "2028-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 6 + }, + "Date": "2028-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 7 + }, + "Date": "2028-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 8 + }, + "Date": "2028-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 9 + }, + "Date": "2028-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 10 + }, + "Date": "2028-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 11 + }, + "Date": "2028-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 0 + }, + "Date": "2029-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 1 + }, + "Date": "2029-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 2 + }, + "Date": "2029-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 3 + }, + "Date": "2029-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 4 + }, + "Date": "2029-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 5 + }, + "Date": "2029-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 6 + }, + "Date": "2029-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 7 + }, + "Date": "2029-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 8 + }, + "Date": "2029-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 9 + }, + "Date": "2029-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 10 + }, + "Date": "2029-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 11 + }, + "Date": "2029-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 0 + }, + "Date": "2030-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 1 + }, + "Date": "2030-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 2 + }, + "Date": "2030-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 3 + }, + "Date": "2030-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 4 + }, + "Date": "2030-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 5 + }, + "Date": "2030-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 6 + }, + "Date": "2030-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 7 + }, + "Date": "2030-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 8 + }, + "Date": "2030-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 9 + }, + "Date": "2030-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 10 + }, + "Date": "2030-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 11 + }, + "Date": "2030-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 0 + }, + "Date": "2031-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 1 + }, + "Date": "2031-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 2 + }, + "Date": "2031-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 3 + }, + "Date": "2031-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 4 + }, + "Date": "2031-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 5 + }, + "Date": "2031-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 6 + }, + "Date": "2031-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 7 + }, + "Date": "2031-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 8 + }, + "Date": "2031-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 9 + }, + "Date": "2031-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 10 + }, + "Date": "2031-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 11 + }, + "Date": "2031-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 0 + }, + "Date": "2032-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 1 + }, + "Date": "2032-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 2 + }, + "Date": "2032-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 3 + }, + "Date": "2032-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 4 + }, + "Date": "2032-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 5 + }, + "Date": "2032-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 6 + }, + "Date": "2032-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 7 + }, + "Date": "2032-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 8 + }, + "Date": "2032-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 9 + }, + "Date": "2032-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 10 + }, + "Date": "2032-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 11 + }, + "Date": "2032-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 0 + }, + "Date": "2033-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 1 + }, + "Date": "2033-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 2 + }, + "Date": "2033-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 3 + }, + "Date": "2033-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 4 + }, + "Date": "2033-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 5 + }, + "Date": "2033-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 6 + }, + "Date": "2033-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 7 + }, + "Date": "2033-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 8 + }, + "Date": "2033-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 9 + }, + "Date": "2033-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 10 + }, + "Date": "2033-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 11 + }, + "Date": "2033-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 0 + }, + "Date": "2034-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 1 + }, + "Date": "2034-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 2 + }, + "Date": "2034-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 3 + }, + "Date": "2034-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 4 + }, + "Date": "2034-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 5 + }, + "Date": "2034-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 6 + }, + "Date": "2034-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 7 + }, + "Date": "2034-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 8 + }, + "Date": "2034-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 9 + }, + "Date": "2034-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 10 + }, + "Date": "2034-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 11 + }, + "Date": "2034-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 65, + "Months": 0 + }, + "Date": "2035-01-01T00:00:00", + "Value": 0.0 + } +] diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Retirement_Credit_Factors_Array_Young.snap b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Retirement_Credit_Factors_Array_Young.snap new file mode 100644 index 00000000..963e2267 --- /dev/null +++ b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Retirement_Credit_Factors_Array_Young.snap @@ -0,0 +1,4162 @@ +[ + { + "Age": { + "Years": 21, + "Months": 9 + }, + "Date": "2024-01-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 21, + "Months": 10 + }, + "Date": "2024-02-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 21, + "Months": 11 + }, + "Date": "2024-03-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 22, + "Months": 0 + }, + "Date": "2024-04-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 22, + "Months": 1 + }, + "Date": "2024-05-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 22, + "Months": 2 + }, + "Date": "2024-06-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 22, + "Months": 3 + }, + "Date": "2024-07-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 22, + "Months": 4 + }, + "Date": "2024-08-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 22, + "Months": 5 + }, + "Date": "2024-09-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 22, + "Months": 6 + }, + "Date": "2024-10-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 22, + "Months": 7 + }, + "Date": "2024-11-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 22, + "Months": 8 + }, + "Date": "2024-12-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 22, + "Months": 9 + }, + "Date": "2025-01-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 22, + "Months": 10 + }, + "Date": "2025-02-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 22, + "Months": 11 + }, + "Date": "2025-03-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 23, + "Months": 0 + }, + "Date": "2025-04-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 23, + "Months": 1 + }, + "Date": "2025-05-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 23, + "Months": 2 + }, + "Date": "2025-06-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 23, + "Months": 3 + }, + "Date": "2025-07-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 23, + "Months": 4 + }, + "Date": "2025-08-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 23, + "Months": 5 + }, + "Date": "2025-09-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 23, + "Months": 6 + }, + "Date": "2025-10-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 23, + "Months": 7 + }, + "Date": "2025-11-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 23, + "Months": 8 + }, + "Date": "2025-12-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 23, + "Months": 9 + }, + "Date": "2026-01-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 23, + "Months": 10 + }, + "Date": "2026-02-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 23, + "Months": 11 + }, + "Date": "2026-03-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 24, + "Months": 0 + }, + "Date": "2026-04-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 24, + "Months": 1 + }, + "Date": "2026-05-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 24, + "Months": 2 + }, + "Date": "2026-06-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 24, + "Months": 3 + }, + "Date": "2026-07-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 24, + "Months": 4 + }, + "Date": "2026-08-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 24, + "Months": 5 + }, + "Date": "2026-09-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 24, + "Months": 6 + }, + "Date": "2026-10-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 24, + "Months": 7 + }, + "Date": "2026-11-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 24, + "Months": 8 + }, + "Date": "2026-12-01T00:00:00", + "Value": 0.0 + }, + { + "Age": { + "Years": 24, + "Months": 9 + }, + "Date": "2027-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 24, + "Months": 10 + }, + "Date": "2027-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 24, + "Months": 11 + }, + "Date": "2027-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 25, + "Months": 0 + }, + "Date": "2027-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 25, + "Months": 1 + }, + "Date": "2027-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 25, + "Months": 2 + }, + "Date": "2027-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 25, + "Months": 3 + }, + "Date": "2027-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 25, + "Months": 4 + }, + "Date": "2027-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 25, + "Months": 5 + }, + "Date": "2027-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 25, + "Months": 6 + }, + "Date": "2027-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 25, + "Months": 7 + }, + "Date": "2027-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 25, + "Months": 8 + }, + "Date": "2027-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 25, + "Months": 9 + }, + "Date": "2028-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 25, + "Months": 10 + }, + "Date": "2028-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 25, + "Months": 11 + }, + "Date": "2028-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 26, + "Months": 0 + }, + "Date": "2028-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 26, + "Months": 1 + }, + "Date": "2028-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 26, + "Months": 2 + }, + "Date": "2028-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 26, + "Months": 3 + }, + "Date": "2028-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 26, + "Months": 4 + }, + "Date": "2028-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 26, + "Months": 5 + }, + "Date": "2028-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 26, + "Months": 6 + }, + "Date": "2028-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 26, + "Months": 7 + }, + "Date": "2028-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 26, + "Months": 8 + }, + "Date": "2028-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 26, + "Months": 9 + }, + "Date": "2029-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 26, + "Months": 10 + }, + "Date": "2029-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 26, + "Months": 11 + }, + "Date": "2029-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 27, + "Months": 0 + }, + "Date": "2029-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 27, + "Months": 1 + }, + "Date": "2029-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 27, + "Months": 2 + }, + "Date": "2029-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 27, + "Months": 3 + }, + "Date": "2029-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 27, + "Months": 4 + }, + "Date": "2029-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 27, + "Months": 5 + }, + "Date": "2029-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 27, + "Months": 6 + }, + "Date": "2029-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 27, + "Months": 7 + }, + "Date": "2029-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 27, + "Months": 8 + }, + "Date": "2029-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 27, + "Months": 9 + }, + "Date": "2030-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 27, + "Months": 10 + }, + "Date": "2030-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 27, + "Months": 11 + }, + "Date": "2030-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 28, + "Months": 0 + }, + "Date": "2030-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 28, + "Months": 1 + }, + "Date": "2030-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 28, + "Months": 2 + }, + "Date": "2030-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 28, + "Months": 3 + }, + "Date": "2030-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 28, + "Months": 4 + }, + "Date": "2030-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 28, + "Months": 5 + }, + "Date": "2030-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 28, + "Months": 6 + }, + "Date": "2030-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 28, + "Months": 7 + }, + "Date": "2030-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 28, + "Months": 8 + }, + "Date": "2030-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 28, + "Months": 9 + }, + "Date": "2031-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 28, + "Months": 10 + }, + "Date": "2031-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 28, + "Months": 11 + }, + "Date": "2031-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 29, + "Months": 0 + }, + "Date": "2031-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 29, + "Months": 1 + }, + "Date": "2031-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 29, + "Months": 2 + }, + "Date": "2031-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 29, + "Months": 3 + }, + "Date": "2031-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 29, + "Months": 4 + }, + "Date": "2031-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 29, + "Months": 5 + }, + "Date": "2031-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 29, + "Months": 6 + }, + "Date": "2031-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 29, + "Months": 7 + }, + "Date": "2031-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 29, + "Months": 8 + }, + "Date": "2031-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 29, + "Months": 9 + }, + "Date": "2032-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 29, + "Months": 10 + }, + "Date": "2032-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 29, + "Months": 11 + }, + "Date": "2032-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 30, + "Months": 0 + }, + "Date": "2032-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 30, + "Months": 1 + }, + "Date": "2032-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 30, + "Months": 2 + }, + "Date": "2032-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 30, + "Months": 3 + }, + "Date": "2032-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 30, + "Months": 4 + }, + "Date": "2032-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 30, + "Months": 5 + }, + "Date": "2032-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 30, + "Months": 6 + }, + "Date": "2032-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 30, + "Months": 7 + }, + "Date": "2032-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 30, + "Months": 8 + }, + "Date": "2032-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 30, + "Months": 9 + }, + "Date": "2033-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 30, + "Months": 10 + }, + "Date": "2033-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 30, + "Months": 11 + }, + "Date": "2033-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 31, + "Months": 0 + }, + "Date": "2033-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 31, + "Months": 1 + }, + "Date": "2033-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 31, + "Months": 2 + }, + "Date": "2033-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 31, + "Months": 3 + }, + "Date": "2033-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 31, + "Months": 4 + }, + "Date": "2033-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 31, + "Months": 5 + }, + "Date": "2033-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 31, + "Months": 6 + }, + "Date": "2033-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 31, + "Months": 7 + }, + "Date": "2033-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 31, + "Months": 8 + }, + "Date": "2033-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 31, + "Months": 9 + }, + "Date": "2034-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 31, + "Months": 10 + }, + "Date": "2034-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 31, + "Months": 11 + }, + "Date": "2034-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 32, + "Months": 0 + }, + "Date": "2034-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 32, + "Months": 1 + }, + "Date": "2034-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 32, + "Months": 2 + }, + "Date": "2034-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 32, + "Months": 3 + }, + "Date": "2034-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 32, + "Months": 4 + }, + "Date": "2034-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 32, + "Months": 5 + }, + "Date": "2034-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 32, + "Months": 6 + }, + "Date": "2034-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 32, + "Months": 7 + }, + "Date": "2034-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 32, + "Months": 8 + }, + "Date": "2034-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 32, + "Months": 9 + }, + "Date": "2035-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 32, + "Months": 10 + }, + "Date": "2035-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 32, + "Months": 11 + }, + "Date": "2035-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 33, + "Months": 0 + }, + "Date": "2035-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 33, + "Months": 1 + }, + "Date": "2035-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 33, + "Months": 2 + }, + "Date": "2035-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 33, + "Months": 3 + }, + "Date": "2035-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 33, + "Months": 4 + }, + "Date": "2035-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 33, + "Months": 5 + }, + "Date": "2035-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 33, + "Months": 6 + }, + "Date": "2035-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 33, + "Months": 7 + }, + "Date": "2035-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 33, + "Months": 8 + }, + "Date": "2035-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 33, + "Months": 9 + }, + "Date": "2036-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 33, + "Months": 10 + }, + "Date": "2036-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 33, + "Months": 11 + }, + "Date": "2036-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 34, + "Months": 0 + }, + "Date": "2036-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 34, + "Months": 1 + }, + "Date": "2036-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 34, + "Months": 2 + }, + "Date": "2036-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 34, + "Months": 3 + }, + "Date": "2036-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 34, + "Months": 4 + }, + "Date": "2036-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 34, + "Months": 5 + }, + "Date": "2036-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 34, + "Months": 6 + }, + "Date": "2036-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 34, + "Months": 7 + }, + "Date": "2036-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 34, + "Months": 8 + }, + "Date": "2036-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 34, + "Months": 9 + }, + "Date": "2037-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 34, + "Months": 10 + }, + "Date": "2037-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 34, + "Months": 11 + }, + "Date": "2037-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 35, + "Months": 0 + }, + "Date": "2037-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 35, + "Months": 1 + }, + "Date": "2037-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 35, + "Months": 2 + }, + "Date": "2037-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 35, + "Months": 3 + }, + "Date": "2037-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 35, + "Months": 4 + }, + "Date": "2037-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 35, + "Months": 5 + }, + "Date": "2037-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 35, + "Months": 6 + }, + "Date": "2037-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 35, + "Months": 7 + }, + "Date": "2037-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 35, + "Months": 8 + }, + "Date": "2037-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 35, + "Months": 9 + }, + "Date": "2038-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 35, + "Months": 10 + }, + "Date": "2038-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 35, + "Months": 11 + }, + "Date": "2038-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 36, + "Months": 0 + }, + "Date": "2038-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 36, + "Months": 1 + }, + "Date": "2038-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 36, + "Months": 2 + }, + "Date": "2038-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 36, + "Months": 3 + }, + "Date": "2038-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 36, + "Months": 4 + }, + "Date": "2038-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 36, + "Months": 5 + }, + "Date": "2038-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 36, + "Months": 6 + }, + "Date": "2038-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 36, + "Months": 7 + }, + "Date": "2038-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 36, + "Months": 8 + }, + "Date": "2038-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 36, + "Months": 9 + }, + "Date": "2039-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 36, + "Months": 10 + }, + "Date": "2039-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 36, + "Months": 11 + }, + "Date": "2039-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 37, + "Months": 0 + }, + "Date": "2039-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 37, + "Months": 1 + }, + "Date": "2039-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 37, + "Months": 2 + }, + "Date": "2039-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 37, + "Months": 3 + }, + "Date": "2039-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 37, + "Months": 4 + }, + "Date": "2039-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 37, + "Months": 5 + }, + "Date": "2039-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 37, + "Months": 6 + }, + "Date": "2039-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 37, + "Months": 7 + }, + "Date": "2039-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 37, + "Months": 8 + }, + "Date": "2039-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 37, + "Months": 9 + }, + "Date": "2040-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 37, + "Months": 10 + }, + "Date": "2040-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 37, + "Months": 11 + }, + "Date": "2040-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 38, + "Months": 0 + }, + "Date": "2040-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 38, + "Months": 1 + }, + "Date": "2040-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 38, + "Months": 2 + }, + "Date": "2040-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 38, + "Months": 3 + }, + "Date": "2040-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 38, + "Months": 4 + }, + "Date": "2040-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 38, + "Months": 5 + }, + "Date": "2040-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 38, + "Months": 6 + }, + "Date": "2040-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 38, + "Months": 7 + }, + "Date": "2040-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 38, + "Months": 8 + }, + "Date": "2040-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 38, + "Months": 9 + }, + "Date": "2041-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 38, + "Months": 10 + }, + "Date": "2041-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 38, + "Months": 11 + }, + "Date": "2041-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 39, + "Months": 0 + }, + "Date": "2041-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 39, + "Months": 1 + }, + "Date": "2041-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 39, + "Months": 2 + }, + "Date": "2041-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 39, + "Months": 3 + }, + "Date": "2041-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 39, + "Months": 4 + }, + "Date": "2041-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 39, + "Months": 5 + }, + "Date": "2041-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 39, + "Months": 6 + }, + "Date": "2041-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 39, + "Months": 7 + }, + "Date": "2041-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 39, + "Months": 8 + }, + "Date": "2041-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 39, + "Months": 9 + }, + "Date": "2042-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 39, + "Months": 10 + }, + "Date": "2042-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 39, + "Months": 11 + }, + "Date": "2042-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 40, + "Months": 0 + }, + "Date": "2042-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 40, + "Months": 1 + }, + "Date": "2042-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 40, + "Months": 2 + }, + "Date": "2042-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 40, + "Months": 3 + }, + "Date": "2042-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 40, + "Months": 4 + }, + "Date": "2042-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 40, + "Months": 5 + }, + "Date": "2042-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 40, + "Months": 6 + }, + "Date": "2042-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 40, + "Months": 7 + }, + "Date": "2042-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 40, + "Months": 8 + }, + "Date": "2042-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 40, + "Months": 9 + }, + "Date": "2043-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 40, + "Months": 10 + }, + "Date": "2043-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 40, + "Months": 11 + }, + "Date": "2043-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 41, + "Months": 0 + }, + "Date": "2043-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 41, + "Months": 1 + }, + "Date": "2043-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 41, + "Months": 2 + }, + "Date": "2043-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 41, + "Months": 3 + }, + "Date": "2043-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 41, + "Months": 4 + }, + "Date": "2043-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 41, + "Months": 5 + }, + "Date": "2043-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 41, + "Months": 6 + }, + "Date": "2043-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 41, + "Months": 7 + }, + "Date": "2043-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 41, + "Months": 8 + }, + "Date": "2043-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 41, + "Months": 9 + }, + "Date": "2044-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 41, + "Months": 10 + }, + "Date": "2044-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 41, + "Months": 11 + }, + "Date": "2044-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 42, + "Months": 0 + }, + "Date": "2044-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 42, + "Months": 1 + }, + "Date": "2044-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 42, + "Months": 2 + }, + "Date": "2044-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 42, + "Months": 3 + }, + "Date": "2044-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 42, + "Months": 4 + }, + "Date": "2044-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 42, + "Months": 5 + }, + "Date": "2044-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 42, + "Months": 6 + }, + "Date": "2044-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 42, + "Months": 7 + }, + "Date": "2044-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 42, + "Months": 8 + }, + "Date": "2044-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 42, + "Months": 9 + }, + "Date": "2045-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 42, + "Months": 10 + }, + "Date": "2045-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 42, + "Months": 11 + }, + "Date": "2045-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 43, + "Months": 0 + }, + "Date": "2045-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 43, + "Months": 1 + }, + "Date": "2045-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 43, + "Months": 2 + }, + "Date": "2045-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 43, + "Months": 3 + }, + "Date": "2045-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 43, + "Months": 4 + }, + "Date": "2045-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 43, + "Months": 5 + }, + "Date": "2045-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 43, + "Months": 6 + }, + "Date": "2045-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 43, + "Months": 7 + }, + "Date": "2045-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 43, + "Months": 8 + }, + "Date": "2045-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 43, + "Months": 9 + }, + "Date": "2046-01-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 43, + "Months": 10 + }, + "Date": "2046-02-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 43, + "Months": 11 + }, + "Date": "2046-03-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 44, + "Months": 0 + }, + "Date": "2046-04-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 44, + "Months": 1 + }, + "Date": "2046-05-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 44, + "Months": 2 + }, + "Date": "2046-06-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 44, + "Months": 3 + }, + "Date": "2046-07-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 44, + "Months": 4 + }, + "Date": "2046-08-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 44, + "Months": 5 + }, + "Date": "2046-09-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 44, + "Months": 6 + }, + "Date": "2046-10-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 44, + "Months": 7 + }, + "Date": "2046-11-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 44, + "Months": 8 + }, + "Date": "2046-12-01T00:00:00", + "Value": 0.09 + }, + { + "Age": { + "Years": 44, + "Months": 9 + }, + "Date": "2047-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 44, + "Months": 10 + }, + "Date": "2047-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 44, + "Months": 11 + }, + "Date": "2047-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 45, + "Months": 0 + }, + "Date": "2047-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 45, + "Months": 1 + }, + "Date": "2047-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 45, + "Months": 2 + }, + "Date": "2047-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 45, + "Months": 3 + }, + "Date": "2047-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 45, + "Months": 4 + }, + "Date": "2047-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 45, + "Months": 5 + }, + "Date": "2047-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 45, + "Months": 6 + }, + "Date": "2047-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 45, + "Months": 7 + }, + "Date": "2047-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 45, + "Months": 8 + }, + "Date": "2047-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 45, + "Months": 9 + }, + "Date": "2048-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 45, + "Months": 10 + }, + "Date": "2048-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 45, + "Months": 11 + }, + "Date": "2048-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 46, + "Months": 0 + }, + "Date": "2048-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 46, + "Months": 1 + }, + "Date": "2048-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 46, + "Months": 2 + }, + "Date": "2048-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 46, + "Months": 3 + }, + "Date": "2048-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 46, + "Months": 4 + }, + "Date": "2048-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 46, + "Months": 5 + }, + "Date": "2048-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 46, + "Months": 6 + }, + "Date": "2048-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 46, + "Months": 7 + }, + "Date": "2048-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 46, + "Months": 8 + }, + "Date": "2048-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 46, + "Months": 9 + }, + "Date": "2049-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 46, + "Months": 10 + }, + "Date": "2049-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 46, + "Months": 11 + }, + "Date": "2049-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 47, + "Months": 0 + }, + "Date": "2049-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 47, + "Months": 1 + }, + "Date": "2049-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 47, + "Months": 2 + }, + "Date": "2049-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 47, + "Months": 3 + }, + "Date": "2049-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 47, + "Months": 4 + }, + "Date": "2049-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 47, + "Months": 5 + }, + "Date": "2049-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 47, + "Months": 6 + }, + "Date": "2049-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 47, + "Months": 7 + }, + "Date": "2049-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 47, + "Months": 8 + }, + "Date": "2049-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 47, + "Months": 9 + }, + "Date": "2050-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 47, + "Months": 10 + }, + "Date": "2050-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 47, + "Months": 11 + }, + "Date": "2050-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 48, + "Months": 0 + }, + "Date": "2050-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 48, + "Months": 1 + }, + "Date": "2050-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 48, + "Months": 2 + }, + "Date": "2050-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 48, + "Months": 3 + }, + "Date": "2050-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 48, + "Months": 4 + }, + "Date": "2050-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 48, + "Months": 5 + }, + "Date": "2050-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 48, + "Months": 6 + }, + "Date": "2050-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 48, + "Months": 7 + }, + "Date": "2050-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 48, + "Months": 8 + }, + "Date": "2050-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 48, + "Months": 9 + }, + "Date": "2051-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 48, + "Months": 10 + }, + "Date": "2051-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 48, + "Months": 11 + }, + "Date": "2051-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 49, + "Months": 0 + }, + "Date": "2051-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 49, + "Months": 1 + }, + "Date": "2051-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 49, + "Months": 2 + }, + "Date": "2051-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 49, + "Months": 3 + }, + "Date": "2051-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 49, + "Months": 4 + }, + "Date": "2051-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 49, + "Months": 5 + }, + "Date": "2051-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 49, + "Months": 6 + }, + "Date": "2051-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 49, + "Months": 7 + }, + "Date": "2051-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 49, + "Months": 8 + }, + "Date": "2051-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 49, + "Months": 9 + }, + "Date": "2052-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 49, + "Months": 10 + }, + "Date": "2052-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 49, + "Months": 11 + }, + "Date": "2052-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 50, + "Months": 0 + }, + "Date": "2052-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 50, + "Months": 1 + }, + "Date": "2052-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 50, + "Months": 2 + }, + "Date": "2052-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 50, + "Months": 3 + }, + "Date": "2052-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 50, + "Months": 4 + }, + "Date": "2052-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 50, + "Months": 5 + }, + "Date": "2052-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 50, + "Months": 6 + }, + "Date": "2052-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 50, + "Months": 7 + }, + "Date": "2052-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 50, + "Months": 8 + }, + "Date": "2052-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 50, + "Months": 9 + }, + "Date": "2053-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 50, + "Months": 10 + }, + "Date": "2053-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 50, + "Months": 11 + }, + "Date": "2053-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 51, + "Months": 0 + }, + "Date": "2053-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 51, + "Months": 1 + }, + "Date": "2053-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 51, + "Months": 2 + }, + "Date": "2053-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 51, + "Months": 3 + }, + "Date": "2053-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 51, + "Months": 4 + }, + "Date": "2053-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 51, + "Months": 5 + }, + "Date": "2053-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 51, + "Months": 6 + }, + "Date": "2053-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 51, + "Months": 7 + }, + "Date": "2053-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 51, + "Months": 8 + }, + "Date": "2053-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 51, + "Months": 9 + }, + "Date": "2054-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 51, + "Months": 10 + }, + "Date": "2054-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 51, + "Months": 11 + }, + "Date": "2054-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 52, + "Months": 0 + }, + "Date": "2054-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 52, + "Months": 1 + }, + "Date": "2054-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 52, + "Months": 2 + }, + "Date": "2054-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 52, + "Months": 3 + }, + "Date": "2054-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 52, + "Months": 4 + }, + "Date": "2054-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 52, + "Months": 5 + }, + "Date": "2054-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 52, + "Months": 6 + }, + "Date": "2054-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 52, + "Months": 7 + }, + "Date": "2054-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 52, + "Months": 8 + }, + "Date": "2054-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 52, + "Months": 9 + }, + "Date": "2055-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 52, + "Months": 10 + }, + "Date": "2055-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 52, + "Months": 11 + }, + "Date": "2055-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 53, + "Months": 0 + }, + "Date": "2055-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 53, + "Months": 1 + }, + "Date": "2055-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 53, + "Months": 2 + }, + "Date": "2055-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 53, + "Months": 3 + }, + "Date": "2055-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 53, + "Months": 4 + }, + "Date": "2055-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 53, + "Months": 5 + }, + "Date": "2055-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 53, + "Months": 6 + }, + "Date": "2055-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 53, + "Months": 7 + }, + "Date": "2055-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 53, + "Months": 8 + }, + "Date": "2055-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 53, + "Months": 9 + }, + "Date": "2056-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 53, + "Months": 10 + }, + "Date": "2056-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 53, + "Months": 11 + }, + "Date": "2056-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 54, + "Months": 0 + }, + "Date": "2056-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 54, + "Months": 1 + }, + "Date": "2056-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 54, + "Months": 2 + }, + "Date": "2056-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 54, + "Months": 3 + }, + "Date": "2056-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 54, + "Months": 4 + }, + "Date": "2056-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 54, + "Months": 5 + }, + "Date": "2056-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 54, + "Months": 6 + }, + "Date": "2056-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 54, + "Months": 7 + }, + "Date": "2056-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 54, + "Months": 8 + }, + "Date": "2056-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 54, + "Months": 9 + }, + "Date": "2057-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 54, + "Months": 10 + }, + "Date": "2057-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 54, + "Months": 11 + }, + "Date": "2057-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 55, + "Months": 0 + }, + "Date": "2057-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 55, + "Months": 1 + }, + "Date": "2057-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 55, + "Months": 2 + }, + "Date": "2057-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 55, + "Months": 3 + }, + "Date": "2057-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 55, + "Months": 4 + }, + "Date": "2057-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 55, + "Months": 5 + }, + "Date": "2057-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 55, + "Months": 6 + }, + "Date": "2057-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 55, + "Months": 7 + }, + "Date": "2057-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 55, + "Months": 8 + }, + "Date": "2057-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 55, + "Months": 9 + }, + "Date": "2058-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 55, + "Months": 10 + }, + "Date": "2058-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 55, + "Months": 11 + }, + "Date": "2058-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 0 + }, + "Date": "2058-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 1 + }, + "Date": "2058-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 2 + }, + "Date": "2058-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 3 + }, + "Date": "2058-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 4 + }, + "Date": "2058-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 5 + }, + "Date": "2058-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 6 + }, + "Date": "2058-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 7 + }, + "Date": "2058-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 8 + }, + "Date": "2058-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 9 + }, + "Date": "2059-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 10 + }, + "Date": "2059-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 56, + "Months": 11 + }, + "Date": "2059-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 0 + }, + "Date": "2059-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 1 + }, + "Date": "2059-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 2 + }, + "Date": "2059-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 3 + }, + "Date": "2059-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 4 + }, + "Date": "2059-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 5 + }, + "Date": "2059-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 6 + }, + "Date": "2059-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 7 + }, + "Date": "2059-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 8 + }, + "Date": "2059-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 9 + }, + "Date": "2060-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 10 + }, + "Date": "2060-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 57, + "Months": 11 + }, + "Date": "2060-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 0 + }, + "Date": "2060-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 1 + }, + "Date": "2060-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 2 + }, + "Date": "2060-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 3 + }, + "Date": "2060-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 4 + }, + "Date": "2060-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 5 + }, + "Date": "2060-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 6 + }, + "Date": "2060-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 7 + }, + "Date": "2060-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 8 + }, + "Date": "2060-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 9 + }, + "Date": "2061-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 10 + }, + "Date": "2061-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 58, + "Months": 11 + }, + "Date": "2061-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 0 + }, + "Date": "2061-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 1 + }, + "Date": "2061-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 2 + }, + "Date": "2061-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 3 + }, + "Date": "2061-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 4 + }, + "Date": "2061-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 5 + }, + "Date": "2061-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 6 + }, + "Date": "2061-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 7 + }, + "Date": "2061-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 8 + }, + "Date": "2061-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 9 + }, + "Date": "2062-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 10 + }, + "Date": "2062-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 59, + "Months": 11 + }, + "Date": "2062-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 0 + }, + "Date": "2062-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 1 + }, + "Date": "2062-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 2 + }, + "Date": "2062-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 3 + }, + "Date": "2062-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 4 + }, + "Date": "2062-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 5 + }, + "Date": "2062-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 6 + }, + "Date": "2062-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 7 + }, + "Date": "2062-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 8 + }, + "Date": "2062-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 9 + }, + "Date": "2063-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 10 + }, + "Date": "2063-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 60, + "Months": 11 + }, + "Date": "2063-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 0 + }, + "Date": "2063-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 1 + }, + "Date": "2063-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 2 + }, + "Date": "2063-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 3 + }, + "Date": "2063-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 4 + }, + "Date": "2063-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 5 + }, + "Date": "2063-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 6 + }, + "Date": "2063-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 7 + }, + "Date": "2063-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 8 + }, + "Date": "2063-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 9 + }, + "Date": "2064-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 10 + }, + "Date": "2064-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 61, + "Months": 11 + }, + "Date": "2064-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 0 + }, + "Date": "2064-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 1 + }, + "Date": "2064-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 2 + }, + "Date": "2064-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 3 + }, + "Date": "2064-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 4 + }, + "Date": "2064-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 5 + }, + "Date": "2064-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 6 + }, + "Date": "2064-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 7 + }, + "Date": "2064-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 8 + }, + "Date": "2064-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 9 + }, + "Date": "2065-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 10 + }, + "Date": "2065-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 62, + "Months": 11 + }, + "Date": "2065-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 0 + }, + "Date": "2065-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 1 + }, + "Date": "2065-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 2 + }, + "Date": "2065-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 3 + }, + "Date": "2065-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 4 + }, + "Date": "2065-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 5 + }, + "Date": "2065-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 6 + }, + "Date": "2065-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 7 + }, + "Date": "2065-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 8 + }, + "Date": "2065-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 9 + }, + "Date": "2066-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 10 + }, + "Date": "2066-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 63, + "Months": 11 + }, + "Date": "2066-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 0 + }, + "Date": "2066-04-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 1 + }, + "Date": "2066-05-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 2 + }, + "Date": "2066-06-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 3 + }, + "Date": "2066-07-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 4 + }, + "Date": "2066-08-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 5 + }, + "Date": "2066-09-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 6 + }, + "Date": "2066-10-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 7 + }, + "Date": "2066-11-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 8 + }, + "Date": "2066-12-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 9 + }, + "Date": "2067-01-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 10 + }, + "Date": "2067-02-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 64, + "Months": 11 + }, + "Date": "2067-03-01T00:00:00", + "Value": 0.14 + }, + { + "Age": { + "Years": 65, + "Months": 0 + }, + "Date": "2067-04-01T00:00:00", + "Value": 0.14 + } +] diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Retirement_Credits_Array.snap b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Retirement_Credits_Array.snap new file mode 100644 index 00000000..d5135f76 --- /dev/null +++ b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.Calculate_Retirement_Credits_Array.snap @@ -0,0 +1,1066 @@ +[ + { + "Age": { + "Years": 54, + "Months": 0 + }, + "Date": "2024-01-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 54, + "Months": 1 + }, + "Date": "2024-02-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 54, + "Months": 2 + }, + "Date": "2024-03-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 54, + "Months": 3 + }, + "Date": "2024-04-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 54, + "Months": 4 + }, + "Date": "2024-05-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 54, + "Months": 5 + }, + "Date": "2024-06-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 54, + "Months": 6 + }, + "Date": "2024-07-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 54, + "Months": 7 + }, + "Date": "2024-08-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 54, + "Months": 8 + }, + "Date": "2024-09-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 54, + "Months": 9 + }, + "Date": "2024-10-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 54, + "Months": 10 + }, + "Date": "2024-11-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 54, + "Months": 11 + }, + "Date": "2024-12-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 55, + "Months": 0 + }, + "Date": "2025-01-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 55, + "Months": 1 + }, + "Date": "2025-02-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 55, + "Months": 2 + }, + "Date": "2025-03-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 55, + "Months": 3 + }, + "Date": "2025-04-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 55, + "Months": 4 + }, + "Date": "2025-05-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 55, + "Months": 5 + }, + "Date": "2025-06-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 55, + "Months": 6 + }, + "Date": "2025-07-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 55, + "Months": 7 + }, + "Date": "2025-08-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 55, + "Months": 8 + }, + "Date": "2025-09-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 55, + "Months": 9 + }, + "Date": "2025-10-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 55, + "Months": 10 + }, + "Date": "2025-11-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 55, + "Months": 11 + }, + "Date": "2025-12-01T00:00:00", + "Value": 11245.5000 + }, + { + "Age": { + "Years": 56, + "Months": 0 + }, + "Date": "2026-01-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 56, + "Months": 1 + }, + "Date": "2026-02-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 56, + "Months": 2 + }, + "Date": "2026-03-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 56, + "Months": 3 + }, + "Date": "2026-04-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 56, + "Months": 4 + }, + "Date": "2026-05-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 56, + "Months": 5 + }, + "Date": "2026-06-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 56, + "Months": 6 + }, + "Date": "2026-07-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 56, + "Months": 7 + }, + "Date": "2026-08-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 56, + "Months": 8 + }, + "Date": "2026-09-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 56, + "Months": 9 + }, + "Date": "2026-10-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 56, + "Months": 10 + }, + "Date": "2026-11-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 56, + "Months": 11 + }, + "Date": "2026-12-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 57, + "Months": 0 + }, + "Date": "2027-01-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 57, + "Months": 1 + }, + "Date": "2027-02-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 57, + "Months": 2 + }, + "Date": "2027-03-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 57, + "Months": 3 + }, + "Date": "2027-04-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 57, + "Months": 4 + }, + "Date": "2027-05-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 57, + "Months": 5 + }, + "Date": "2027-06-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 57, + "Months": 6 + }, + "Date": "2027-07-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 57, + "Months": 7 + }, + "Date": "2027-08-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 57, + "Months": 8 + }, + "Date": "2027-09-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 57, + "Months": 9 + }, + "Date": "2027-10-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 57, + "Months": 10 + }, + "Date": "2027-11-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 57, + "Months": 11 + }, + "Date": "2027-12-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 58, + "Months": 0 + }, + "Date": "2028-01-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 58, + "Months": 1 + }, + "Date": "2028-02-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 58, + "Months": 2 + }, + "Date": "2028-03-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 58, + "Months": 3 + }, + "Date": "2028-04-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 58, + "Months": 4 + }, + "Date": "2028-05-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 58, + "Months": 5 + }, + "Date": "2028-06-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 58, + "Months": 6 + }, + "Date": "2028-07-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 58, + "Months": 7 + }, + "Date": "2028-08-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 58, + "Months": 8 + }, + "Date": "2028-09-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 58, + "Months": 9 + }, + "Date": "2028-10-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 58, + "Months": 10 + }, + "Date": "2028-11-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 58, + "Months": 11 + }, + "Date": "2028-12-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 59, + "Months": 0 + }, + "Date": "2029-01-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 59, + "Months": 1 + }, + "Date": "2029-02-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 59, + "Months": 2 + }, + "Date": "2029-03-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 59, + "Months": 3 + }, + "Date": "2029-04-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 59, + "Months": 4 + }, + "Date": "2029-05-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 59, + "Months": 5 + }, + "Date": "2029-06-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 59, + "Months": 6 + }, + "Date": "2029-07-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 59, + "Months": 7 + }, + "Date": "2029-08-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 59, + "Months": 8 + }, + "Date": "2029-09-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 59, + "Months": 9 + }, + "Date": "2029-10-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 59, + "Months": 10 + }, + "Date": "2029-11-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 59, + "Months": 11 + }, + "Date": "2029-12-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 60, + "Months": 0 + }, + "Date": "2030-01-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 60, + "Months": 1 + }, + "Date": "2030-02-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 60, + "Months": 2 + }, + "Date": "2030-03-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 60, + "Months": 3 + }, + "Date": "2030-04-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 60, + "Months": 4 + }, + "Date": "2030-05-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 60, + "Months": 5 + }, + "Date": "2030-06-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 60, + "Months": 6 + }, + "Date": "2030-07-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 60, + "Months": 7 + }, + "Date": "2030-08-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 60, + "Months": 8 + }, + "Date": "2030-09-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 60, + "Months": 9 + }, + "Date": "2030-10-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 60, + "Months": 10 + }, + "Date": "2030-11-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 60, + "Months": 11 + }, + "Date": "2030-12-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 61, + "Months": 0 + }, + "Date": "2031-01-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 61, + "Months": 1 + }, + "Date": "2031-02-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 61, + "Months": 2 + }, + "Date": "2031-03-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 61, + "Months": 3 + }, + "Date": "2031-04-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 61, + "Months": 4 + }, + "Date": "2031-05-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 61, + "Months": 5 + }, + "Date": "2031-06-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 61, + "Months": 6 + }, + "Date": "2031-07-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 61, + "Months": 7 + }, + "Date": "2031-08-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 61, + "Months": 8 + }, + "Date": "2031-09-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 61, + "Months": 9 + }, + "Date": "2031-10-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 61, + "Months": 10 + }, + "Date": "2031-11-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 61, + "Months": 11 + }, + "Date": "2031-12-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 62, + "Months": 0 + }, + "Date": "2032-01-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 62, + "Months": 1 + }, + "Date": "2032-02-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 62, + "Months": 2 + }, + "Date": "2032-03-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 62, + "Months": 3 + }, + "Date": "2032-04-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 62, + "Months": 4 + }, + "Date": "2032-05-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 62, + "Months": 5 + }, + "Date": "2032-06-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 62, + "Months": 6 + }, + "Date": "2032-07-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 62, + "Months": 7 + }, + "Date": "2032-08-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 62, + "Months": 8 + }, + "Date": "2032-09-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 62, + "Months": 9 + }, + "Date": "2032-10-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 62, + "Months": 10 + }, + "Date": "2032-11-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 62, + "Months": 11 + }, + "Date": "2032-12-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 63, + "Months": 0 + }, + "Date": "2033-01-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 63, + "Months": 1 + }, + "Date": "2033-02-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 63, + "Months": 2 + }, + "Date": "2033-03-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 63, + "Months": 3 + }, + "Date": "2033-04-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 63, + "Months": 4 + }, + "Date": "2033-05-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 63, + "Months": 5 + }, + "Date": "2033-06-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 63, + "Months": 6 + }, + "Date": "2033-07-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 63, + "Months": 7 + }, + "Date": "2033-08-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 63, + "Months": 8 + }, + "Date": "2033-09-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 63, + "Months": 9 + }, + "Date": "2033-10-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 63, + "Months": 10 + }, + "Date": "2033-11-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 63, + "Months": 11 + }, + "Date": "2033-12-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 64, + "Months": 0 + }, + "Date": "2034-01-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 64, + "Months": 1 + }, + "Date": "2034-02-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 64, + "Months": 2 + }, + "Date": "2034-03-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 64, + "Months": 3 + }, + "Date": "2034-04-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 64, + "Months": 4 + }, + "Date": "2034-05-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 64, + "Months": 5 + }, + "Date": "2034-06-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 64, + "Months": 6 + }, + "Date": "2034-07-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 64, + "Months": 7 + }, + "Date": "2034-08-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 64, + "Months": 8 + }, + "Date": "2034-09-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 64, + "Months": 9 + }, + "Date": "2034-10-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 64, + "Months": 10 + }, + "Date": "2034-11-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 64, + "Months": 11 + }, + "Date": "2034-12-01T00:00:00", + "Value": 9878.400 + }, + { + "Age": { + "Years": 65, + "Months": 0 + }, + "Date": "2035-01-01T00:00:00", + "Value": 0.0 + } +] diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.ShouldCalculateDefaultResult.snap b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.ShouldCalculateDefaultResult.snap new file mode 100644 index 00000000..4961a2da --- /dev/null +++ b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.ShouldCalculateDefaultResult.snap @@ -0,0 +1,1156 @@ +{ + "DateOfRetirement": "2035-01-01T00:00:00", + "EffectiveSalary": 100000.0, + "InsuredSalary": 62475.00, + "RetirementCredit": 11245.5000, + "RetirementCreditFactor": 0.18, + "RetirementCapitalEndOfYear": 11245.5, + "FinalRetirementCapital": 118791.70195120797773087820969, + "FinalRetirementCapitalWithoutInterest": 111396.6000, + "RetirementPension": 7127.5021170724786638526925814, + "DisabilityPension": 6683.796000, + "PartnerPension": 4010.2776000, + "OrphanPension": 1336.7592000, + "ChildPensionForDisabled": 1336.7592000, + "RetirementCreditSequence": [ + { + "Age": 54, + "AmountRaw": 11245.5000 + }, + { + "Age": 54, + "AmountRaw": 11245.5000 + }, + { + "Age": 54, + "AmountRaw": 11245.5000 + }, + { + "Age": 54, + "AmountRaw": 11245.5000 + }, + { + "Age": 54, + "AmountRaw": 11245.5000 + }, + { + "Age": 54, + "AmountRaw": 11245.5000 + }, + { + "Age": 54, + "AmountRaw": 11245.5000 + }, + { + "Age": 54, + "AmountRaw": 11245.5000 + }, + { + "Age": 54, + "AmountRaw": 11245.5000 + }, + { + "Age": 54, + "AmountRaw": 11245.5000 + }, + { + "Age": 54, + "AmountRaw": 11245.5000 + }, + { + "Age": 54, + "AmountRaw": 11245.5000 + }, + { + "Age": 55, + "AmountRaw": 11245.5000 + }, + { + "Age": 55, + "AmountRaw": 11245.5000 + }, + { + "Age": 55, + "AmountRaw": 11245.5000 + }, + { + "Age": 55, + "AmountRaw": 11245.5000 + }, + { + "Age": 55, + "AmountRaw": 11245.5000 + }, + { + "Age": 55, + "AmountRaw": 11245.5000 + }, + { + "Age": 55, + "AmountRaw": 11245.5000 + }, + { + "Age": 55, + "AmountRaw": 11245.5000 + }, + { + "Age": 55, + "AmountRaw": 11245.5000 + }, + { + "Age": 55, + "AmountRaw": 11245.5000 + }, + { + "Age": 55, + "AmountRaw": 11245.5000 + }, + { + "Age": 55, + "AmountRaw": 11245.5000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 65, + "AmountRaw": 0.00 + } + ], + "RetirementCapitalSequence": [ + { + "Date": "2025-01-01T00:00:00", + "Value": 11245.5, + "ValueWithoutInterest": 11245.5 + }, + { + "Date": "2025-02-01T00:00:00", + "Value": 12194.339062500000000000000000, + "ValueWithoutInterest": 12182.625000000000000000000000 + }, + { + "Date": "2025-03-01T00:00:00", + "Value": 13143.178125000000000000000000, + "ValueWithoutInterest": 13119.750000000000000000000000 + }, + { + "Date": "2025-04-01T00:00:00", + "Value": 14092.0171875, + "ValueWithoutInterest": 14056.875000 + }, + { + "Date": "2025-05-01T00:00:00", + "Value": 15040.856250000000000000000000, + "ValueWithoutInterest": 14994.000000000000000000000000 + }, + { + "Date": "2025-06-01T00:00:00", + "Value": 15989.695312500000000000000000, + "ValueWithoutInterest": 15931.125000000000000000000000 + }, + { + "Date": "2025-07-01T00:00:00", + "Value": 16938.534375, + "ValueWithoutInterest": 16868.25000 + }, + { + "Date": "2025-08-01T00:00:00", + "Value": 17887.373437500000000000000000, + "ValueWithoutInterest": 17805.375000000000000000000000 + }, + { + "Date": "2025-09-01T00:00:00", + "Value": 18836.212500000000000000000000, + "ValueWithoutInterest": 18742.500000000000000000000000 + }, + { + "Date": "2025-10-01T00:00:00", + "Value": 19785.0515625, + "ValueWithoutInterest": 19679.625000 + }, + { + "Date": "2025-11-01T00:00:00", + "Value": 20733.890625000000000000000000, + "ValueWithoutInterest": 20616.750000000000000000000000 + }, + { + "Date": "2025-12-01T00:00:00", + "Value": 21682.729687500000000000000000, + "ValueWithoutInterest": 21553.875000000000000000000000 + }, + { + "Date": "2026-01-01T00:00:00", + "Value": 22631.56875, + "ValueWithoutInterest": 22491.0000 + }, + { + "Date": "2026-02-01T00:00:00", + "Value": 23478.343300781250000000000001, + "ValueWithoutInterest": 23314.200000000000000000000000 + }, + { + "Date": "2026-03-01T00:00:00", + "Value": 24325.117851562499999999999999, + "ValueWithoutInterest": 24137.400000000000000000000000 + }, + { + "Date": "2026-04-01T00:00:00", + "Value": 25171.89240234375, + "ValueWithoutInterest": 24960.600000 + }, + { + "Date": "2026-05-01T00:00:00", + "Value": 26018.666953125000000000000001, + "ValueWithoutInterest": 25783.800000000000000000000000 + }, + { + "Date": "2026-06-01T00:00:00", + "Value": 26865.441503906249999999999999, + "ValueWithoutInterest": 26607.000000000000000000000000 + }, + { + "Date": "2026-07-01T00:00:00", + "Value": 27712.2160546875, + "ValueWithoutInterest": 27430.20000 + }, + { + "Date": "2026-08-01T00:00:00", + "Value": 28558.990605468750000000000001, + "ValueWithoutInterest": 28253.400000000000000000000000 + }, + { + "Date": "2026-09-01T00:00:00", + "Value": 29405.765156249999999999999999, + "ValueWithoutInterest": 29076.600000000000000000000000 + }, + { + "Date": "2026-10-01T00:00:00", + "Value": 30252.53970703125, + "ValueWithoutInterest": 29899.800000 + }, + { + "Date": "2026-11-01T00:00:00", + "Value": 31099.314257812500000000000001, + "ValueWithoutInterest": 30723.000000000000000000000000 + }, + { + "Date": "2026-12-01T00:00:00", + "Value": 31946.088808593749999999999999, + "ValueWithoutInterest": 31546.200000000000000000000000 + }, + { + "Date": "2027-01-01T00:00:00", + "Value": 32792.863359375, + "ValueWithoutInterest": 32369.4000 + }, + { + "Date": "2027-02-01T00:00:00", + "Value": 33650.222592041015625000000001, + "ValueWithoutInterest": 33192.600000000000000000000000 + }, + { + "Date": "2027-03-01T00:00:00", + "Value": 34507.581824707031249999999999, + "ValueWithoutInterest": 34015.800000000000000000000000 + }, + { + "Date": "2027-04-01T00:00:00", + "Value": 35364.941057373046875, + "ValueWithoutInterest": 34839.000000 + }, + { + "Date": "2027-05-01T00:00:00", + "Value": 36222.300290039062500000000001, + "ValueWithoutInterest": 35662.200000000000000000000000 + }, + { + "Date": "2027-06-01T00:00:00", + "Value": 37079.659522705078124999999999, + "ValueWithoutInterest": 36485.400000000000000000000000 + }, + { + "Date": "2027-07-01T00:00:00", + "Value": 37937.01875537109375, + "ValueWithoutInterest": 37308.60000 + }, + { + "Date": "2027-08-01T00:00:00", + "Value": 38794.377988037109375000000001, + "ValueWithoutInterest": 38131.800000000000000000000000 + }, + { + "Date": "2027-09-01T00:00:00", + "Value": 39651.737220703124999999999999, + "ValueWithoutInterest": 38955.000000000000000000000000 + }, + { + "Date": "2027-10-01T00:00:00", + "Value": 40509.096453369140625, + "ValueWithoutInterest": 39778.200000 + }, + { + "Date": "2027-11-01T00:00:00", + "Value": 41366.455686035156250000000001, + "ValueWithoutInterest": 40601.400000000000000000000000 + }, + { + "Date": "2027-12-01T00:00:00", + "Value": 42223.814918701171874999999999, + "ValueWithoutInterest": 41424.600000000000000000000000 + }, + { + "Date": "2028-01-01T00:00:00", + "Value": 43081.1741513671875, + "ValueWithoutInterest": 42247.8000 + }, + { + "Date": "2028-02-01T00:00:00", + "Value": 43949.250374441528320312500001, + "ValueWithoutInterest": 43071.000000000000000000000000 + }, + { + "Date": "2028-03-01T00:00:00", + "Value": 44817.326597515869140624999999, + "ValueWithoutInterest": 43894.200000000000000000000000 + }, + { + "Date": "2028-04-01T00:00:00", + "Value": 45685.4028205902099609375, + "ValueWithoutInterest": 44717.400000 + }, + { + "Date": "2028-05-01T00:00:00", + "Value": 46553.479043664550781250000001, + "ValueWithoutInterest": 45540.600000000000000000000000 + }, + { + "Date": "2028-06-01T00:00:00", + "Value": 47421.555266738891601562499999, + "ValueWithoutInterest": 46363.800000000000000000000000 + }, + { + "Date": "2028-07-01T00:00:00", + "Value": 48289.631489813232421875, + "ValueWithoutInterest": 47187.00000 + }, + { + "Date": "2028-08-01T00:00:00", + "Value": 49157.707712887573242187500001, + "ValueWithoutInterest": 48010.200000000000000000000000 + }, + { + "Date": "2028-09-01T00:00:00", + "Value": 50025.783935961914062499999999, + "ValueWithoutInterest": 48833.400000000000000000000000 + }, + { + "Date": "2028-10-01T00:00:00", + "Value": 50893.8601590362548828125, + "ValueWithoutInterest": 49656.600000 + }, + { + "Date": "2028-11-01T00:00:00", + "Value": 51761.936382110595703125000001, + "ValueWithoutInterest": 50479.800000000000000000000000 + }, + { + "Date": "2028-12-01T00:00:00", + "Value": 52630.012605184936523437499999, + "ValueWithoutInterest": 51303.000000000000000000000000 + }, + { + "Date": "2029-01-01T00:00:00", + "Value": 53498.08882825927734375, + "ValueWithoutInterest": 52126.2000 + }, + { + "Date": "2029-02-01T00:00:00", + "Value": 54377.016004122047424316406252, + "ValueWithoutInterest": 52949.400000000000000000000000 + }, + { + "Date": "2029-03-01T00:00:00", + "Value": 55255.943179984817504882812498, + "ValueWithoutInterest": 53772.600000000000000000000000 + }, + { + "Date": "2029-04-01T00:00:00", + "Value": 56134.87035584758758544921875, + "ValueWithoutInterest": 54595.800000 + }, + { + "Date": "2029-05-01T00:00:00", + "Value": 57013.797531710357666015625002, + "ValueWithoutInterest": 55419.000000000000000000000000 + }, + { + "Date": "2029-06-01T00:00:00", + "Value": 57892.724707573127746582031248, + "ValueWithoutInterest": 56242.200000000000000000000000 + }, + { + "Date": "2029-07-01T00:00:00", + "Value": 58771.6518834358978271484375, + "ValueWithoutInterest": 57065.40000 + }, + { + "Date": "2029-08-01T00:00:00", + "Value": 59650.579059298667907714843752, + "ValueWithoutInterest": 57888.600000000000000000000000 + }, + { + "Date": "2029-09-01T00:00:00", + "Value": 60529.506235161437988281249998, + "ValueWithoutInterest": 58711.800000000000000000000000 + }, + { + "Date": "2029-10-01T00:00:00", + "Value": 61408.43341102420806884765625, + "ValueWithoutInterest": 59535.000000 + }, + { + "Date": "2029-11-01T00:00:00", + "Value": 62287.360586886978149414062502, + "ValueWithoutInterest": 60358.200000000000000000000000 + }, + { + "Date": "2029-12-01T00:00:00", + "Value": 63166.287762749748229980468748, + "ValueWithoutInterest": 61181.400000000000000000000000 + }, + { + "Date": "2030-01-01T00:00:00", + "Value": 64045.214938612518310546875, + "ValueWithoutInterest": 62004.6000 + }, + { + "Date": "2030-02-01T00:00:00", + "Value": 64935.128704173573017120361330, + "ValueWithoutInterest": 62827.800000000000000000000000 + }, + { + "Date": "2030-03-01T00:00:00", + "Value": 65825.042469734627723693847654, + "ValueWithoutInterest": 63651.000000000000000000000000 + }, + { + "Date": "2030-04-01T00:00:00", + "Value": 66714.956235295682430267333984, + "ValueWithoutInterest": 64474.200000 + }, + { + "Date": "2030-05-01T00:00:00", + "Value": 67604.870000856737136840820315, + "ValueWithoutInterest": 65297.400000000000000000000000 + }, + { + "Date": "2030-06-01T00:00:00", + "Value": 68494.783766417791843414306638, + "ValueWithoutInterest": 66120.600000000000000000000000 + }, + { + "Date": "2030-07-01T00:00:00", + "Value": 69384.697531978846549987792969, + "ValueWithoutInterest": 66943.80000 + }, + { + "Date": "2030-08-01T00:00:00", + "Value": 70274.611297539901256561279299, + "ValueWithoutInterest": 67767.000000000000000000000000 + }, + { + "Date": "2030-09-01T00:00:00", + "Value": 71164.525063100955963134765623, + "ValueWithoutInterest": 68590.200000000000000000000000 + }, + { + "Date": "2030-10-01T00:00:00", + "Value": 72054.438828662010669708251953, + "ValueWithoutInterest": 69413.400000 + }, + { + "Date": "2030-11-01T00:00:00", + "Value": 72944.352594223065376281738283, + "ValueWithoutInterest": 70236.600000000000000000000000 + }, + { + "Date": "2030-12-01T00:00:00", + "Value": 73834.266359784120082855224607, + "ValueWithoutInterest": 71059.800000000000000000000000 + }, + { + "Date": "2031-01-01T00:00:00", + "Value": 74724.180125345174789428710938, + "ValueWithoutInterest": 71883.0000 + }, + { + "Date": "2031-02-01T00:00:00", + "Value": 75625.217812975742679834365848, + "ValueWithoutInterest": 72706.200000000000000000000000 + }, + { + "Date": "2031-03-01T00:00:00", + "Value": 76526.255500606310570240020750, + "ValueWithoutInterest": 73529.400000000000000000000000 + }, + { + "Date": "2031-04-01T00:00:00", + "Value": 77427.293188236878460645675660, + "ValueWithoutInterest": 74352.600000 + }, + { + "Date": "2031-05-01T00:00:00", + "Value": 78328.330875867446351051330569, + "ValueWithoutInterest": 75175.800000000000000000000000 + }, + { + "Date": "2031-06-01T00:00:00", + "Value": 79229.36856349801424145698547, + "ValueWithoutInterest": 75999.000000000000000000000000 + }, + { + "Date": "2031-07-01T00:00:00", + "Value": 80130.40625112858213186264038, + "ValueWithoutInterest": 76822.20000 + }, + { + "Date": "2031-08-01T00:00:00", + "Value": 81031.44393875915002226829529, + "ValueWithoutInterest": 77645.400000000000000000000000 + }, + { + "Date": "2031-09-01T00:00:00", + "Value": 81932.48162638971791267395019, + "ValueWithoutInterest": 78468.600000000000000000000000 + }, + { + "Date": "2031-10-01T00:00:00", + "Value": 82833.51931402028580307960510, + "ValueWithoutInterest": 79291.800000 + }, + { + "Date": "2031-11-01T00:00:00", + "Value": 83734.55700165085369348526001, + "ValueWithoutInterest": 80115.00000000000000000000000 + }, + { + "Date": "2031-12-01T00:00:00", + "Value": 84635.59468928142158389091492, + "ValueWithoutInterest": 80938.20000000000000000000000 + }, + { + "Date": "2032-01-01T00:00:00", + "Value": 85536.63237691198947429656982, + "ValueWithoutInterest": 81761.4000 + }, + { + "Date": "2032-02-01T00:00:00", + "Value": 86448.93303563793946333229542, + "ValueWithoutInterest": 82584.60000000000000000000000 + }, + { + "Date": "2032-03-01T00:00:00", + "Value": 87361.23369436388945236802100, + "ValueWithoutInterest": 83407.80000000000000000000000 + }, + { + "Date": "2032-04-01T00:00:00", + "Value": 88273.53435308983944140374660, + "ValueWithoutInterest": 84231.000000 + }, + { + "Date": "2032-05-01T00:00:00", + "Value": 89185.83501181578943043947220, + "ValueWithoutInterest": 85054.20000000000000000000000 + }, + { + "Date": "2032-06-01T00:00:00", + "Value": 90098.13567054173941947519778, + "ValueWithoutInterest": 85877.40000000000000000000000 + }, + { + "Date": "2032-07-01T00:00:00", + "Value": 91010.43632926768940851092338, + "ValueWithoutInterest": 86700.60000 + }, + { + "Date": "2032-08-01T00:00:00", + "Value": 91922.73698799363939754664898, + "ValueWithoutInterest": 87523.80000000000000000000000 + }, + { + "Date": "2032-09-01T00:00:00", + "Value": 92835.03764671958938658237457, + "ValueWithoutInterest": 88347.00000000000000000000000 + }, + { + "Date": "2032-10-01T00:00:00", + "Value": 93747.33830544553937561810016, + "ValueWithoutInterest": 89170.200000 + }, + { + "Date": "2032-11-01T00:00:00", + "Value": 94659.63896417148936465382576, + "ValueWithoutInterest": 89993.40000000000000000000000 + }, + { + "Date": "2032-12-01T00:00:00", + "Value": 95571.93962289743935368955135, + "ValueWithoutInterest": 90816.60000000000000000000000 + }, + { + "Date": "2033-01-01T00:00:00", + "Value": 96484.24028162338934272527694, + "ValueWithoutInterest": 91639.8000 + }, + { + "Date": "2033-02-01T00:00:00", + "Value": 97407.94469858341370662394911, + "ValueWithoutInterest": 92463.00000000000000000000000 + }, + { + "Date": "2033-03-01T00:00:00", + "Value": 98331.64911554343807052262126, + "ValueWithoutInterest": 93286.20000000000000000000000 + }, + { + "Date": "2033-04-01T00:00:00", + "Value": 99255.35353250346243442129343, + "ValueWithoutInterest": 94109.400000 + }, + { + "Date": "2033-05-01T00:00:00", + "Value": 100179.05794946348679831996560, + "ValueWithoutInterest": 94932.60000000000000000000000 + }, + { + "Date": "2033-06-01T00:00:00", + "Value": 101102.76236642351116221863775, + "ValueWithoutInterest": 95755.80000000000000000000000 + }, + { + "Date": "2033-07-01T00:00:00", + "Value": 102026.46678338353552611730992, + "ValueWithoutInterest": 96579.00000 + }, + { + "Date": "2033-08-01T00:00:00", + "Value": 102950.17120034355989001598209, + "ValueWithoutInterest": 97402.20000000000000000000000 + }, + { + "Date": "2033-09-01T00:00:00", + "Value": 103873.87561730358425391465424, + "ValueWithoutInterest": 98225.40000000000000000000000 + }, + { + "Date": "2033-10-01T00:00:00", + "Value": 104797.58003426360861781332641, + "ValueWithoutInterest": 99048.600000 + }, + { + "Date": "2033-11-01T00:00:00", + "Value": 105721.28445122363298171199858, + "ValueWithoutInterest": 99871.80000000000000000000000 + }, + { + "Date": "2033-12-01T00:00:00", + "Value": 106644.98886818365734561067074, + "ValueWithoutInterest": 100695.00000000000000000000000 + }, + { + "Date": "2034-01-01T00:00:00", + "Value": 107568.69328514368170950934290, + "ValueWithoutInterest": 101518.2000 + }, + { + "Date": "2034-02-01T00:00:00", + "Value": 108503.94400731570637795674847, + "ValueWithoutInterest": 102341.40000000000000000000000 + }, + { + "Date": "2034-03-01T00:00:00", + "Value": 109439.19472948773104640415403, + "ValueWithoutInterest": 103164.60000000000000000000000 + }, + { + "Date": "2034-04-01T00:00:00", + "Value": 110374.44545165975571485155960, + "ValueWithoutInterest": 103987.800000 + }, + { + "Date": "2034-05-01T00:00:00", + "Value": 111309.69617383178038329896517, + "ValueWithoutInterest": 104811.00000000000000000000000 + }, + { + "Date": "2034-06-01T00:00:00", + "Value": 112244.94689600380505174637072, + "ValueWithoutInterest": 105634.20000000000000000000000 + }, + { + "Date": "2034-07-01T00:00:00", + "Value": 113180.19761817582972019377629, + "ValueWithoutInterest": 106457.40000 + }, + { + "Date": "2034-08-01T00:00:00", + "Value": 114115.44834034785438864118186, + "ValueWithoutInterest": 107280.60000000000000000000000 + }, + { + "Date": "2034-09-01T00:00:00", + "Value": 115050.69906251987905708858742, + "ValueWithoutInterest": 108103.80000000000000000000000 + }, + { + "Date": "2034-10-01T00:00:00", + "Value": 115985.94978469190372553599299, + "ValueWithoutInterest": 108927.000000 + }, + { + "Date": "2034-11-01T00:00:00", + "Value": 116921.20050686392839398339856, + "ValueWithoutInterest": 109750.20000000000000000000000 + }, + { + "Date": "2034-12-01T00:00:00", + "Value": 117856.45122903595306243080412, + "ValueWithoutInterest": 110573.40000000000000000000000 + }, + { + "Date": "2035-01-01T00:00:00", + "Value": 118791.70195120797773087820969, + "ValueWithoutInterest": 111396.6000 + } + ] +} diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.ShouldCalculateResultWhenRetiring.snap b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.ShouldCalculateResultWhenRetiring.snap new file mode 100644 index 00000000..bc59ada2 --- /dev/null +++ b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.ShouldCalculateResultWhenRetiring.snap @@ -0,0 +1,40 @@ +{ + "DateOfRetirement": "2024-04-01T00:00:00", + "EffectiveSalary": 85000.0, + "InsuredSalary": 59275.00, + "RetirementCredit": 10669.5000, + "RetirementCreditFactor": 0.18, + "RetirementCapitalEndOfYear": 2667.375, + "FinalRetirementCapital": 2667.375, + "FinalRetirementCapitalWithoutInterest": 2667.375, + "RetirementPension": 181.0, + "DisabilityPension": 181.0, + "PartnerPension": 108.6, + "OrphanPension": 36.2, + "ChildPensionForDisabled": 36.2, + "RetirementCreditSequence": [ + { + "Age": 64, + "AmountRaw": 10669.5000 + }, + { + "Age": 64, + "AmountRaw": 10669.5000 + }, + { + "Age": 64, + "AmountRaw": 10669.5000 + }, + { + "Age": 65, + "AmountRaw": 10669.5000 + } + ], + "RetirementCapitalSequence": [ + { + "Date": "2024-04-01T00:00:00", + "Value": 2667.375, + "ValueWithoutInterest": 2667.375 + } + ] +} diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.ShouldReturnBenefitsCalculationResult.snap b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.ShouldReturnBenefitsCalculationResult.snap new file mode 100644 index 00000000..c7bed370 --- /dev/null +++ b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.ShouldReturnBenefitsCalculationResult.snap @@ -0,0 +1,1660 @@ +{ + "DateOfRetirement": "2039-09-01T00:00:00", + "EffectiveSalary": 100000.0, + "InsuredSalary": 62475.00, + "RetirementCredit": 9371.2500, + "RetirementCreditFactor": 0.15, + "RetirementCapitalEndOfYear": 9371.25, + "FinalRetirementCapital": 168595.77444068498599030583098, + "FinalRetirementCapitalWithoutInterest": 153747.30000000000000000000000, + "RetirementPension": 10115.746466441099159418349859, + "DisabilityPension": 9224.838000000000000000000000, + "PartnerPension": 5534.9028000000000000000000000, + "OrphanPension": 1844.9676000000000000000000000, + "ChildPensionForDisabled": 1844.9676000000000000000000000, + "RetirementCreditSequence": [ + { + "Age": 49, + "AmountRaw": 9371.2500 + }, + { + "Age": 49, + "AmountRaw": 9371.2500 + }, + { + "Age": 49, + "AmountRaw": 9371.2500 + }, + { + "Age": 49, + "AmountRaw": 9371.2500 + }, + { + "Age": 49, + "AmountRaw": 9371.2500 + }, + { + "Age": 49, + "AmountRaw": 9371.2500 + }, + { + "Age": 49, + "AmountRaw": 9371.2500 + }, + { + "Age": 49, + "AmountRaw": 9371.2500 + }, + { + "Age": 50, + "AmountRaw": 9371.2500 + }, + { + "Age": 50, + "AmountRaw": 9371.2500 + }, + { + "Age": 50, + "AmountRaw": 9371.2500 + }, + { + "Age": 50, + "AmountRaw": 9371.2500 + }, + { + "Age": 50, + "AmountRaw": 9371.2500 + }, + { + "Age": 50, + "AmountRaw": 9371.2500 + }, + { + "Age": 50, + "AmountRaw": 9371.2500 + }, + { + "Age": 50, + "AmountRaw": 9371.2500 + }, + { + "Age": 50, + "AmountRaw": 9371.2500 + }, + { + "Age": 50, + "AmountRaw": 9371.2500 + }, + { + "Age": 50, + "AmountRaw": 9371.2500 + }, + { + "Age": 50, + "AmountRaw": 9371.2500 + }, + { + "Age": 51, + "AmountRaw": 9371.2500 + }, + { + "Age": 51, + "AmountRaw": 9371.2500 + }, + { + "Age": 51, + "AmountRaw": 9371.2500 + }, + { + "Age": 51, + "AmountRaw": 9371.2500 + }, + { + "Age": 51, + "AmountRaw": 9878.4000 + }, + { + "Age": 51, + "AmountRaw": 9878.4000 + }, + { + "Age": 51, + "AmountRaw": 9878.4000 + }, + { + "Age": 51, + "AmountRaw": 9878.4000 + }, + { + "Age": 51, + "AmountRaw": 9878.4000 + }, + { + "Age": 51, + "AmountRaw": 9878.4000 + }, + { + "Age": 51, + "AmountRaw": 9878.4000 + }, + { + "Age": 51, + "AmountRaw": 9878.4000 + }, + { + "Age": 52, + "AmountRaw": 9878.4000 + }, + { + "Age": 52, + "AmountRaw": 9878.4000 + }, + { + "Age": 52, + "AmountRaw": 9878.4000 + }, + { + "Age": 52, + "AmountRaw": 9878.4000 + }, + { + "Age": 52, + "AmountRaw": 9878.4000 + }, + { + "Age": 52, + "AmountRaw": 9878.4000 + }, + { + "Age": 52, + "AmountRaw": 9878.4000 + }, + { + "Age": 52, + "AmountRaw": 9878.4000 + }, + { + "Age": 52, + "AmountRaw": 9878.4000 + }, + { + "Age": 52, + "AmountRaw": 9878.4000 + }, + { + "Age": 52, + "AmountRaw": 9878.4000 + }, + { + "Age": 52, + "AmountRaw": 9878.4000 + }, + { + "Age": 53, + "AmountRaw": 9878.4000 + }, + { + "Age": 53, + "AmountRaw": 9878.4000 + }, + { + "Age": 53, + "AmountRaw": 9878.4000 + }, + { + "Age": 53, + "AmountRaw": 9878.4000 + }, + { + "Age": 53, + "AmountRaw": 9878.4000 + }, + { + "Age": 53, + "AmountRaw": 9878.4000 + }, + { + "Age": 53, + "AmountRaw": 9878.4000 + }, + { + "Age": 53, + "AmountRaw": 9878.4000 + }, + { + "Age": 53, + "AmountRaw": 9878.4000 + }, + { + "Age": 53, + "AmountRaw": 9878.4000 + }, + { + "Age": 53, + "AmountRaw": 9878.4000 + }, + { + "Age": 53, + "AmountRaw": 9878.4000 + }, + { + "Age": 54, + "AmountRaw": 9878.4000 + }, + { + "Age": 54, + "AmountRaw": 9878.4000 + }, + { + "Age": 54, + "AmountRaw": 9878.4000 + }, + { + "Age": 54, + "AmountRaw": 9878.4000 + }, + { + "Age": 54, + "AmountRaw": 9878.4000 + }, + { + "Age": 54, + "AmountRaw": 9878.4000 + }, + { + "Age": 54, + "AmountRaw": 9878.4000 + }, + { + "Age": 54, + "AmountRaw": 9878.4000 + }, + { + "Age": 54, + "AmountRaw": 9878.4000 + }, + { + "Age": 54, + "AmountRaw": 9878.4000 + }, + { + "Age": 54, + "AmountRaw": 9878.4000 + }, + { + "Age": 54, + "AmountRaw": 9878.4000 + }, + { + "Age": 55, + "AmountRaw": 9878.4000 + }, + { + "Age": 55, + "AmountRaw": 9878.4000 + }, + { + "Age": 55, + "AmountRaw": 9878.4000 + }, + { + "Age": 55, + "AmountRaw": 9878.4000 + }, + { + "Age": 55, + "AmountRaw": 9878.4000 + }, + { + "Age": 55, + "AmountRaw": 9878.4000 + }, + { + "Age": 55, + "AmountRaw": 9878.4000 + }, + { + "Age": 55, + "AmountRaw": 9878.4000 + }, + { + "Age": 55, + "AmountRaw": 9878.4000 + }, + { + "Age": 55, + "AmountRaw": 9878.4000 + }, + { + "Age": 55, + "AmountRaw": 9878.4000 + }, + { + "Age": 55, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 56, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 57, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 58, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 59, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 60, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 61, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 62, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 63, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 64, + "AmountRaw": 9878.4000 + }, + { + "Age": 65, + "AmountRaw": 9878.4000 + } + ], + "RetirementCapitalSequence": [ + { + "Date": "2025-01-01T00:00:00", + "Value": 9371.25, + "ValueWithoutInterest": 9371.25 + }, + { + "Date": "2025-02-01T00:00:00", + "Value": 10161.949218750000000000000000, + "ValueWithoutInterest": 10152.187500000000000000000000 + }, + { + "Date": "2025-03-01T00:00:00", + "Value": 10952.648437500000000000000000, + "ValueWithoutInterest": 10933.125000000000000000000000 + }, + { + "Date": "2025-04-01T00:00:00", + "Value": 11743.34765625, + "ValueWithoutInterest": 11714.062500 + }, + { + "Date": "2025-05-01T00:00:00", + "Value": 12534.046875000000000000000000, + "ValueWithoutInterest": 12495.000000000000000000000000 + }, + { + "Date": "2025-06-01T00:00:00", + "Value": 13324.746093750000000000000000, + "ValueWithoutInterest": 13275.937500000000000000000000 + }, + { + "Date": "2025-07-01T00:00:00", + "Value": 14115.4453125, + "ValueWithoutInterest": 14056.87500 + }, + { + "Date": "2025-08-01T00:00:00", + "Value": 14906.144531250000000000000000, + "ValueWithoutInterest": 14837.812500000000000000000000 + }, + { + "Date": "2025-09-01T00:00:00", + "Value": 15696.843750000000000000000000, + "ValueWithoutInterest": 15618.750000000000000000000000 + }, + { + "Date": "2025-10-01T00:00:00", + "Value": 16487.54296875, + "ValueWithoutInterest": 16399.687500 + }, + { + "Date": "2025-11-01T00:00:00", + "Value": 17278.242187500000000000000000, + "ValueWithoutInterest": 17180.625000000000000000000000 + }, + { + "Date": "2025-12-01T00:00:00", + "Value": 18068.941406250000000000000000, + "ValueWithoutInterest": 17961.562500000000000000000000 + }, + { + "Date": "2026-01-01T00:00:00", + "Value": 18859.640625, + "ValueWithoutInterest": 18742.5000 + }, + { + "Date": "2026-02-01T00:00:00", + "Value": 19702.486083984375000000000001, + "ValueWithoutInterest": 19565.700000000000000000000000 + }, + { + "Date": "2026-03-01T00:00:00", + "Value": 20545.331542968749999999999999, + "ValueWithoutInterest": 20388.900000000000000000000000 + }, + { + "Date": "2026-04-01T00:00:00", + "Value": 21388.177001953125, + "ValueWithoutInterest": 21212.100000 + }, + { + "Date": "2026-05-01T00:00:00", + "Value": 22231.022460937500000000000001, + "ValueWithoutInterest": 22035.300000000000000000000000 + }, + { + "Date": "2026-06-01T00:00:00", + "Value": 23073.867919921874999999999999, + "ValueWithoutInterest": 22858.500000000000000000000000 + }, + { + "Date": "2026-07-01T00:00:00", + "Value": 23916.71337890625, + "ValueWithoutInterest": 23681.70000 + }, + { + "Date": "2026-08-01T00:00:00", + "Value": 24759.558837890625000000000001, + "ValueWithoutInterest": 24504.900000000000000000000000 + }, + { + "Date": "2026-09-01T00:00:00", + "Value": 25602.404296874999999999999999, + "ValueWithoutInterest": 25328.100000000000000000000000 + }, + { + "Date": "2026-10-01T00:00:00", + "Value": 26445.249755859375, + "ValueWithoutInterest": 26151.300000 + }, + { + "Date": "2026-11-01T00:00:00", + "Value": 27288.095214843750000000000001, + "ValueWithoutInterest": 26974.500000000000000000000000 + }, + { + "Date": "2026-12-01T00:00:00", + "Value": 28130.940673828124999999999999, + "ValueWithoutInterest": 27797.700000000000000000000000 + }, + { + "Date": "2027-01-01T00:00:00", + "Value": 28973.7861328125, + "ValueWithoutInterest": 28620.9000 + }, + { + "Date": "2027-02-01T00:00:00", + "Value": 29827.167160034179687500000001, + "ValueWithoutInterest": 29444.100000000000000000000000 + }, + { + "Date": "2027-03-01T00:00:00", + "Value": 30680.548187255859374999999999, + "ValueWithoutInterest": 30267.300000000000000000000000 + }, + { + "Date": "2027-04-01T00:00:00", + "Value": 31533.9292144775390625, + "ValueWithoutInterest": 31090.500000 + }, + { + "Date": "2027-05-01T00:00:00", + "Value": 32387.310241699218750000000001, + "ValueWithoutInterest": 31913.700000000000000000000000 + }, + { + "Date": "2027-06-01T00:00:00", + "Value": 33240.691268920898437499999999, + "ValueWithoutInterest": 32736.900000000000000000000000 + }, + { + "Date": "2027-07-01T00:00:00", + "Value": 34094.072296142578125, + "ValueWithoutInterest": 33560.10000 + }, + { + "Date": "2027-08-01T00:00:00", + "Value": 34947.453323364257812500000001, + "ValueWithoutInterest": 34383.300000000000000000000000 + }, + { + "Date": "2027-09-01T00:00:00", + "Value": 35800.834350585937499999999999, + "ValueWithoutInterest": 35206.500000000000000000000000 + }, + { + "Date": "2027-10-01T00:00:00", + "Value": 36654.2153778076171875, + "ValueWithoutInterest": 36029.700000 + }, + { + "Date": "2027-11-01T00:00:00", + "Value": 37507.596405029296875000000001, + "ValueWithoutInterest": 36852.900000000000000000000000 + }, + { + "Date": "2027-12-01T00:00:00", + "Value": 38360.977432250976562499999999, + "ValueWithoutInterest": 37676.100000000000000000000000 + }, + { + "Date": "2028-01-01T00:00:00", + "Value": 39214.35845947265625, + "ValueWithoutInterest": 38499.3000 + }, + { + "Date": "2028-02-01T00:00:00", + "Value": 40078.406749534606933593750001, + "ValueWithoutInterest": 39322.500000000000000000000000 + }, + { + "Date": "2028-03-01T00:00:00", + "Value": 40942.455039596557617187499999, + "ValueWithoutInterest": 40145.700000000000000000000000 + }, + { + "Date": "2028-04-01T00:00:00", + "Value": 41806.50332965850830078125, + "ValueWithoutInterest": 40968.900000 + }, + { + "Date": "2028-05-01T00:00:00", + "Value": 42670.551619720458984375000001, + "ValueWithoutInterest": 41792.100000000000000000000000 + }, + { + "Date": "2028-06-01T00:00:00", + "Value": 43534.599909782409667968749999, + "ValueWithoutInterest": 42615.300000000000000000000000 + }, + { + "Date": "2028-07-01T00:00:00", + "Value": 44398.6481998443603515625, + "ValueWithoutInterest": 43438.50000 + }, + { + "Date": "2028-08-01T00:00:00", + "Value": 45262.696489906311035156250001, + "ValueWithoutInterest": 44261.700000000000000000000000 + }, + { + "Date": "2028-09-01T00:00:00", + "Value": 46126.744779968261718749999999, + "ValueWithoutInterest": 45084.900000000000000000000000 + }, + { + "Date": "2028-10-01T00:00:00", + "Value": 46990.79307003021240234375, + "ValueWithoutInterest": 45908.100000 + }, + { + "Date": "2028-11-01T00:00:00", + "Value": 47854.841360092163085937500001, + "ValueWithoutInterest": 46731.300000000000000000000000 + }, + { + "Date": "2028-12-01T00:00:00", + "Value": 48718.889650154113769531249999, + "ValueWithoutInterest": 47554.500000000000000000000000 + }, + { + "Date": "2029-01-01T00:00:00", + "Value": 49582.937940216064453125, + "ValueWithoutInterest": 48377.7000 + }, + { + "Date": "2029-02-01T00:00:00", + "Value": 50457.786833903789520263671877, + "ValueWithoutInterest": 49200.900000000000000000000000 + }, + { + "Date": "2029-03-01T00:00:00", + "Value": 51332.635727591514587402343748, + "ValueWithoutInterest": 50024.100000000000000000000000 + }, + { + "Date": "2029-04-01T00:00:00", + "Value": 52207.484621279239654541015625, + "ValueWithoutInterest": 50847.300000 + }, + { + "Date": "2029-05-01T00:00:00", + "Value": 53082.333514966964721679687502, + "ValueWithoutInterest": 51670.500000000000000000000000 + }, + { + "Date": "2029-06-01T00:00:00", + "Value": 53957.182408654689788818359373, + "ValueWithoutInterest": 52493.700000000000000000000000 + }, + { + "Date": "2029-07-01T00:00:00", + "Value": 54832.03130234241485595703125, + "ValueWithoutInterest": 53316.90000 + }, + { + "Date": "2029-08-01T00:00:00", + "Value": 55706.880196030139923095703127, + "ValueWithoutInterest": 54140.100000000000000000000000 + }, + { + "Date": "2029-09-01T00:00:00", + "Value": 56581.729089717864990234374998, + "ValueWithoutInterest": 54963.300000000000000000000000 + }, + { + "Date": "2029-10-01T00:00:00", + "Value": 57456.577983405590057373046875, + "ValueWithoutInterest": 55786.500000 + }, + { + "Date": "2029-11-01T00:00:00", + "Value": 58331.426877093315124511718752, + "ValueWithoutInterest": 56609.700000000000000000000000 + }, + { + "Date": "2029-12-01T00:00:00", + "Value": 59206.275770781040191650390623, + "ValueWithoutInterest": 57432.900000000000000000000000 + }, + { + "Date": "2030-01-01T00:00:00", + "Value": 60081.1246644687652587890625, + "ValueWithoutInterest": 58256.1000 + }, + { + "Date": "2030-02-01T00:00:00", + "Value": 60966.909169327586889266967775, + "ValueWithoutInterest": 59079.300000000000000000000000 + }, + { + "Date": "2030-03-01T00:00:00", + "Value": 61852.693674186408519744873045, + "ValueWithoutInterest": 59902.500000000000000000000000 + }, + { + "Date": "2030-04-01T00:00:00", + "Value": 62738.478179045230150222778320, + "ValueWithoutInterest": 60725.700000 + }, + { + "Date": "2030-05-01T00:00:00", + "Value": 63624.262683904051780700683596, + "ValueWithoutInterest": 61548.900000000000000000000000 + }, + { + "Date": "2030-06-01T00:00:00", + "Value": 64510.047188762873411178588865, + "ValueWithoutInterest": 62372.100000000000000000000000 + }, + { + "Date": "2030-07-01T00:00:00", + "Value": 65395.831693621695041656494141, + "ValueWithoutInterest": 63195.30000 + }, + { + "Date": "2030-08-01T00:00:00", + "Value": 66281.616198480516672134399416, + "ValueWithoutInterest": 64018.500000000000000000000000 + }, + { + "Date": "2030-09-01T00:00:00", + "Value": 67167.400703339338302612304685, + "ValueWithoutInterest": 64841.700000000000000000000000 + }, + { + "Date": "2030-10-01T00:00:00", + "Value": 68053.185208198159933090209961, + "ValueWithoutInterest": 65664.900000 + }, + { + "Date": "2030-11-01T00:00:00", + "Value": 68938.969713056981563568115236, + "ValueWithoutInterest": 66488.100000000000000000000000 + }, + { + "Date": "2030-12-01T00:00:00", + "Value": 69824.754217915803194046020506, + "ValueWithoutInterest": 67311.300000000000000000000000 + }, + { + "Date": "2031-01-01T00:00:00", + "Value": 70710.538722774624824523925781, + "ValueWithoutInterest": 68134.5000 + }, + { + "Date": "2031-02-01T00:00:00", + "Value": 71607.395533944181725382804873, + "ValueWithoutInterest": 68957.700000000000000000000000 + }, + { + "Date": "2031-03-01T00:00:00", + "Value": 72504.252345113738626241683957, + "ValueWithoutInterest": 69780.900000000000000000000000 + }, + { + "Date": "2031-04-01T00:00:00", + "Value": 73401.109156283295527100563049, + "ValueWithoutInterest": 70604.100000 + }, + { + "Date": "2031-05-01T00:00:00", + "Value": 74297.965967452852427959442141, + "ValueWithoutInterest": 71427.300000000000000000000000 + }, + { + "Date": "2031-06-01T00:00:00", + "Value": 75194.822778622409328818321225, + "ValueWithoutInterest": 72250.500000000000000000000000 + }, + { + "Date": "2031-07-01T00:00:00", + "Value": 76091.679589791966229677200317, + "ValueWithoutInterest": 73073.70000 + }, + { + "Date": "2031-08-01T00:00:00", + "Value": 76988.536400961523130536079409, + "ValueWithoutInterest": 73896.900000000000000000000000 + }, + { + "Date": "2031-09-01T00:00:00", + "Value": 77885.393212131080031394958493, + "ValueWithoutInterest": 74720.100000000000000000000000 + }, + { + "Date": "2031-10-01T00:00:00", + "Value": 78782.250023300636932253837585, + "ValueWithoutInterest": 75543.300000 + }, + { + "Date": "2031-11-01T00:00:00", + "Value": 79679.10683447019383311271668, + "ValueWithoutInterest": 76366.500000000000000000000000 + }, + { + "Date": "2031-12-01T00:00:00", + "Value": 80575.96364563975073397159576, + "ValueWithoutInterest": 77189.700000000000000000000000 + }, + { + "Date": "2032-01-01T00:00:00", + "Value": 81472.82045680930763483047485, + "ValueWithoutInterest": 78012.9000 + }, + { + "Date": "2032-02-01T00:00:00", + "Value": 82380.88797811848399695008993, + "ValueWithoutInterest": 78836.100000000000000000000000 + }, + { + "Date": "2032-03-01T00:00:00", + "Value": 83288.95549942766035906970500, + "ValueWithoutInterest": 79659.30000000000000000000000 + }, + { + "Date": "2032-04-01T00:00:00", + "Value": 84197.02302073683672118932008, + "ValueWithoutInterest": 80482.500000 + }, + { + "Date": "2032-05-01T00:00:00", + "Value": 85105.09054204601308330893516, + "ValueWithoutInterest": 81305.70000000000000000000000 + }, + { + "Date": "2032-06-01T00:00:00", + "Value": 86013.15806335518944542855024, + "ValueWithoutInterest": 82128.90000000000000000000000 + }, + { + "Date": "2032-07-01T00:00:00", + "Value": 86921.22558466436580754816532, + "ValueWithoutInterest": 82952.10000 + }, + { + "Date": "2032-08-01T00:00:00", + "Value": 87829.29310597354216966778040, + "ValueWithoutInterest": 83775.30000000000000000000000 + }, + { + "Date": "2032-09-01T00:00:00", + "Value": 88737.36062728271853178739547, + "ValueWithoutInterest": 84598.50000000000000000000000 + }, + { + "Date": "2032-10-01T00:00:00", + "Value": 89645.42814859189489390701055, + "ValueWithoutInterest": 85421.700000 + }, + { + "Date": "2032-11-01T00:00:00", + "Value": 90553.49566990107125602662563, + "ValueWithoutInterest": 86244.90000000000000000000000 + }, + { + "Date": "2032-12-01T00:00:00", + "Value": 91461.56319121024761814624070, + "ValueWithoutInterest": 87068.10000000000000000000000 + }, + { + "Date": "2033-01-01T00:00:00", + "Value": 92369.63071251942398026585579, + "ValueWithoutInterest": 87891.3000 + }, + { + "Date": "2033-02-01T00:00:00", + "Value": 93289.04907784496504691196606, + "ValueWithoutInterest": 88714.50000000000000000000000 + }, + { + "Date": "2033-03-01T00:00:00", + "Value": 94208.46744317050611355807632, + "ValueWithoutInterest": 89537.70000000000000000000000 + }, + { + "Date": "2033-04-01T00:00:00", + "Value": 95127.88580849604718020418659, + "ValueWithoutInterest": 90360.900000 + }, + { + "Date": "2033-05-01T00:00:00", + "Value": 96047.30417382158824685029686, + "ValueWithoutInterest": 91184.10000000000000000000000 + }, + { + "Date": "2033-06-01T00:00:00", + "Value": 96966.72253914712931349640712, + "ValueWithoutInterest": 92007.30000000000000000000000 + }, + { + "Date": "2033-07-01T00:00:00", + "Value": 97886.14090447267038014251739, + "ValueWithoutInterest": 92830.50000 + }, + { + "Date": "2033-08-01T00:00:00", + "Value": 98805.55926979821144678862766, + "ValueWithoutInterest": 93653.70000000000000000000000 + }, + { + "Date": "2033-09-01T00:00:00", + "Value": 99724.97763512375251343473792, + "ValueWithoutInterest": 94476.90000000000000000000000 + }, + { + "Date": "2033-10-01T00:00:00", + "Value": 100644.39600044929358008084819, + "ValueWithoutInterest": 95300.100000 + }, + { + "Date": "2033-11-01T00:00:00", + "Value": 101563.81436577483464672695846, + "ValueWithoutInterest": 96123.30000000000000000000000 + }, + { + "Date": "2033-12-01T00:00:00", + "Value": 102483.23273110037571337306872, + "ValueWithoutInterest": 96946.50000000000000000000000 + }, + { + "Date": "2034-01-01T00:00:00", + "Value": 103402.65109642591678001917899, + "ValueWithoutInterest": 97769.7000 + }, + { + "Date": "2034-02-01T00:00:00", + "Value": 104333.56219131802710999836564, + "ValueWithoutInterest": 98592.90000000000000000000000 + }, + { + "Date": "2034-03-01T00:00:00", + "Value": 105264.47328621013743997755228, + "ValueWithoutInterest": 99416.10000000000000000000000 + }, + { + "Date": "2034-04-01T00:00:00", + "Value": 106195.38438110224776995673892, + "ValueWithoutInterest": 100239.300000 + }, + { + "Date": "2034-05-01T00:00:00", + "Value": 107126.29547599435809993592557, + "ValueWithoutInterest": 101062.50000000000000000000000 + }, + { + "Date": "2034-06-01T00:00:00", + "Value": 108057.20657088646842991511221, + "ValueWithoutInterest": 101885.70000000000000000000000 + }, + { + "Date": "2034-07-01T00:00:00", + "Value": 108988.11766577857875989429886, + "ValueWithoutInterest": 102708.90000 + }, + { + "Date": "2034-08-01T00:00:00", + "Value": 109919.02876067068908987348551, + "ValueWithoutInterest": 103532.10000000000000000000000 + }, + { + "Date": "2034-09-01T00:00:00", + "Value": 110849.93985556279941985267214, + "ValueWithoutInterest": 104355.30000000000000000000000 + }, + { + "Date": "2034-10-01T00:00:00", + "Value": 111780.85095045490974983185879, + "ValueWithoutInterest": 105178.500000 + }, + { + "Date": "2034-11-01T00:00:00", + "Value": 112711.76204534702007981104544, + "ValueWithoutInterest": 106001.70000000000000000000000 + }, + { + "Date": "2034-12-01T00:00:00", + "Value": 113642.67314023913040979023208, + "ValueWithoutInterest": 106824.90000000000000000000000 + }, + { + "Date": "2035-01-01T00:00:00", + "Value": 114573.58423513124073976941873, + "ValueWithoutInterest": 107648.1000 + }, + { + "Date": "2035-02-01T00:00:00", + "Value": 115516.13171870950244887334521, + "ValueWithoutInterest": 108471.30000000000000000000000 + }, + { + "Date": "2035-03-01T00:00:00", + "Value": 116458.67920228776415797727168, + "ValueWithoutInterest": 109294.50000000000000000000000 + }, + { + "Date": "2035-04-01T00:00:00", + "Value": 117401.22668586602586708119816, + "ValueWithoutInterest": 110117.700000 + }, + { + "Date": "2035-05-01T00:00:00", + "Value": 118343.77416944428757618512465, + "ValueWithoutInterest": 110940.90000000000000000000000 + }, + { + "Date": "2035-06-01T00:00:00", + "Value": 119286.32165302254928528905112, + "ValueWithoutInterest": 111764.10000000000000000000000 + }, + { + "Date": "2035-07-01T00:00:00", + "Value": 120228.86913660081099439297760, + "ValueWithoutInterest": 112587.30000 + }, + { + "Date": "2035-08-01T00:00:00", + "Value": 121171.41662017907270349690408, + "ValueWithoutInterest": 113410.50000000000000000000000 + }, + { + "Date": "2035-09-01T00:00:00", + "Value": 122113.96410375733441260083055, + "ValueWithoutInterest": 114233.70000000000000000000000 + }, + { + "Date": "2035-10-01T00:00:00", + "Value": 123056.51158733559612170475703, + "ValueWithoutInterest": 115056.900000 + }, + { + "Date": "2035-11-01T00:00:00", + "Value": 123999.05907091385783080868351, + "ValueWithoutInterest": 115880.10000000000000000000000 + }, + { + "Date": "2035-12-01T00:00:00", + "Value": 124941.60655449211953991260998, + "ValueWithoutInterest": 116703.30000000000000000000000 + }, + { + "Date": "2036-01-01T00:00:00", + "Value": 125884.15403807038124901653646, + "ValueWithoutInterest": 117526.5000 + }, + { + "Date": "2036-02-01T00:00:00", + "Value": 126838.48336519337122948426202, + "ValueWithoutInterest": 118349.70000000000000000000000 + }, + { + "Date": "2036-03-01T00:00:00", + "Value": 127792.81269231636120995198757, + "ValueWithoutInterest": 119172.90000000000000000000000 + }, + { + "Date": "2036-04-01T00:00:00", + "Value": 128747.14201943935119041971314, + "ValueWithoutInterest": 119996.100000 + }, + { + "Date": "2036-05-01T00:00:00", + "Value": 129701.47134656234117088743870, + "ValueWithoutInterest": 120819.30000000000000000000000 + }, + { + "Date": "2036-06-01T00:00:00", + "Value": 130655.80067368533115135516425, + "ValueWithoutInterest": 121642.50000000000000000000000 + }, + { + "Date": "2036-07-01T00:00:00", + "Value": 131610.13000080832113182288981, + "ValueWithoutInterest": 122465.70000 + }, + { + "Date": "2036-08-01T00:00:00", + "Value": 132564.45932793131111229061538, + "ValueWithoutInterest": 123288.90000000000000000000000 + }, + { + "Date": "2036-09-01T00:00:00", + "Value": 133518.78865505430109275834093, + "ValueWithoutInterest": 124112.10000000000000000000000 + }, + { + "Date": "2036-10-01T00:00:00", + "Value": 134473.11798217729107322606649, + "ValueWithoutInterest": 124935.300000 + }, + { + "Date": "2036-11-01T00:00:00", + "Value": 135427.44730930028105369379205, + "ValueWithoutInterest": 125758.50000000000000000000000 + }, + { + "Date": "2036-12-01T00:00:00", + "Value": 136381.77663642327103416151760, + "ValueWithoutInterest": 126581.70000000000000000000000 + }, + { + "Date": "2037-01-01T00:00:00", + "Value": 137336.10596354626101462924317, + "ValueWithoutInterest": 127404.9000 + }, + { + "Date": "2037-02-01T00:00:00", + "Value": 138302.36440725828836985281530, + "ValueWithoutInterest": 128228.10000000000000000000000 + }, + { + "Date": "2037-03-01T00:00:00", + "Value": 139268.62285097031572507638742, + "ValueWithoutInterest": 129051.30000000000000000000000 + }, + { + "Date": "2037-04-01T00:00:00", + "Value": 140234.88129468234308029995955, + "ValueWithoutInterest": 129874.500000 + }, + { + "Date": "2037-05-01T00:00:00", + "Value": 141201.13973839437043552353169, + "ValueWithoutInterest": 130697.70000000000000000000000 + }, + { + "Date": "2037-06-01T00:00:00", + "Value": 142167.39818210639779074710381, + "ValueWithoutInterest": 131520.90000000000000000000000 + }, + { + "Date": "2037-07-01T00:00:00", + "Value": 143133.65662581842514597067594, + "ValueWithoutInterest": 132344.10000 + }, + { + "Date": "2037-08-01T00:00:00", + "Value": 144099.91506953045250119424807, + "ValueWithoutInterest": 133167.30000000000000000000000 + }, + { + "Date": "2037-09-01T00:00:00", + "Value": 145066.17351324247985641782019, + "ValueWithoutInterest": 133990.50000000000000000000000 + }, + { + "Date": "2037-10-01T00:00:00", + "Value": 146032.43195695450721164139232, + "ValueWithoutInterest": 134813.700000 + }, + { + "Date": "2037-11-01T00:00:00", + "Value": 146998.69040066653456686496446, + "ValueWithoutInterest": 135636.90000000000000000000000 + }, + { + "Date": "2037-12-01T00:00:00", + "Value": 147964.94884437856192208853658, + "ValueWithoutInterest": 136460.10000000000000000000000 + }, + { + "Date": "2038-01-01T00:00:00", + "Value": 148931.20728809058927731210871, + "ValueWithoutInterest": 137283.3000 + }, + { + "Date": "2038-02-01T00:00:00", + "Value": 149909.54396234901697447597549, + "ValueWithoutInterest": 138106.50000000000000000000000 + }, + { + "Date": "2038-03-01T00:00:00", + "Value": 150887.88063660744467163984226, + "ValueWithoutInterest": 138929.70000000000000000000000 + }, + { + "Date": "2038-04-01T00:00:00", + "Value": 151866.21731086587236880370905, + "ValueWithoutInterest": 139752.900000 + }, + { + "Date": "2038-05-01T00:00:00", + "Value": 152844.55398512430006596757583, + "ValueWithoutInterest": 140576.10000000000000000000000 + }, + { + "Date": "2038-06-01T00:00:00", + "Value": 153822.89065938272776313144260, + "ValueWithoutInterest": 141399.30000000000000000000000 + }, + { + "Date": "2038-07-01T00:00:00", + "Value": 154801.22733364115546029530939, + "ValueWithoutInterest": 142222.50000 + }, + { + "Date": "2038-08-01T00:00:00", + "Value": 155779.56400789958315745917617, + "ValueWithoutInterest": 143045.70000000000000000000000 + }, + { + "Date": "2038-09-01T00:00:00", + "Value": 156757.90068215801085462304294, + "ValueWithoutInterest": 143868.90000000000000000000000 + }, + { + "Date": "2038-10-01T00:00:00", + "Value": 157736.23735641643855178690973, + "ValueWithoutInterest": 144692.100000 + }, + { + "Date": "2038-11-01T00:00:00", + "Value": 158714.57403067486624895077651, + "ValueWithoutInterest": 145515.30000000000000000000000 + }, + { + "Date": "2038-12-01T00:00:00", + "Value": 159692.91070493329394611464328, + "ValueWithoutInterest": 146338.50000000000000000000000 + }, + { + "Date": "2039-01-01T00:00:00", + "Value": 160671.24737919172164327851007, + "ValueWithoutInterest": 147161.7000 + }, + { + "Date": "2039-02-01T00:00:00", + "Value": 161661.81326187837968665692519, + "ValueWithoutInterest": 147984.90000000000000000000000 + }, + { + "Date": "2039-03-01T00:00:00", + "Value": 162652.37914456503773003534029, + "ValueWithoutInterest": 148808.10000000000000000000000 + }, + { + "Date": "2039-04-01T00:00:00", + "Value": 163642.94502725169577341375541, + "ValueWithoutInterest": 149631.300000 + }, + { + "Date": "2039-05-01T00:00:00", + "Value": 164633.51090993835381679217053, + "ValueWithoutInterest": 150454.50000000000000000000000 + }, + { + "Date": "2039-06-01T00:00:00", + "Value": 165624.07679262501186017058564, + "ValueWithoutInterest": 151277.70000000000000000000000 + }, + { + "Date": "2039-07-01T00:00:00", + "Value": 166614.64267531166990354900076, + "ValueWithoutInterest": 152100.90000 + }, + { + "Date": "2039-08-01T00:00:00", + "Value": 167605.20855799832794692741588, + "ValueWithoutInterest": 152924.10000000000000000000000 + }, + { + "Date": "2039-09-01T00:00:00", + "Value": 168595.77444068498599030583098, + "ValueWithoutInterest": 153747.30000000000000000000000 + } + ] +} diff --git a/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.ShouldReturnBenefitsIfBelowSalaryThreshold.snap b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.ShouldReturnBenefitsIfBelowSalaryThreshold.snap new file mode 100644 index 00000000..786c66f4 --- /dev/null +++ b/src/BvgCalculator.Tests/__snapshots__/BvgRevisionCalculatorTests.ShouldReturnBenefitsIfBelowSalaryThreshold.snap @@ -0,0 +1,1075 @@ +{ + "DateOfRetirement": "2034-04-01T00:00:00", + "EffectiveSalary": 20000.0, + "InsuredSalary": 0.00, + "RetirementCredit": 0.0000, + "RetirementCreditFactor": 0.18, + "RetirementCapitalEndOfYear": 0.0, + "FinalRetirementCapital": 19342.421548370682096481323242, + "FinalRetirementCapitalWithoutInterest": 18480.000000, + "RetirementPension": 1160.5452929022409257888793945, + "DisabilityPension": 1108.80000000, + "PartnerPension": 665.280000000, + "OrphanPension": 221.760000000, + "ChildPensionForDisabled": 221.760000000, + "RetirementCreditSequence": [ + { + "Age": 54, + "AmountRaw": 0.0000 + }, + { + "Age": 54, + "AmountRaw": 0.0000 + }, + { + "Age": 54, + "AmountRaw": 0.0000 + }, + { + "Age": 55, + "AmountRaw": 0.0000 + }, + { + "Age": 55, + "AmountRaw": 0.0000 + }, + { + "Age": 55, + "AmountRaw": 0.0000 + }, + { + "Age": 55, + "AmountRaw": 0.0000 + }, + { + "Age": 55, + "AmountRaw": 0.0000 + }, + { + "Age": 55, + "AmountRaw": 0.0000 + }, + { + "Age": 55, + "AmountRaw": 0.0000 + }, + { + "Age": 55, + "AmountRaw": 0.0000 + }, + { + "Age": 55, + "AmountRaw": 0.0000 + }, + { + "Age": 55, + "AmountRaw": 0.0000 + }, + { + "Age": 55, + "AmountRaw": 0.0000 + }, + { + "Age": 55, + "AmountRaw": 0.0000 + }, + { + "Age": 56, + "AmountRaw": 0.0000 + }, + { + "Age": 56, + "AmountRaw": 0.0000 + }, + { + "Age": 56, + "AmountRaw": 0.0000 + }, + { + "Age": 56, + "AmountRaw": 0.0000 + }, + { + "Age": 56, + "AmountRaw": 0.0000 + }, + { + "Age": 56, + "AmountRaw": 0.0000 + }, + { + "Age": 56, + "AmountRaw": 0.0000 + }, + { + "Age": 56, + "AmountRaw": 0.0000 + }, + { + "Age": 56, + "AmountRaw": 0.0000 + }, + { + "Age": 56, + "AmountRaw": 2240.0000 + }, + { + "Age": 56, + "AmountRaw": 2240.0000 + }, + { + "Age": 56, + "AmountRaw": 2240.0000 + }, + { + "Age": 57, + "AmountRaw": 2240.0000 + }, + { + "Age": 57, + "AmountRaw": 2240.0000 + }, + { + "Age": 57, + "AmountRaw": 2240.0000 + }, + { + "Age": 57, + "AmountRaw": 2240.0000 + }, + { + "Age": 57, + "AmountRaw": 2240.0000 + }, + { + "Age": 57, + "AmountRaw": 2240.0000 + }, + { + "Age": 57, + "AmountRaw": 2240.0000 + }, + { + "Age": 57, + "AmountRaw": 2240.0000 + }, + { + "Age": 57, + "AmountRaw": 2240.0000 + }, + { + "Age": 57, + "AmountRaw": 2240.0000 + }, + { + "Age": 57, + "AmountRaw": 2240.0000 + }, + { + "Age": 57, + "AmountRaw": 2240.0000 + }, + { + "Age": 58, + "AmountRaw": 2240.0000 + }, + { + "Age": 58, + "AmountRaw": 2240.0000 + }, + { + "Age": 58, + "AmountRaw": 2240.0000 + }, + { + "Age": 58, + "AmountRaw": 2240.0000 + }, + { + "Age": 58, + "AmountRaw": 2240.0000 + }, + { + "Age": 58, + "AmountRaw": 2240.0000 + }, + { + "Age": 58, + "AmountRaw": 2240.0000 + }, + { + "Age": 58, + "AmountRaw": 2240.0000 + }, + { + "Age": 58, + "AmountRaw": 2240.0000 + }, + { + "Age": 58, + "AmountRaw": 2240.0000 + }, + { + "Age": 58, + "AmountRaw": 2240.0000 + }, + { + "Age": 58, + "AmountRaw": 2240.0000 + }, + { + "Age": 59, + "AmountRaw": 2240.0000 + }, + { + "Age": 59, + "AmountRaw": 2240.0000 + }, + { + "Age": 59, + "AmountRaw": 2240.0000 + }, + { + "Age": 59, + "AmountRaw": 2240.0000 + }, + { + "Age": 59, + "AmountRaw": 2240.0000 + }, + { + "Age": 59, + "AmountRaw": 2240.0000 + }, + { + "Age": 59, + "AmountRaw": 2240.0000 + }, + { + "Age": 59, + "AmountRaw": 2240.0000 + }, + { + "Age": 59, + "AmountRaw": 2240.0000 + }, + { + "Age": 59, + "AmountRaw": 2240.0000 + }, + { + "Age": 59, + "AmountRaw": 2240.0000 + }, + { + "Age": 59, + "AmountRaw": 2240.0000 + }, + { + "Age": 60, + "AmountRaw": 2240.0000 + }, + { + "Age": 60, + "AmountRaw": 2240.0000 + }, + { + "Age": 60, + "AmountRaw": 2240.0000 + }, + { + "Age": 60, + "AmountRaw": 2240.0000 + }, + { + "Age": 60, + "AmountRaw": 2240.0000 + }, + { + "Age": 60, + "AmountRaw": 2240.0000 + }, + { + "Age": 60, + "AmountRaw": 2240.0000 + }, + { + "Age": 60, + "AmountRaw": 2240.0000 + }, + { + "Age": 60, + "AmountRaw": 2240.0000 + }, + { + "Age": 60, + "AmountRaw": 2240.0000 + }, + { + "Age": 60, + "AmountRaw": 2240.0000 + }, + { + "Age": 60, + "AmountRaw": 2240.0000 + }, + { + "Age": 61, + "AmountRaw": 2240.0000 + }, + { + "Age": 61, + "AmountRaw": 2240.0000 + }, + { + "Age": 61, + "AmountRaw": 2240.0000 + }, + { + "Age": 61, + "AmountRaw": 2240.0000 + }, + { + "Age": 61, + "AmountRaw": 2240.0000 + }, + { + "Age": 61, + "AmountRaw": 2240.0000 + }, + { + "Age": 61, + "AmountRaw": 2240.0000 + }, + { + "Age": 61, + "AmountRaw": 2240.0000 + }, + { + "Age": 61, + "AmountRaw": 2240.0000 + }, + { + "Age": 61, + "AmountRaw": 2240.0000 + }, + { + "Age": 61, + "AmountRaw": 2240.0000 + }, + { + "Age": 61, + "AmountRaw": 2240.0000 + }, + { + "Age": 62, + "AmountRaw": 2240.0000 + }, + { + "Age": 62, + "AmountRaw": 2240.0000 + }, + { + "Age": 62, + "AmountRaw": 2240.0000 + }, + { + "Age": 62, + "AmountRaw": 2240.0000 + }, + { + "Age": 62, + "AmountRaw": 2240.0000 + }, + { + "Age": 62, + "AmountRaw": 2240.0000 + }, + { + "Age": 62, + "AmountRaw": 2240.0000 + }, + { + "Age": 62, + "AmountRaw": 2240.0000 + }, + { + "Age": 62, + "AmountRaw": 2240.0000 + }, + { + "Age": 62, + "AmountRaw": 2240.0000 + }, + { + "Age": 62, + "AmountRaw": 2240.0000 + }, + { + "Age": 62, + "AmountRaw": 2240.0000 + }, + { + "Age": 63, + "AmountRaw": 2240.0000 + }, + { + "Age": 63, + "AmountRaw": 2240.0000 + }, + { + "Age": 63, + "AmountRaw": 2240.0000 + }, + { + "Age": 63, + "AmountRaw": 2240.0000 + }, + { + "Age": 63, + "AmountRaw": 2240.0000 + }, + { + "Age": 63, + "AmountRaw": 2240.0000 + }, + { + "Age": 63, + "AmountRaw": 2240.0000 + }, + { + "Age": 63, + "AmountRaw": 2240.0000 + }, + { + "Age": 63, + "AmountRaw": 2240.0000 + }, + { + "Age": 63, + "AmountRaw": 2240.0000 + }, + { + "Age": 63, + "AmountRaw": 2240.0000 + }, + { + "Age": 63, + "AmountRaw": 2240.0000 + }, + { + "Age": 64, + "AmountRaw": 2240.0000 + }, + { + "Age": 64, + "AmountRaw": 2240.0000 + }, + { + "Age": 64, + "AmountRaw": 2240.0000 + }, + { + "Age": 64, + "AmountRaw": 2240.0000 + }, + { + "Age": 64, + "AmountRaw": 2240.0000 + }, + { + "Age": 64, + "AmountRaw": 2240.0000 + }, + { + "Age": 64, + "AmountRaw": 2240.0000 + }, + { + "Age": 64, + "AmountRaw": 2240.0000 + }, + { + "Age": 64, + "AmountRaw": 2240.0000 + }, + { + "Age": 64, + "AmountRaw": 2240.0000 + }, + { + "Age": 64, + "AmountRaw": 2240.0000 + }, + { + "Age": 64, + "AmountRaw": 2240.0000 + }, + { + "Age": 65, + "AmountRaw": 2240.0000 + } + ], + "RetirementCapitalSequence": [ + { + "Date": "2025-01-01T00:00:00", + "Value": 0.0, + "ValueWithoutInterest": 0.0 + }, + { + "Date": "2025-02-01T00:00:00", + "Value": 0.0, + "ValueWithoutInterest": 0.0 + }, + { + "Date": "2025-03-01T00:00:00", + "Value": 0.0, + "ValueWithoutInterest": 0.0 + }, + { + "Date": "2025-04-01T00:00:00", + "Value": 0.000000, + "ValueWithoutInterest": 0.000000 + }, + { + "Date": "2025-05-01T00:00:00", + "Value": 0.0, + "ValueWithoutInterest": 0.0 + }, + { + "Date": "2025-06-01T00:00:00", + "Value": 0.0, + "ValueWithoutInterest": 0.0 + }, + { + "Date": "2025-07-01T00:00:00", + "Value": 0.00000, + "ValueWithoutInterest": 0.00000 + }, + { + "Date": "2025-08-01T00:00:00", + "Value": 0.0, + "ValueWithoutInterest": 0.0 + }, + { + "Date": "2025-09-01T00:00:00", + "Value": 0.0, + "ValueWithoutInterest": 0.0 + }, + { + "Date": "2025-10-01T00:00:00", + "Value": 0.000000, + "ValueWithoutInterest": 0.000000 + }, + { + "Date": "2025-11-01T00:00:00", + "Value": 0.0, + "ValueWithoutInterest": 0.0 + }, + { + "Date": "2025-12-01T00:00:00", + "Value": 0.0, + "ValueWithoutInterest": 0.0 + }, + { + "Date": "2026-01-01T00:00:00", + "Value": 0.0000, + "ValueWithoutInterest": 0.0000 + }, + { + "Date": "2026-02-01T00:00:00", + "Value": 186.66666666666666666666666659, + "ValueWithoutInterest": 186.66666666666666666666666659 + }, + { + "Date": "2026-03-01T00:00:00", + "Value": 373.33333333333333333333333341, + "ValueWithoutInterest": 373.33333333333333333333333341 + }, + { + "Date": "2026-04-01T00:00:00", + "Value": 560.0000000000, + "ValueWithoutInterest": 560.000000 + }, + { + "Date": "2026-05-01T00:00:00", + "Value": 746.66666666666666666666666659, + "ValueWithoutInterest": 746.66666666666666666666666659 + }, + { + "Date": "2026-06-01T00:00:00", + "Value": 933.3333333333333333333333334, + "ValueWithoutInterest": 933.3333333333333333333333334 + }, + { + "Date": "2026-07-01T00:00:00", + "Value": 1120.000000000, + "ValueWithoutInterest": 1120.00000 + }, + { + "Date": "2026-08-01T00:00:00", + "Value": 1306.6666666666666666666666666, + "ValueWithoutInterest": 1306.6666666666666666666666666 + }, + { + "Date": "2026-09-01T00:00:00", + "Value": 1493.3333333333333333333333334, + "ValueWithoutInterest": 1493.3333333333333333333333334 + }, + { + "Date": "2026-10-01T00:00:00", + "Value": 1680.0000000000, + "ValueWithoutInterest": 1680.000000 + }, + { + "Date": "2026-11-01T00:00:00", + "Value": 1866.6666666666666666666666666, + "ValueWithoutInterest": 1866.6666666666666666666666666 + }, + { + "Date": "2026-12-01T00:00:00", + "Value": 2053.3333333333333333333333334, + "ValueWithoutInterest": 2053.3333333333333333333333334 + }, + { + "Date": "2027-01-01T00:00:00", + "Value": 2240.00000000, + "ValueWithoutInterest": 2240.0000 + }, + { + "Date": "2027-02-01T00:00:00", + "Value": 2429.0000000000000000000000000, + "ValueWithoutInterest": 2426.6666666666666666666666666 + }, + { + "Date": "2027-03-01T00:00:00", + "Value": 2618.0000000000000000000000000, + "ValueWithoutInterest": 2613.3333333333333333333333334 + }, + { + "Date": "2027-04-01T00:00:00", + "Value": 2807.00000000000000, + "ValueWithoutInterest": 2800.000000 + }, + { + "Date": "2027-05-01T00:00:00", + "Value": 2996.0000000000000000000000000, + "ValueWithoutInterest": 2986.6666666666666666666666666 + }, + { + "Date": "2027-06-01T00:00:00", + "Value": 3185.0000000000000000000000000, + "ValueWithoutInterest": 3173.3333333333333333333333334 + }, + { + "Date": "2027-07-01T00:00:00", + "Value": 3374.0000000000000, + "ValueWithoutInterest": 3360.00000 + }, + { + "Date": "2027-08-01T00:00:00", + "Value": 3563.0000000000000000000000000, + "ValueWithoutInterest": 3546.6666666666666666666666666 + }, + { + "Date": "2027-09-01T00:00:00", + "Value": 3752.0000000000000000000000000, + "ValueWithoutInterest": 3733.3333333333333333333333334 + }, + { + "Date": "2027-10-01T00:00:00", + "Value": 3941.00000000000000, + "ValueWithoutInterest": 3920.000000 + }, + { + "Date": "2027-11-01T00:00:00", + "Value": 4130.0000000000000000000000000, + "ValueWithoutInterest": 4106.6666666666666666666666666 + }, + { + "Date": "2027-12-01T00:00:00", + "Value": 4319.0000000000000000000000000, + "ValueWithoutInterest": 4293.3333333333333333333333334 + }, + { + "Date": "2028-01-01T00:00:00", + "Value": 4508.000000000000, + "ValueWithoutInterest": 4480.0000 + }, + { + "Date": "2028-02-01T00:00:00", + "Value": 4699.3625000000000000000000001, + "ValueWithoutInterest": 4666.6666666666666666666666666 + }, + { + "Date": "2028-03-01T00:00:00", + "Value": 4890.7249999999999999999999999, + "ValueWithoutInterest": 4853.3333333333333333333333334 + }, + { + "Date": "2028-04-01T00:00:00", + "Value": 5082.087500000000000000, + "ValueWithoutInterest": 5040.000000 + }, + { + "Date": "2028-05-01T00:00:00", + "Value": 5273.4500000000000000000000001, + "ValueWithoutInterest": 5226.6666666666666666666666666 + }, + { + "Date": "2028-06-01T00:00:00", + "Value": 5464.8124999999999999999999999, + "ValueWithoutInterest": 5413.3333333333333333333333334 + }, + { + "Date": "2028-07-01T00:00:00", + "Value": 5656.17500000000000000, + "ValueWithoutInterest": 5600.00000 + }, + { + "Date": "2028-08-01T00:00:00", + "Value": 5847.5375000000000000000000001, + "ValueWithoutInterest": 5786.6666666666666666666666666 + }, + { + "Date": "2028-09-01T00:00:00", + "Value": 6038.8999999999999999999999999, + "ValueWithoutInterest": 5973.3333333333333333333333334 + }, + { + "Date": "2028-10-01T00:00:00", + "Value": 6230.262500000000000000, + "ValueWithoutInterest": 6160.000000 + }, + { + "Date": "2028-11-01T00:00:00", + "Value": 6421.6250000000000000000000001, + "ValueWithoutInterest": 6346.6666666666666666666666666 + }, + { + "Date": "2028-12-01T00:00:00", + "Value": 6612.9874999999999999999999999, + "ValueWithoutInterest": 6533.3333333333333333333333334 + }, + { + "Date": "2029-01-01T00:00:00", + "Value": 6804.3500000000000000, + "ValueWithoutInterest": 6720.0000 + }, + { + "Date": "2029-02-01T00:00:00", + "Value": 6998.1045312500000000000000002, + "ValueWithoutInterest": 6906.6666666666666666666666666 + }, + { + "Date": "2029-03-01T00:00:00", + "Value": 7191.8590624999999999999999998, + "ValueWithoutInterest": 7093.3333333333333333333333334 + }, + { + "Date": "2029-04-01T00:00:00", + "Value": 7385.6135937500000000000000, + "ValueWithoutInterest": 7280.000000 + }, + { + "Date": "2029-05-01T00:00:00", + "Value": 7579.3681250000000000000000002, + "ValueWithoutInterest": 7466.6666666666666666666666666 + }, + { + "Date": "2029-06-01T00:00:00", + "Value": 7773.1226562499999999999999998, + "ValueWithoutInterest": 7653.3333333333333333333333334 + }, + { + "Date": "2029-07-01T00:00:00", + "Value": 7966.877187500000000000000, + "ValueWithoutInterest": 7840.00000 + }, + { + "Date": "2029-08-01T00:00:00", + "Value": 8160.631718750000000000000000, + "ValueWithoutInterest": 8026.666666666666666666666667 + }, + { + "Date": "2029-09-01T00:00:00", + "Value": 8354.386250000000000000000000, + "ValueWithoutInterest": 8213.333333333333333333333333 + }, + { + "Date": "2029-10-01T00:00:00", + "Value": 8548.1407812500000000000000, + "ValueWithoutInterest": 8400.000000 + }, + { + "Date": "2029-11-01T00:00:00", + "Value": 8741.895312500000000000000000, + "ValueWithoutInterest": 8586.666666666666666666666667 + }, + { + "Date": "2029-12-01T00:00:00", + "Value": 8935.649843750000000000000000, + "ValueWithoutInterest": 8773.333333333333333333333333 + }, + { + "Date": "2030-01-01T00:00:00", + "Value": 9129.40437500000000000000, + "ValueWithoutInterest": 8960.0000 + }, + { + "Date": "2030-02-01T00:00:00", + "Value": 9325.580837890625000000000001, + "ValueWithoutInterest": 9146.666666666666666666666667 + }, + { + "Date": "2030-03-01T00:00:00", + "Value": 9521.757300781249999999999999, + "ValueWithoutInterest": 9333.333333333333333333333333 + }, + { + "Date": "2030-04-01T00:00:00", + "Value": 9717.933763671875000000000000, + "ValueWithoutInterest": 9520.000000 + }, + { + "Date": "2030-05-01T00:00:00", + "Value": 9914.110226562500000000000001, + "ValueWithoutInterest": 9706.666666666666666666666667 + }, + { + "Date": "2030-06-01T00:00:00", + "Value": 10110.286689453124999999999999, + "ValueWithoutInterest": 9893.333333333333333333333333 + }, + { + "Date": "2030-07-01T00:00:00", + "Value": 10306.463152343750000000000000, + "ValueWithoutInterest": 10080.00000 + }, + { + "Date": "2030-08-01T00:00:00", + "Value": 10502.639615234375000000000001, + "ValueWithoutInterest": 10266.666666666666666666666667 + }, + { + "Date": "2030-09-01T00:00:00", + "Value": 10698.816078124999999999999999, + "ValueWithoutInterest": 10453.333333333333333333333333 + }, + { + "Date": "2030-10-01T00:00:00", + "Value": 10894.992541015625000000000000, + "ValueWithoutInterest": 10640.000000 + }, + { + "Date": "2030-11-01T00:00:00", + "Value": 11091.169003906250000000000001, + "ValueWithoutInterest": 10826.666666666666666666666667 + }, + { + "Date": "2030-12-01T00:00:00", + "Value": 11287.345466796874999999999999, + "ValueWithoutInterest": 11013.333333333333333333333333 + }, + { + "Date": "2031-01-01T00:00:00", + "Value": 11483.521929687500000000000000, + "ValueWithoutInterest": 11200.0000 + }, + { + "Date": "2031-02-01T00:00:00", + "Value": 11682.150598364257812500000001, + "ValueWithoutInterest": 11386.666666666666666666666667 + }, + { + "Date": "2031-03-01T00:00:00", + "Value": 11880.779267041015624999999999, + "ValueWithoutInterest": 11573.333333333333333333333333 + }, + { + "Date": "2031-04-01T00:00:00", + "Value": 12079.407935717773437500000000, + "ValueWithoutInterest": 11760.000000 + }, + { + "Date": "2031-05-01T00:00:00", + "Value": 12278.036604394531250000000001, + "ValueWithoutInterest": 11946.666666666666666666666667 + }, + { + "Date": "2031-06-01T00:00:00", + "Value": 12476.665273071289062499999999, + "ValueWithoutInterest": 12133.333333333333333333333333 + }, + { + "Date": "2031-07-01T00:00:00", + "Value": 12675.293941748046875000000000, + "ValueWithoutInterest": 12320.00000 + }, + { + "Date": "2031-08-01T00:00:00", + "Value": 12873.922610424804687500000001, + "ValueWithoutInterest": 12506.666666666666666666666667 + }, + { + "Date": "2031-09-01T00:00:00", + "Value": 13072.551279101562499999999999, + "ValueWithoutInterest": 12693.333333333333333333333333 + }, + { + "Date": "2031-10-01T00:00:00", + "Value": 13271.179947778320312500000000, + "ValueWithoutInterest": 12880.000000 + }, + { + "Date": "2031-11-01T00:00:00", + "Value": 13469.808616455078125000000001, + "ValueWithoutInterest": 13066.666666666666666666666667 + }, + { + "Date": "2031-12-01T00:00:00", + "Value": 13668.437285131835937499999999, + "ValueWithoutInterest": 13253.333333333333333333333333 + }, + { + "Date": "2032-01-01T00:00:00", + "Value": 13867.065953808593750000000000, + "ValueWithoutInterest": 13440.0000 + }, + { + "Date": "2032-02-01T00:00:00", + "Value": 14068.177480843811035156250001, + "ValueWithoutInterest": 13626.666666666666666666666667 + }, + { + "Date": "2032-03-01T00:00:00", + "Value": 14269.289007879028320312499999, + "ValueWithoutInterest": 13813.333333333333333333333333 + }, + { + "Date": "2032-04-01T00:00:00", + "Value": 14470.400534914245605468750000, + "ValueWithoutInterest": 14000.000000 + }, + { + "Date": "2032-05-01T00:00:00", + "Value": 14671.512061949462890625000001, + "ValueWithoutInterest": 14186.666666666666666666666667 + }, + { + "Date": "2032-06-01T00:00:00", + "Value": 14872.623588984680175781249999, + "ValueWithoutInterest": 14373.333333333333333333333333 + }, + { + "Date": "2032-07-01T00:00:00", + "Value": 15073.735116019897460937500000, + "ValueWithoutInterest": 14560.00000 + }, + { + "Date": "2032-08-01T00:00:00", + "Value": 15274.846643055114746093750001, + "ValueWithoutInterest": 14746.666666666666666666666667 + }, + { + "Date": "2032-09-01T00:00:00", + "Value": 15475.958170090332031249999999, + "ValueWithoutInterest": 14933.333333333333333333333333 + }, + { + "Date": "2032-10-01T00:00:00", + "Value": 15677.069697125549316406250000, + "ValueWithoutInterest": 15120.000000 + }, + { + "Date": "2032-11-01T00:00:00", + "Value": 15878.181224160766601562500001, + "ValueWithoutInterest": 15306.666666666666666666666667 + }, + { + "Date": "2032-12-01T00:00:00", + "Value": 16079.292751195983886718749999, + "ValueWithoutInterest": 15493.333333333333333333333333 + }, + { + "Date": "2033-01-01T00:00:00", + "Value": 16280.404278231201171875000000, + "ValueWithoutInterest": 15680.0000 + }, + { + "Date": "2033-02-01T00:00:00", + "Value": 16484.029699354358673095703126, + "ValueWithoutInterest": 15866.666666666666666666666667 + }, + { + "Date": "2033-03-01T00:00:00", + "Value": 16687.655120477516174316406249, + "ValueWithoutInterest": 16053.333333333333333333333333 + }, + { + "Date": "2033-04-01T00:00:00", + "Value": 16891.280541600673675537109375, + "ValueWithoutInterest": 16240.000000 + }, + { + "Date": "2033-05-01T00:00:00", + "Value": 17094.905962723831176757812501, + "ValueWithoutInterest": 16426.666666666666666666666667 + }, + { + "Date": "2033-06-01T00:00:00", + "Value": 17298.531383846988677978515624, + "ValueWithoutInterest": 16613.333333333333333333333333 + }, + { + "Date": "2033-07-01T00:00:00", + "Value": 17502.156804970146179199218750, + "ValueWithoutInterest": 16800.00000 + }, + { + "Date": "2033-08-01T00:00:00", + "Value": 17705.782226093303680419921876, + "ValueWithoutInterest": 16986.666666666666666666666667 + }, + { + "Date": "2033-09-01T00:00:00", + "Value": 17909.407647216461181640624999, + "ValueWithoutInterest": 17173.333333333333333333333333 + }, + { + "Date": "2033-10-01T00:00:00", + "Value": 18113.033068339618682861328125, + "ValueWithoutInterest": 17360.000000 + }, + { + "Date": "2033-11-01T00:00:00", + "Value": 18316.658489462776184082031251, + "ValueWithoutInterest": 17546.666666666666666666666667 + }, + { + "Date": "2033-12-01T00:00:00", + "Value": 18520.283910585933685302734374, + "ValueWithoutInterest": 17733.333333333333333333333333 + }, + { + "Date": "2034-01-01T00:00:00", + "Value": 18723.909331709091186523437500, + "ValueWithoutInterest": 17920.0000 + }, + { + "Date": "2034-02-01T00:00:00", + "Value": 18930.080070596288156509399415, + "ValueWithoutInterest": 18106.666666666666666666666667 + }, + { + "Date": "2034-03-01T00:00:00", + "Value": 19136.250809483485126495361327, + "ValueWithoutInterest": 18293.333333333333333333333333 + }, + { + "Date": "2034-04-01T00:00:00", + "Value": 19342.421548370682096481323242, + "ValueWithoutInterest": 18480.000000 + } + ] +} diff --git a/src/BvgCalculator.Tests/__snapshots__/RetirementSavingsProcessCalculationTests.SavingsProcessTable_WithSampleInputs_ReturnsExpectedResults.snap b/src/BvgCalculator.Tests/__snapshots__/RetirementSavingsProcessCalculationTests.SavingsProcessTable_WithSampleInputs_ReturnsExpectedResults.snap new file mode 100644 index 00000000..723372d5 --- /dev/null +++ b/src/BvgCalculator.Tests/__snapshots__/RetirementSavingsProcessCalculationTests.SavingsProcessTable_WithSampleInputs_ReturnsExpectedResults.snap @@ -0,0 +1,3130 @@ +[ + { + "DateOfCalculation": "2024-01-01T00:00:00", + "BvgAge": 55, + "TechnicalAge": { + "Years": 54, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 11245.5, + "RetirementCapitalWithoutInterest": 0.0, + "RetirementCapital": 0.0, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2024-02-01T00:00:00", + "BvgAge": 55, + "TechnicalAge": { + "Years": 54, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 937.1249999999999999999999996, + "RetirementCapitalWithoutInterest": 937.1249999999999999999999996, + "RetirementCapital": 937.1249999999999999999999996, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2024-03-01T00:00:00", + "BvgAge": 55, + "TechnicalAge": { + "Years": 54, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1874.2500000000000000000000004, + "RetirementCapitalWithoutInterest": 1874.2500000000000000000000004, + "RetirementCapital": 1874.2500000000000000000000004, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2024-04-01T00:00:00", + "BvgAge": 55, + "TechnicalAge": { + "Years": 55, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2811.375, + "RetirementCapitalWithoutInterest": 2811.375, + "RetirementCapital": 2811.375000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2024-05-01T00:00:00", + "BvgAge": 55, + "TechnicalAge": { + "Years": 55, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3748.4999999999999999999999996, + "RetirementCapitalWithoutInterest": 3748.4999999999999999999999996, + "RetirementCapital": 3748.4999999999999999999999996, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2024-06-01T00:00:00", + "BvgAge": 55, + "TechnicalAge": { + "Years": 55, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4685.6250000000000000000000004, + "RetirementCapitalWithoutInterest": 4685.6250000000000000000000004, + "RetirementCapital": 4685.6250000000000000000000004, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2024-07-01T00:00:00", + "BvgAge": 55, + "TechnicalAge": { + "Years": 55, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5622.75, + "RetirementCapitalWithoutInterest": 5622.75, + "RetirementCapital": 5622.75000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2024-08-01T00:00:00", + "BvgAge": 55, + "TechnicalAge": { + "Years": 55, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6559.8749999999999999999999996, + "RetirementCapitalWithoutInterest": 6559.8749999999999999999999996, + "RetirementCapital": 6559.8749999999999999999999996, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2024-09-01T00:00:00", + "BvgAge": 55, + "TechnicalAge": { + "Years": 55, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7497.0000000000000000000000004, + "RetirementCapitalWithoutInterest": 7497.0000000000000000000000004, + "RetirementCapital": 7497.0000000000000000000000004, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2024-10-01T00:00:00", + "BvgAge": 55, + "TechnicalAge": { + "Years": 55, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8434.125, + "RetirementCapitalWithoutInterest": 8434.125, + "RetirementCapital": 8434.125000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2024-11-01T00:00:00", + "BvgAge": 55, + "TechnicalAge": { + "Years": 55, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9371.250000000000000000000000, + "RetirementCapitalWithoutInterest": 9371.250000000000000000000000, + "RetirementCapital": 9371.250000000000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2024-12-01T00:00:00", + "BvgAge": 55, + "TechnicalAge": { + "Years": 55, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 10308.375000000000000000000000, + "RetirementCapitalWithoutInterest": 10308.375000000000000000000000, + "RetirementCapital": 10308.375000000000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-01-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 55, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 11245.5, + "RetirementCapitalWithoutInterest": 11245.5, + "RetirementCapital": 11245.5000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-02-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 55, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 937.1249999999999999999999996, + "RetirementCapitalWithoutInterest": 12182.625000000000000000000000, + "RetirementCapital": 12194.339062500000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-03-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 55, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1874.2500000000000000000000004, + "RetirementCapitalWithoutInterest": 13119.750000000000000000000000, + "RetirementCapital": 13143.178125000000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-04-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2811.375, + "RetirementCapitalWithoutInterest": 14056.875, + "RetirementCapital": 14092.0171875000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2025-05-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3748.4999999999999999999999996, + "RetirementCapitalWithoutInterest": 14994.000000000000000000000000, + "RetirementCapital": 15040.856250000000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-06-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4685.6250000000000000000000004, + "RetirementCapitalWithoutInterest": 15931.125000000000000000000000, + "RetirementCapital": 15989.695312500000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-07-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5622.75, + "RetirementCapitalWithoutInterest": 16868.25, + "RetirementCapital": 16938.534375000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-08-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6559.8749999999999999999999996, + "RetirementCapitalWithoutInterest": 17805.375000000000000000000000, + "RetirementCapital": 17887.373437500000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-09-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7497.0000000000000000000000004, + "RetirementCapitalWithoutInterest": 18742.500000000000000000000000, + "RetirementCapital": 18836.212500000000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-10-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8434.125, + "RetirementCapitalWithoutInterest": 19679.625, + "RetirementCapital": 19785.0515625000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-11-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9371.250000000000000000000000, + "RetirementCapitalWithoutInterest": 20616.750000000000000000000000, + "RetirementCapital": 20733.890625000000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2025-12-01T00:00:00", + "BvgAge": 56, + "TechnicalAge": { + "Years": 56, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 10308.375000000000000000000000, + "RetirementCapitalWithoutInterest": 21553.875000000000000000000000, + "RetirementCapital": 21682.729687500000000000000000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-01-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 56, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 11245.5, + "RetirementCapitalWithoutInterest": 22491.0, + "RetirementCapital": 22631.56875000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-02-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 56, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 937.1249999999999999999999996, + "RetirementCapitalWithoutInterest": 23428.125000000000000000000000, + "RetirementCapital": 23592.268300781250000000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-03-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 56, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 24137.400000000000000000000000, + "RetirementCapital": 24325.117851562499999999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-04-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 24960.600, + "RetirementCapital": 25171.89240234375000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2026-05-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 25783.800000000000000000000000, + "RetirementCapital": 26018.666953125000000000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-06-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 26607.000000000000000000000000, + "RetirementCapital": 26865.441503906249999999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-07-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 27430.20, + "RetirementCapital": 27712.2160546875000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-08-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 28253.400000000000000000000000, + "RetirementCapital": 28558.990605468750000000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-09-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 29076.600000000000000000000000, + "RetirementCapital": 29405.765156249999999999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-10-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 29899.800, + "RetirementCapital": 30252.53970703125000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-11-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 30723.000000000000000000000000, + "RetirementCapital": 31099.314257812500000000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2026-12-01T00:00:00", + "BvgAge": 57, + "TechnicalAge": { + "Years": 57, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 31546.200000000000000000000000, + "RetirementCapital": 31946.088808593749999999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-01-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 57, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 32369.4, + "RetirementCapital": 32792.863359375000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-02-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 57, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 33192.600000000000000000000000, + "RetirementCapital": 33650.222592041015625000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-03-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 57, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 34015.800000000000000000000000, + "RetirementCapital": 34507.581824707031249999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-04-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 34839.000, + "RetirementCapital": 35364.941057373046875000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2027-05-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 35662.200000000000000000000000, + "RetirementCapital": 36222.300290039062500000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-06-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 36485.400000000000000000000000, + "RetirementCapital": 37079.659522705078124999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-07-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 37308.60, + "RetirementCapital": 37937.01875537109375000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-08-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 38131.800000000000000000000000, + "RetirementCapital": 38794.377988037109375000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-09-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 38955.000000000000000000000000, + "RetirementCapital": 39651.737220703124999999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-10-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 39778.200, + "RetirementCapital": 40509.096453369140625000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-11-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 40601.400000000000000000000000, + "RetirementCapital": 41366.455686035156250000000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2027-12-01T00:00:00", + "BvgAge": 58, + "TechnicalAge": { + "Years": 58, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 41424.600000000000000000000000, + "RetirementCapital": 42223.814918701171874999999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-01-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 58, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 42247.8, + "RetirementCapital": 43081.1741513671875000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-02-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 58, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 43071.000000000000000000000000, + "RetirementCapital": 43949.250374441528320312500001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-03-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 58, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 43894.200000000000000000000000, + "RetirementCapital": 44817.326597515869140624999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-04-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 44717.400, + "RetirementCapital": 45685.4028205902099609375000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2028-05-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 45540.600000000000000000000000, + "RetirementCapital": 46553.479043664550781250000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-06-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 46363.800000000000000000000000, + "RetirementCapital": 47421.555266738891601562499999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-07-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 47187.00, + "RetirementCapital": 48289.631489813232421875000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-08-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 48010.200000000000000000000000, + "RetirementCapital": 49157.707712887573242187500001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-09-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 48833.400000000000000000000000, + "RetirementCapital": 50025.783935961914062499999999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-10-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 49656.600, + "RetirementCapital": 50893.8601590362548828125000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-11-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 50479.800000000000000000000000, + "RetirementCapital": 51761.936382110595703125000001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2028-12-01T00:00:00", + "BvgAge": 59, + "TechnicalAge": { + "Years": 59, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 51303.000000000000000000000000, + "RetirementCapital": 52630.012605184936523437499999, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-01-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 59, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 52126.2, + "RetirementCapital": 53498.08882825927734375000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-02-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 59, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 52949.400000000000000000000000, + "RetirementCapital": 54377.016004122047424316406252, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-03-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 59, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 53772.600000000000000000000000, + "RetirementCapital": 55255.943179984817504882812498, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-04-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 54595.800, + "RetirementCapital": 56134.870355847587585449218750, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2029-05-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 55419.000000000000000000000000, + "RetirementCapital": 57013.797531710357666015625002, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-06-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 56242.200000000000000000000000, + "RetirementCapital": 57892.724707573127746582031248, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-07-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 57065.40, + "RetirementCapital": 58771.651883435897827148437500, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-08-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 57888.600000000000000000000000, + "RetirementCapital": 59650.579059298667907714843752, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-09-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 58711.800000000000000000000000, + "RetirementCapital": 60529.506235161437988281249998, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-10-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 59535.000, + "RetirementCapital": 61408.433411024208068847656250, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-11-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 60358.200000000000000000000000, + "RetirementCapital": 62287.360586886978149414062502, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2029-12-01T00:00:00", + "BvgAge": 60, + "TechnicalAge": { + "Years": 60, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 61181.400000000000000000000000, + "RetirementCapital": 63166.287762749748229980468748, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-01-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 60, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 62004.6, + "RetirementCapital": 64045.214938612518310546875000, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-02-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 60, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 62827.800000000000000000000000, + "RetirementCapital": 64935.128704173573017120361330, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-03-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 60, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 63651.000000000000000000000000, + "RetirementCapital": 65825.042469734627723693847654, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-04-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 64474.200, + "RetirementCapital": 66714.956235295682430267333984, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2030-05-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 65297.400000000000000000000000, + "RetirementCapital": 67604.870000856737136840820315, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-06-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 66120.600000000000000000000000, + "RetirementCapital": 68494.783766417791843414306638, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-07-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 66943.80, + "RetirementCapital": 69384.697531978846549987792969, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-08-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 67767.000000000000000000000000, + "RetirementCapital": 70274.611297539901256561279299, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-09-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 68590.200000000000000000000000, + "RetirementCapital": 71164.525063100955963134765623, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-10-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 69413.400, + "RetirementCapital": 72054.438828662010669708251953, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-11-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 70236.600000000000000000000000, + "RetirementCapital": 72944.352594223065376281738283, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2030-12-01T00:00:00", + "BvgAge": 61, + "TechnicalAge": { + "Years": 61, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 71059.800000000000000000000000, + "RetirementCapital": 73834.266359784120082855224607, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-01-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 61, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 71883.0, + "RetirementCapital": 74724.180125345174789428710938, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-02-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 61, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 72706.200000000000000000000000, + "RetirementCapital": 75625.217812975742679834365848, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-03-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 61, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 73529.400000000000000000000000, + "RetirementCapital": 76526.255500606310570240020750, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-04-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 74352.600, + "RetirementCapital": 77427.293188236878460645675660, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2031-05-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 75175.800000000000000000000000, + "RetirementCapital": 78328.330875867446351051330569, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-06-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 75999.000000000000000000000000, + "RetirementCapital": 79229.36856349801424145698547, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-07-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 76822.20, + "RetirementCapital": 80130.40625112858213186264038, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-08-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 77645.400000000000000000000000, + "RetirementCapital": 81031.44393875915002226829529, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-09-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 78468.600000000000000000000000, + "RetirementCapital": 81932.48162638971791267395019, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-10-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 79291.800, + "RetirementCapital": 82833.51931402028580307960510, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-11-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 80115.00000000000000000000000, + "RetirementCapital": 83734.55700165085369348526001, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2031-12-01T00:00:00", + "BvgAge": 62, + "TechnicalAge": { + "Years": 62, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 80938.20000000000000000000000, + "RetirementCapital": 84635.59468928142158389091492, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-01-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 62, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 81761.4, + "RetirementCapital": 85536.63237691198947429656982, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-02-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 62, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 82584.60000000000000000000000, + "RetirementCapital": 86448.93303563793946333229542, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-03-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 62, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 83407.80000000000000000000000, + "RetirementCapital": 87361.23369436388945236802100, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-04-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 84231.000, + "RetirementCapital": 88273.53435308983944140374660, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2032-05-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 85054.20000000000000000000000, + "RetirementCapital": 89185.83501181578943043947220, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-06-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 85877.40000000000000000000000, + "RetirementCapital": 90098.13567054173941947519778, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-07-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 86700.60, + "RetirementCapital": 91010.43632926768940851092338, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-08-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 87523.80000000000000000000000, + "RetirementCapital": 91922.73698799363939754664898, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-09-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 88347.00000000000000000000000, + "RetirementCapital": 92835.03764671958938658237457, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-10-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 89170.200, + "RetirementCapital": 93747.33830544553937561810016, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-11-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 89993.40000000000000000000000, + "RetirementCapital": 94659.63896417148936465382576, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2032-12-01T00:00:00", + "BvgAge": 63, + "TechnicalAge": { + "Years": 63, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 90816.60000000000000000000000, + "RetirementCapital": 95571.93962289743935368955135, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-01-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 63, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 91639.8, + "RetirementCapital": 96484.24028162338934272527694, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-02-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 63, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 92463.00000000000000000000000, + "RetirementCapital": 97407.94469858341370662394911, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-03-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 63, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 93286.20000000000000000000000, + "RetirementCapital": 98331.64911554343807052262126, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-04-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 94109.400, + "RetirementCapital": 99255.35353250346243442129343, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2033-05-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 94932.60000000000000000000000, + "RetirementCapital": 100179.05794946348679831996560, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-06-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 95755.80000000000000000000000, + "RetirementCapital": 101102.76236642351116221863775, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-07-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 96579.00, + "RetirementCapital": 102026.46678338353552611730992, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-08-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 97402.20000000000000000000000, + "RetirementCapital": 102950.17120034355989001598209, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-09-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 98225.40000000000000000000000, + "RetirementCapital": 103873.87561730358425391465424, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-10-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 99048.600, + "RetirementCapital": 104797.58003426360861781332641, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-11-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 99871.80000000000000000000000, + "RetirementCapital": 105721.28445122363298171199858, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2033-12-01T00:00:00", + "BvgAge": 64, + "TechnicalAge": { + "Years": 64, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 100695.00000000000000000000000, + "RetirementCapital": 106644.98886818365734561067074, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-01-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 64, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 101518.2, + "RetirementCapital": 107568.69328514368170950934290, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-02-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 64, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 102341.40000000000000000000000, + "RetirementCapital": 108503.94400731570637795674847, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-03-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 64, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 103164.60000000000000000000000, + "RetirementCapital": 109439.19472948773104640415403, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-04-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 65, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 103987.800, + "RetirementCapital": 110374.44545165975571485155960, + "IsRetirementDate": true, + "IsEndOfSavings": true, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2034-05-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 65, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 104811.00000000000000000000000, + "RetirementCapital": 111309.69617383178038329896517, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-06-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 65, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 105634.20000000000000000000000, + "RetirementCapital": 112244.94689600380505174637072, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-07-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 65, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 106457.40, + "RetirementCapital": 113180.19761817582972019377629, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-08-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 65, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 107280.60000000000000000000000, + "RetirementCapital": 114115.44834034785438864118186, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-09-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 65, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 108103.80000000000000000000000, + "RetirementCapital": 115050.69906251987905708858742, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-10-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 65, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 108927.000, + "RetirementCapital": 115985.94978469190372553599299, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-11-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 65, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 109750.20000000000000000000000, + "RetirementCapital": 116921.20050686392839398339856, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2034-12-01T00:00:00", + "BvgAge": 65, + "TechnicalAge": { + "Years": 65, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 110573.40000000000000000000000, + "RetirementCapital": 117856.45122903595306243080412, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2035-01-01T00:00:00", + "BvgAge": 66, + "TechnicalAge": { + "Years": 65, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 111396.6, + "RetirementCapital": 118791.70195120797773087820969, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2035-02-01T00:00:00", + "BvgAge": 66, + "TechnicalAge": { + "Years": 65, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 112219.80000000000000000000000, + "RetirementCapital": 119738.64330740715270768120783, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2035-03-01T00:00:00", + "BvgAge": 66, + "TechnicalAge": { + "Years": 65, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 113043.00000000000000000000000, + "RetirementCapital": 120685.58466360632768448420596, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2035-04-01T00:00:00", + "BvgAge": 66, + "TechnicalAge": { + "Years": 66, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 113866.200, + "RetirementCapital": 121632.52601980550266128720410, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2035-05-01T00:00:00", + "BvgAge": 66, + "TechnicalAge": { + "Years": 66, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 114689.40000000000000000000000, + "RetirementCapital": 122579.46737600467763809020223, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2035-06-01T00:00:00", + "BvgAge": 66, + "TechnicalAge": { + "Years": 66, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 115512.60000000000000000000000, + "RetirementCapital": 123526.40873220385261489320036, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2035-07-01T00:00:00", + "BvgAge": 66, + "TechnicalAge": { + "Years": 66, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 116335.80, + "RetirementCapital": 124473.35008840302759169619850, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2035-08-01T00:00:00", + "BvgAge": 66, + "TechnicalAge": { + "Years": 66, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 117159.00000000000000000000000, + "RetirementCapital": 125420.29144460220256849919664, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2035-09-01T00:00:00", + "BvgAge": 66, + "TechnicalAge": { + "Years": 66, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 117982.20000000000000000000000, + "RetirementCapital": 126367.23280080137754530219477, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2035-10-01T00:00:00", + "BvgAge": 66, + "TechnicalAge": { + "Years": 66, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 118805.400, + "RetirementCapital": 127314.17415700055252210519291, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2035-11-01T00:00:00", + "BvgAge": 66, + "TechnicalAge": { + "Years": 66, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 119628.60000000000000000000000, + "RetirementCapital": 128261.11551319972749890819104, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2035-12-01T00:00:00", + "BvgAge": 66, + "TechnicalAge": { + "Years": 66, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 120451.80000000000000000000000, + "RetirementCapital": 129208.05686939890247571118917, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2036-01-01T00:00:00", + "BvgAge": 67, + "TechnicalAge": { + "Years": 66, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 121275.0, + "RetirementCapital": 130154.99822559807745251418731, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2036-02-01T00:00:00", + "BvgAge": 67, + "TechnicalAge": { + "Years": 66, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 122098.20000000000000000000000, + "RetirementCapital": 131113.77634874974211652722293, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2036-03-01T00:00:00", + "BvgAge": 67, + "TechnicalAge": { + "Years": 66, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 122921.40000000000000000000000, + "RetirementCapital": 132072.55447190140678054025853, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2036-04-01T00:00:00", + "BvgAge": 67, + "TechnicalAge": { + "Years": 67, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 123744.600, + "RetirementCapital": 133031.33259505307144455329415, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2036-05-01T00:00:00", + "BvgAge": 67, + "TechnicalAge": { + "Years": 67, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 124567.80000000000000000000000, + "RetirementCapital": 133990.11071820473610856632976, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2036-06-01T00:00:00", + "BvgAge": 67, + "TechnicalAge": { + "Years": 67, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 125391.00000000000000000000000, + "RetirementCapital": 134948.88884135640077257936536, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2036-07-01T00:00:00", + "BvgAge": 67, + "TechnicalAge": { + "Years": 67, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 126214.20, + "RetirementCapital": 135907.66696450806543659240098, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2036-08-01T00:00:00", + "BvgAge": 67, + "TechnicalAge": { + "Years": 67, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 127037.40000000000000000000000, + "RetirementCapital": 136866.44508765973010060543660, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2036-09-01T00:00:00", + "BvgAge": 67, + "TechnicalAge": { + "Years": 67, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 127860.60000000000000000000000, + "RetirementCapital": 137825.22321081139476461847220, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2036-10-01T00:00:00", + "BvgAge": 67, + "TechnicalAge": { + "Years": 67, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 128683.800, + "RetirementCapital": 138784.00133396305942863150782, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2036-11-01T00:00:00", + "BvgAge": 67, + "TechnicalAge": { + "Years": 67, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 129507.00000000000000000000000, + "RetirementCapital": 139742.77945711472409264454343, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2036-12-01T00:00:00", + "BvgAge": 67, + "TechnicalAge": { + "Years": 67, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 130330.20000000000000000000000, + "RetirementCapital": 140701.55758026638875665757904, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2037-01-01T00:00:00", + "BvgAge": 68, + "TechnicalAge": { + "Years": 67, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 131153.4, + "RetirementCapital": 141660.33570341805342067061465, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2037-02-01T00:00:00", + "BvgAge": 68, + "TechnicalAge": { + "Years": 67, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 131976.60000000000000000000000, + "RetirementCapital": 142631.09855310911389298381321, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2037-03-01T00:00:00", + "BvgAge": 68, + "TechnicalAge": { + "Years": 67, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 132799.80000000000000000000000, + "RetirementCapital": 143601.86140280017436529701176, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2037-04-01T00:00:00", + "BvgAge": 68, + "TechnicalAge": { + "Years": 68, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 133623.000, + "RetirementCapital": 144572.62425249123483761021032, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2037-05-01T00:00:00", + "BvgAge": 68, + "TechnicalAge": { + "Years": 68, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 134446.20000000000000000000000, + "RetirementCapital": 145543.38710218229530992340888, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2037-06-01T00:00:00", + "BvgAge": 68, + "TechnicalAge": { + "Years": 68, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 135269.40000000000000000000000, + "RetirementCapital": 146514.14995187335578223660743, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2037-07-01T00:00:00", + "BvgAge": 68, + "TechnicalAge": { + "Years": 68, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 136092.60, + "RetirementCapital": 147484.91280156441625454980599, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2037-08-01T00:00:00", + "BvgAge": 68, + "TechnicalAge": { + "Years": 68, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 136915.80000000000000000000000, + "RetirementCapital": 148455.67565125547672686300455, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2037-09-01T00:00:00", + "BvgAge": 68, + "TechnicalAge": { + "Years": 68, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 137739.00000000000000000000000, + "RetirementCapital": 149426.43850094653719917620310, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2037-10-01T00:00:00", + "BvgAge": 68, + "TechnicalAge": { + "Years": 68, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 138562.200, + "RetirementCapital": 150397.20135063759767148940166, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2037-11-01T00:00:00", + "BvgAge": 68, + "TechnicalAge": { + "Years": 68, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 139385.40000000000000000000000, + "RetirementCapital": 151367.96420032865814380260022, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2037-12-01T00:00:00", + "BvgAge": 68, + "TechnicalAge": { + "Years": 68, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 140208.60000000000000000000000, + "RetirementCapital": 152338.72705001971861611579877, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2038-01-01T00:00:00", + "BvgAge": 69, + "TechnicalAge": { + "Years": 68, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 141031.8, + "RetirementCapital": 153309.48989971077908842899733, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2038-02-01T00:00:00", + "BvgAge": 69, + "TechnicalAge": { + "Years": 68, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 141855.00000000000000000000000, + "RetirementCapital": 154292.38728502297781664611087, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2038-03-01T00:00:00", + "BvgAge": 69, + "TechnicalAge": { + "Years": 68, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 142678.20000000000000000000000, + "RetirementCapital": 155275.28467033517654486322440, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2038-04-01T00:00:00", + "BvgAge": 69, + "TechnicalAge": { + "Years": 69, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 143501.400, + "RetirementCapital": 156258.18205564737527308033795, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + }, + { + "DateOfCalculation": "2038-05-01T00:00:00", + "BvgAge": 69, + "TechnicalAge": { + "Years": 69, + "Months": 1 + }, + "ProRatedFactor": 0.3333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 3292.7999999999999999999999997, + "RetirementCapitalWithoutInterest": 144324.60000000000000000000000, + "RetirementCapital": 157241.07944095957400129745149, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2038-06-01T00:00:00", + "BvgAge": 69, + "TechnicalAge": { + "Years": 69, + "Months": 2 + }, + "ProRatedFactor": 0.4166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4116.0000000000000000000000003, + "RetirementCapitalWithoutInterest": 145147.80000000000000000000000, + "RetirementCapital": 158223.97682627177272951456502, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2038-07-01T00:00:00", + "BvgAge": 69, + "TechnicalAge": { + "Years": 69, + "Months": 3 + }, + "ProRatedFactor": 0.5, + "GrossInterestRate": 0.0125, + "RetirementCredit": 4939.20, + "RetirementCapitalWithoutInterest": 145971.00, + "RetirementCapital": 159206.87421158397145773167856, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2038-08-01T00:00:00", + "BvgAge": 69, + "TechnicalAge": { + "Years": 69, + "Months": 4 + }, + "ProRatedFactor": 0.5833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 5762.3999999999999999999999997, + "RetirementCapitalWithoutInterest": 146794.20000000000000000000000, + "RetirementCapital": 160189.77159689617018594879211, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2038-09-01T00:00:00", + "BvgAge": 69, + "TechnicalAge": { + "Years": 69, + "Months": 5 + }, + "ProRatedFactor": 0.6666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 6585.6000000000000000000000003, + "RetirementCapitalWithoutInterest": 147617.40000000000000000000000, + "RetirementCapital": 161172.66898220836891416590564, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2038-10-01T00:00:00", + "BvgAge": 69, + "TechnicalAge": { + "Years": 69, + "Months": 6 + }, + "ProRatedFactor": 0.75, + "GrossInterestRate": 0.0125, + "RetirementCredit": 7408.800, + "RetirementCapitalWithoutInterest": 148440.600, + "RetirementCapital": 162155.56636752056764238301918, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2038-11-01T00:00:00", + "BvgAge": 69, + "TechnicalAge": { + "Years": 69, + "Months": 7 + }, + "ProRatedFactor": 0.8333333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 8232.000000000000000000000000, + "RetirementCapitalWithoutInterest": 149263.80000000000000000000000, + "RetirementCapital": 163138.46375283276637060013272, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2038-12-01T00:00:00", + "BvgAge": 69, + "TechnicalAge": { + "Years": 69, + "Months": 8 + }, + "ProRatedFactor": 0.9166666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9055.200000000000000000000000, + "RetirementCapitalWithoutInterest": 150087.00000000000000000000000, + "RetirementCapital": 164121.36113814496509881724625, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2039-01-01T00:00:00", + "BvgAge": 70, + "TechnicalAge": { + "Years": 69, + "Months": 9 + }, + "ProRatedFactor": 1.0, + "GrossInterestRate": 0.0125, + "RetirementCredit": 9878.4, + "RetirementCapitalWithoutInterest": 150910.2, + "RetirementCapital": 165104.25852345716382703435980, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": true, + "IsFullAge": false + }, + { + "DateOfCalculation": "2039-02-01T00:00:00", + "BvgAge": 70, + "TechnicalAge": { + "Years": 69, + "Months": 10 + }, + "ProRatedFactor": 0.0833333333333333333333333333, + "GrossInterestRate": 0.0125, + "RetirementCredit": 823.1999999999999999999999997, + "RetirementCapitalWithoutInterest": 151733.40000000000000000000000, + "RetirementCapital": 166099.44212608576503935418726, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2039-03-01T00:00:00", + "BvgAge": 70, + "TechnicalAge": { + "Years": 69, + "Months": 11 + }, + "ProRatedFactor": 0.1666666666666666666666666667, + "GrossInterestRate": 0.0125, + "RetirementCredit": 1646.4000000000000000000000003, + "RetirementCapitalWithoutInterest": 152556.60000000000000000000000, + "RetirementCapital": 167094.62572871436625167401471, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": false + }, + { + "DateOfCalculation": "2039-04-01T00:00:00", + "BvgAge": 70, + "TechnicalAge": { + "Years": 70, + "Months": 0 + }, + "ProRatedFactor": 0.25, + "GrossInterestRate": 0.0125, + "RetirementCredit": 2469.600, + "RetirementCapitalWithoutInterest": 153379.800, + "RetirementCapital": 168089.80933134296746399384217, + "IsRetirementDate": false, + "IsEndOfSavings": false, + "IsFullYear": false, + "IsFullAge": true + } +] diff --git a/src/BvgCalculator/BvgCalculator.cs b/src/BvgCalculator/BvgCalculator.cs deleted file mode 100644 index e39d7a84..00000000 --- a/src/BvgCalculator/BvgCalculator.cs +++ /dev/null @@ -1,338 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using FluentValidation; -using FluentValidation.Results; -using LanguageExt; -using PensionCoach.Tools.BvgCalculator.Models; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonUtils; - -namespace PensionCoach.Tools.BvgCalculator -{ - public class BvgCalculator : IBvgCalculator - { - private readonly IBvgRetirementCredits RetirementCredits; - private readonly IValidator bvgPersonValidator; - - public BvgCalculator( - IBvgRetirementCredits retirementCredits, IValidator bvgPersonValidator) - { - RetirementCredits = retirementCredits; - this.bvgPersonValidator = bvgPersonValidator; - } - - public Task> CalculateAsync( - PredecessorRetirementCapital predecessorCapital, DateTime dateOfProcess, BvgPerson person) - { - Option validationResult = bvgPersonValidator.Validate(person); - - return validationResult - .Where(r => !r.IsValid) - .Map>(r => - { - var errorMessageLine = string.Join(";", r.Errors.Select(x => x.ErrorMessage)); - return $"validation failed: {errorMessageLine}"; - }) - .IfNone(true) - .Bind(_ => CalculateInternal(predecessorCapital, dateOfProcess, person)) - .AsTask(); - } - - private Either CalculateInternal(PredecessorRetirementCapital predecessorCapital, in DateTime dateOfProcess, BvgPerson person) - { - BvgSalary salary = GetBvgSalary(dateOfProcess, person); - - decimal retirementCreditFactor = GetRetirementCreditFactor(person, dateOfProcess); - decimal retirementCredit = salary.InsuredSalary * retirementCreditFactor; - - IReadOnlyCollection retirementCreditSequence = - GetRetirementCreditSequence(person, dateOfProcess, salary); - - IReadOnlyCollection retirementCapitalSequence = - GetRetirementCapitalSequence(predecessorCapital, dateOfProcess, person, retirementCreditSequence); - - decimal retirementCapitalEndOfYear = - GetRetirementCapitalEndOfYear(dateOfProcess, retirementCapitalSequence); - - decimal finalRetirementCapital = - GetFinalRetirementCapital(retirementCapitalSequence); - - decimal finalRetirementCapitalWithoutInterest = - GetFinalRetirementCapitalWithoutInterest(retirementCapitalSequence); - - decimal retirementPension = GetRetirementPension(predecessorCapital, person, dateOfProcess, retirementCreditSequence); - - // reset risk benefits to 0 if below salary threshold - decimal disabilityPension = 0; - decimal partnerPension = 0; - decimal childPension = 0; - decimal orphanPension = 0; - - int year = dateOfProcess.Year; - - if (salary.EffectiveSalary > Bvg.GetEntranceThreshold(Bvg.GetPensionMaximum(year))) - { - disabilityPension = GetDisabilityPension(retirementCapitalSequence, person, dateOfProcess); - partnerPension = GetPartnerPension(retirementCapitalSequence, dateOfProcess, person); - childPension = GetChildPensionForDisabled(retirementCapitalSequence, person, dateOfProcess); - orphanPension = childPension; - } - - Either result = new BvgCalculationResult - { - DateOfRetirement = GetRetirementDate(person.DateOfBirth, person.Gender), - EffectiveSalary = salary.EffectiveSalary, - InsuredSalary = salary.InsuredSalary, - RetirementCredit = retirementCredit, - RetirementCreditFactor = retirementCreditFactor, - RetirementPension = retirementPension, - RetirementCapitalEndOfYear = retirementCapitalEndOfYear, - FinalRetirementCapital = finalRetirementCapital, - FinalRetirementCapitalWithoutInterest = finalRetirementCapitalWithoutInterest, - DisabilityPension = disabilityPension, - PartnerPension = partnerPension, - OrphanPension = orphanPension, - ChildPensionForDisabled = childPension, - RetirementCreditSequence = retirementCreditSequence, - RetirementCapitalSequence = retirementCapitalSequence - }; - - return result; - } - - public static bool IsRetired(BvgPerson person, DateTime dateOfProcess) - { - DateTime retiredAt = GetRetirementDate(person.DateOfBirth, person.Gender); - - return (new DateTime(retiredAt.Year, retiredAt.Month, 1).AddDays(-1) < dateOfProcess.Date); - } - - public static DateTime GetRetirementDate(DateTime dateOfBirth, Gender gender) - { - // FinalAgeByPlan BVG - int xsBvg = GetRetirementAge(gender); - - // Date of retirement - return dateOfBirth.GetRetirementDate(xsBvg); - } - - public static int GetRetirementAge(Gender typeOfGender) - { - return typeOfGender switch - { - Gender.Female => Bvg.RetirementAgeWomen, - Gender.Male => Bvg.RetirementAgeMen, - _ => throw new ArgumentException(nameof(typeOfGender)) - }; - } - - private static BvgSalary GetBvgSalary(DateTime dateOfProcess, BvgPerson person) - { - decimal workingAbilityDegree = decimal.One - person.DisabilityDegree; - - BvgSalary salary = new BvgSalary(); - - bool isRetired = IsRetired(person, dateOfProcess); - - if (isRetired && person.DisabilityDegree == decimal.One) - { - return salary; - } - - salary.ReportedSalary = person.ReportedSalary; - salary.EffectiveSalary = person.ReportedSalary * workingAbilityDegree; - salary.InsuredSalary = isRetired ? 0M : GetInsuredSalary(person, dateOfProcess); - - return salary; - } - - private decimal GetRetirementCapitalEndOfYear(DateTime dateOfProcess, - IReadOnlyCollection retirementCapitalSequence) - { - DateTime endOfYearDate = dateOfProcess.GetEndOfYearDate(); - - return retirementCapitalSequence - .Where(item => item.Date == endOfYearDate.Date) - .Select(item => item.Value) - .DefaultIfEmpty(retirementCapitalSequence.Last().Value) - .Single(); - } - - private decimal GetRetirementPension( - PredecessorRetirementCapital predecessorCapital, - BvgPerson personDetails, - DateTime dateOfProcess, - IReadOnlyCollection retirementCreditSequence) - { - IEnumerable retirementCapitalSequence = - GetRetirementCapitalSequence(predecessorCapital, dateOfProcess, personDetails, retirementCreditSequence); - - RetirementCapital latestElement = retirementCapitalSequence.First(); - - return MathUtils.Round((latestElement.Value) * Bvg.GetUwsRateBvg(dateOfProcess.Year, personDetails.Gender)); - } - - private decimal GetPartnerPension( - IReadOnlyCollection retirementCapitalSequence, - DateTime dateOfProcess, - BvgPerson personDetails) - { - decimal capital = GetFinalRetirementCapitalWithoutInterest(retirementCapitalSequence); - - return MathUtils.Round(capital * Bvg.FactorPartnersPension * - Bvg.GetUwsRateBvg(dateOfProcess.Year, personDetails.Gender)); - } - - private decimal GetChildPensionForDisabled( - IReadOnlyCollection retirementCapitalSequence, - BvgPerson personDetails, - DateTime dateOfProcess) - { - decimal capital = GetFinalRetirementCapitalWithoutInterest(retirementCapitalSequence); - - return MathUtils.Round(capital * Bvg.FactorChildPension * - Bvg.GetUwsRateBvg(dateOfProcess.Year, personDetails.Gender)); - } - - private decimal GetDisabilityPension( - IReadOnlyCollection retirementCapitalSequence, BvgPerson personDetails, DateTime dateOfProcess) - { - decimal capital = GetFinalRetirementCapitalWithoutInterest(retirementCapitalSequence); - - return MathUtils.Round(capital * Bvg.GetUwsRateBvg(dateOfProcess.Year, personDetails.Gender)); - } - - private decimal GetFinalRetirementCapital( - IReadOnlyCollection retirementCapitalSequence) - { - RetirementCapital final = retirementCapitalSequence.First(); - - return MathUtils.Round(final.Value); - } - - private decimal GetFinalRetirementCapitalWithoutInterest(IReadOnlyCollection retirementCapitalSequence) - { - RetirementCapital final = retirementCapitalSequence.First(); - - return MathUtils.Round(final.ValueWithoutInterest); - } - - private IReadOnlyCollection GetRetirementCapitalSequence( - PredecessorRetirementCapital predecessorCapital, - DateTime dateOfProcess, - BvgPerson personDetails, - IReadOnlyCollection retirementCreditSequence) - { - // Date of retirement - DateTime dateOfRetirement = GetRetirementDate(personDetails.DateOfBirth, personDetails.Gender); - - // Interest rates - decimal iBvg = Bvg.GetInterestRate(dateOfProcess.Year); - - // Retirement assets at end of insurance period Bvg portion - int age = dateOfProcess.Year - personDetails.DateOfBirth.Year; - int retirementAgeBvg = GetRetirementAge(personDetails.Gender); - - return BvgCapitalCalculationHelper.GetRetirementCapitalSequence(dateOfProcess, - dateOfRetirement, - age, - retirementAgeBvg, - iBvg, - predecessorCapital, - retirementCreditSequence); - } - - private IReadOnlyCollection GetRetirementCreditSequence( - BvgPerson personDetails, - DateTime dateOfProcess, - BvgSalary salary) - { - return BvgCapitalCalculationHelper.GetRetirementCreditSequence(personDetails, dateOfProcess, salary); - } - - private decimal GetRetirementCreditFactor(BvgPerson person, DateTime dateOfProcess) - { - int xBvg = dateOfProcess.Year - person.DateOfBirth.Year; - - return RetirementCredits.GetRate(xBvg); - } - - private static decimal GetInsuredSalary(BvgPerson person, DateTime dateOfProcess) - { - const decimal fullEmployedDegree = 1.0M; - - int year = dateOfProcess.Year; - - Option insuredSalary = 0M; - - if (person.DisabilityDegree == fullEmployedDegree) - { - return insuredSalary.IfNone(0M); - } - - if (person.DisabilityDegree > 0M) - { - insuredSalary = GetInsuredSalaryWhenDisabled(); - } - else - { - insuredSalary = GetInsuredSalaryWhenNotDisabled(); - } - - return MathUtils.Round5(insuredSalary.IfNone(0M)); - - Option GetInsuredSalaryWhenNotDisabled() - { - decimal ahvMax = Bvg.GetPensionMaximum(year); - - return Prelude.Some(person.ReportedSalary) - - // check salary entrance level - .Where(v => v > Bvg.GetEntranceThreshold(ahvMax)) - - // restrict by BVG salary max - .Map(v => Math.Min(v, Bvg.GetMaximumSalary(ahvMax))) - - // reduce by coordination deduction - .Map(v => v - GetCoordinationDeduction()) - - .Map(v => Math.Max(v, Bvg.GetMinimumSalary(ahvMax))) - .Map(v => Math.Round(v, 0, MidpointRounding.AwayFromZero)); - } - - Option GetInsuredSalaryWhenDisabled() - { - decimal minSalary = Bvg.GetMinimumSalary(Bvg.GetPensionMaximum(year)); - - Option disabilityDegree = person.DisabilityDegree; - - return disabilityDegree - .Where(v => v > 0 && v < decimal.One) - .Map(v => fullEmployedDegree - v) - - // scale salary up - .Map(v => person.ReportedSalary / v) - - // check salary entrance level - .Where(v => v > Bvg.GetEntranceThreshold(Bvg.GetPensionMaximum(year))) - - .Map(v => Math.Min(v, Bvg.GetMaximumSalary(Bvg.GetPensionMaximum(dateOfProcess.Year)))) - - // reduce by coordination deduction - .Map(v => v - GetCoordinationDeduction()) - - // restrict by BVG salary max - .Map(v => v * (fullEmployedDegree - person.DisabilityDegree)) - .Map(v => v < minSalary ? minSalary : v) - .Map(MathUtils.Round5); - } - - decimal GetCoordinationDeduction() - { - return Bvg.GetCoordinationDeduction(Bvg.GetPensionMaximum(year)); - } - } - } -} diff --git a/src/BvgCalculator/BvgCalculator.csproj b/src/BvgCalculator/BvgCalculator.csproj deleted file mode 100644 index aaf98792..00000000 --- a/src/BvgCalculator/BvgCalculator.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - net7.0 - PensionCoach.Tools.BvgCalculator - PensionCoach.Tools.BvgCalculator - - - - - - - - - - - - - diff --git a/src/BvgCalculator/BvgCalculatorsCollectionExtensions.cs b/src/BvgCalculator/BvgCalculatorsCollectionExtensions.cs deleted file mode 100644 index 8757610e..00000000 --- a/src/BvgCalculator/BvgCalculatorsCollectionExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using FluentValidation; -using Microsoft.Extensions.DependencyInjection; -using PensionCoach.Tools.BvgCalculator.Models; -using PensionCoach.Tools.BvgCalculator.Validators; - - -namespace PensionCoach.Tools.BvgCalculator -{ - public static class BvgCalculatorsCollectionExtensions - { - public static void AddBvgCalculators(this IServiceCollection serviceCollection) - { - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - - serviceCollection.AddSingleton, BvgPersonValidator>(); - } - } -} diff --git a/src/BvgCalculator/OccupationalBenefitsDateExtensions.cs b/src/BvgCalculator/OccupationalBenefitsDateExtensions.cs deleted file mode 100644 index 848da3c1..00000000 --- a/src/BvgCalculator/OccupationalBenefitsDateExtensions.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using PensionCoach.Tools.CommonTypes; - -namespace PensionCoach.Tools.BvgCalculator -{ - /// - /// Extension methods related to date calculations in - /// the context of occupational benefits (Vorsorge) - /// - public static class OccupationalBenefitsDateExtensions - { - public static DateTime GetRetirementDate(this DateTime birthdate, int retirementAge) - { - - // Date of retirement - return birthdate - .GetBirthdateTechnical() - .AddYears(retirementAge); - } - - public static DateTime GetRetirementDate(this DateTime birthdate, Gender gender) - { - // FinalAgeByPlan BVG - int xsBvg = Bvg.GetRetirementAge(gender); - - // Date of retirement - return GetRetirementDate(birthdate, xsBvg); - } - - /// - /// Gets the birthdate technical: first day of following month. - /// - /// The birthdate. - /// - public static DateTime GetBirthdateTechnical(this DateTime birthdate) - { - return new DateTime(birthdate.Year, birthdate.Month, 1).AddMonths(1); - } - - public static int GetBvgAge(this DateTime birthdate, int calculationYear) - { - return calculationYear - birthdate.Year; - } - } -} \ No newline at end of file diff --git a/src/BvgCalculator/Validators/BvgPersonValidator.cs b/src/BvgCalculator/Validators/BvgPersonValidator.cs deleted file mode 100644 index 4a563a9b..00000000 --- a/src/BvgCalculator/Validators/BvgPersonValidator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using FluentValidation; -using PensionCoach.Tools.BvgCalculator.Models; -using PensionCoach.Tools.CommonTypes; - -namespace PensionCoach.Tools.BvgCalculator.Validators -{ - public class BvgPersonValidator : AbstractValidator - { - public BvgPersonValidator() - { - RuleFor(x => x.Gender).Must(x => x != Gender.Undefined); - RuleFor(x => x.PartTimeDegree).Must(x => x > decimal.Zero); - } - } -} diff --git a/src/Calculators.CashFlow.Integration.Tests/Calculators.CashFlow.Integration.Tests.csproj b/src/Calculators.CashFlow.Integration.Tests/Calculators.CashFlow.Integration.Tests.csproj index f68a0c45..cf914435 100644 --- a/src/Calculators.CashFlow.Integration.Tests/Calculators.CashFlow.Integration.Tests.csproj +++ b/src/Calculators.CashFlow.Integration.Tests/Calculators.CashFlow.Integration.Tests.csproj @@ -1,29 +1,28 @@  - net7.0 + net8.0 false - - - - - - - + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - diff --git a/src/Calculators.CashFlow.Integration.Tests/CashFlowCalculatorsIntegrationTests.cs b/src/Calculators.CashFlow.Integration.Tests/CashFlowCalculatorsIntegrationTests.cs index 21c2deca..b82411a1 100644 --- a/src/Calculators.CashFlow.Integration.Tests/CashFlowCalculatorsIntegrationTests.cs +++ b/src/Calculators.CashFlow.Integration.Tests/CashFlowCalculatorsIntegrationTests.cs @@ -3,12 +3,13 @@ using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; +using Application.Features.TaxScenarios.Models; +using Domain.Enums; +using Domain.Models.Tax; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxComparison; using Snapshooter.Xunit; using TaxCalculator.WebApi; using Xunit; @@ -39,7 +40,7 @@ public async Task ShouldCalculateSuccessfully() { CapitalBenefitTransferInComparerRequest request = GetRequest(); - var response = await client.PostAsJsonAsync("CalculateTransferInCapitalBenefits", request); + var response = await client.PostAsJsonAsync("CalculateCapitalBenefitTransferInsYears", request); response.EnsureSuccessStatusCode(); diff --git a/src/Calculators.CashFlow.Integration.Tests/MultiPeriodCalculatorsIntegrationTests.cs b/src/Calculators.CashFlow.Integration.Tests/MultiPeriodCalculatorsIntegrationTests.cs index 1919e0f5..e4f06845 100644 --- a/src/Calculators.CashFlow.Integration.Tests/MultiPeriodCalculatorsIntegrationTests.cs +++ b/src/Calculators.CashFlow.Integration.Tests/MultiPeriodCalculatorsIntegrationTests.cs @@ -2,98 +2,100 @@ using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; +using Domain.Enums; +using Domain.Models.MultiPeriod.Definitions; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using PensionCoach.Tools.CommonTypes; using PensionCoach.Tools.CommonTypes.MultiPeriod; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; using Snapshooter.Xunit; using TaxCalculator.WebApi; using Xunit; -namespace Calculators.CashFlow.Integration.Tests; - -[Trait("Cash-Flow", "Integration")] -public class MultiPeriodCalculatorsIntegrationTests : IClassFixture> +namespace Calculators.CashFlow.Integration.Tests { - private readonly HttpClient client; - - public MultiPeriodCalculatorsIntegrationTests() + [Trait("Cash-Flow", "Integration")] + public class MultiPeriodCalculatorsIntegrationTests : IClassFixture> { - var testServer = new TestServer( - new WebHostBuilder() - .ConfigureAppConfiguration((_, builder) => - { - builder.AddJsonFile("appsettings.integration.json"); - }) - .UseStartup()); - - client = testServer.CreateClient(); - client.BaseAddress = new Uri("http://localhost/api/calculator/"); - } + private readonly HttpClient client; - [Fact(DisplayName = "Wealth Only")] - public async Task Calculate_Wealth_Only() - { - MultiPeriodRequest request = GetRequest(); - - var response = await client.PostAsJsonAsync("multiperiod", request); - - response.EnsureSuccessStatusCode(); + public MultiPeriodCalculatorsIntegrationTests() + { + var testServer = new TestServer( + new WebHostBuilder() + .ConfigureAppConfiguration((_, builder) => + { + builder.AddJsonFile("appsettings.integration.json"); + }) + .UseStartup()); + + client = testServer.CreateClient(); + client.BaseAddress = new Uri("http://localhost/api/calculator/"); + } + + [Fact(DisplayName = "Wealth Only")] + public async Task Calculate_Wealth_Only() + { + MultiPeriodRequest request = GetRequest(); - var result = await response.Content.ReadAsStringAsync(); + var response = await client.PostAsJsonAsync("multiperiod", request); - Snapshot.Match(result); - } + response.EnsureSuccessStatusCode(); - [Fact(DisplayName = "Wealth And Income")] - public async Task Calculate_Wealth_And_Income() - { - // given - decimal income = 100000; + var result = await response.Content.ReadAsStringAsync(); - MultiPeriodRequest request = GetRequest(); + Snapshot.Match(result); + } - request.CashFlowDefinitionRequest.SalaryPaymentsDefinition = new SalaryPaymentsDefinition + [Fact(DisplayName = "Wealth And Income")] + public async Task Calculate_Wealth_And_Income() { - YearlyAmount = income, - DateOfEndOfPeriod = new DateTime(2034, 4, 1), - }; + // given + decimal income = 100000; + + MultiPeriodRequest request = GetRequest(); - var response = await client.PostAsJsonAsync("multiperiod", request); + request.CashFlowDefinitionRequest.SalaryPaymentsDefinition = new SalaryPaymentsDefinition + { + YearlyAmount = income, + DateOfEndOfPeriod = new DateTime(2034, 4, 1), + }; - response.EnsureSuccessStatusCode(); + var response = await client.PostAsJsonAsync("multiperiod", request); - var result = await response.Content.ReadAsStringAsync(); + response.EnsureSuccessStatusCode(); - Snapshot.Match(result); - } + var result = await response.Content.ReadAsStringAsync(); - private static MultiPeriodRequest GetRequest() - { - decimal wealth = 500000; + Snapshot.Match(result); + } - return new MultiPeriodRequest + private static MultiPeriodRequest GetRequest() { - StartingYear = 2023, - NumberOfPeriods = 10, - Name = "Test Multi-Period Calculator", - BfsMunicipalityId = 261, - CivilStatus = CivilStatus.Single, - Income = 0, - Wealth = wealth, - DateOfBirth = new DateTime(1980, 1, 1), - ReligiousGroupType = ReligiousGroupType.Other, - Gender = Gender.Male, - CashFlowDefinitionRequest = new() + decimal wealth = 500000; + + return new MultiPeriodRequest { - SetupAccountDefinition = new SetupAccountDefinition + StartingYear = 2023, + NumberOfPeriods = 10, + Name = "Test Multi-Period Calculator", + BfsMunicipalityId = 261, + CivilStatus = CivilStatus.Single, + Income = 0, + Wealth = wealth, + DateOfBirth = new DateTime(1980, 1, 1), + ReligiousGroupType = ReligiousGroupType.Other, + Gender = Gender.Male, + CashFlowDefinitionRequest = new() { - InitialWealth = wealth + SetupAccountDefinition = new SetupAccountDefinition + { + InitialWealth = wealth + } } - } - }; + }; + } } } diff --git a/src/Calculators.CashFlow.Integration.Tests/__snapshots__/MultiPeriodCalculatorsIntegrationTests.Calculate_Wealth_And_Income.snap b/src/Calculators.CashFlow.Integration.Tests/__snapshots__/MultiPeriodCalculatorsIntegrationTests.Calculate_Wealth_And_Income.snap index 536a6148..333ff474 100644 --- a/src/Calculators.CashFlow.Integration.Tests/__snapshots__/MultiPeriodCalculatorsIntegrationTests.Calculate_Wealth_And_Income.snap +++ b/src/Calculators.CashFlow.Integration.Tests/__snapshots__/MultiPeriodCalculatorsIntegrationTests.Calculate_Wealth_And_Income.snap @@ -1 +1 @@ -{"startingYear":2023,"numberOfPeriods":12,"accounts":[{"year":2023,"amount":-512574.6350,"accountType":"Exogenous"},{"year":2023,"amount":0,"accountType":"Income"},{"year":2023,"amount":512574.6350,"accountType":"Wealth"},{"year":2023,"amount":0,"accountType":"OccupationalPension"},{"year":2023,"amount":0,"accountType":"ThirdPillar"},{"year":2024,"amount":-525122.990000,"accountType":"Exogenous"},{"year":2024,"amount":0,"accountType":"Income"},{"year":2024,"amount":525122.990000,"accountType":"Wealth"},{"year":2024,"amount":0,"accountType":"OccupationalPension"},{"year":2024,"amount":0,"accountType":"ThirdPillar"},{"year":2025,"amount":-537642.87500000,"accountType":"Exogenous"},{"year":2025,"amount":0,"accountType":"Income"},{"year":2025,"amount":537642.87500000,"accountType":"Wealth"},{"year":2025,"amount":0,"accountType":"OccupationalPension"},{"year":2025,"amount":0,"accountType":"ThirdPillar"},{"year":2026,"amount":-550136.4800000000,"accountType":"Exogenous"},{"year":2026,"amount":0,"accountType":"Income"},{"year":2026,"amount":550136.4800000000,"accountType":"Wealth"},{"year":2026,"amount":0,"accountType":"OccupationalPension"},{"year":2026,"amount":0,"accountType":"ThirdPillar"},{"year":2027,"amount":-562601.615000000000,"accountType":"Exogenous"},{"year":2027,"amount":0,"accountType":"Income"},{"year":2027,"amount":562601.615000000000,"accountType":"Wealth"},{"year":2027,"amount":0,"accountType":"OccupationalPension"},{"year":2027,"amount":0,"accountType":"ThirdPillar"},{"year":2028,"amount":-575040.47000000000000,"accountType":"Exogenous"},{"year":2028,"amount":0,"accountType":"Income"},{"year":2028,"amount":575040.47000000000000,"accountType":"Wealth"},{"year":2028,"amount":0,"accountType":"OccupationalPension"},{"year":2028,"amount":0,"accountType":"ThirdPillar"},{"year":2029,"amount":-587450.8550000000000000,"accountType":"Exogenous"},{"year":2029,"amount":0,"accountType":"Income"},{"year":2029,"amount":587450.8550000000000000,"accountType":"Wealth"},{"year":2029,"amount":0,"accountType":"OccupationalPension"},{"year":2029,"amount":0,"accountType":"ThirdPillar"},{"year":2030,"amount":-599834.960000000000000000,"accountType":"Exogenous"},{"year":2030,"amount":0,"accountType":"Income"},{"year":2030,"amount":599834.960000000000000000,"accountType":"Wealth"},{"year":2030,"amount":0,"accountType":"OccupationalPension"},{"year":2030,"amount":0,"accountType":"ThirdPillar"},{"year":2031,"amount":-612192.78500000000000000000,"accountType":"Exogenous"},{"year":2031,"amount":0,"accountType":"Income"},{"year":2031,"amount":612192.78500000000000000000,"accountType":"Wealth"},{"year":2031,"amount":0,"accountType":"OccupationalPension"},{"year":2031,"amount":0,"accountType":"ThirdPillar"},{"year":2032,"amount":-624522.1400000000000000000000,"accountType":"Exogenous"},{"year":2032,"amount":0,"accountType":"Income"},{"year":2032,"amount":624522.1400000000000000000000,"accountType":"Wealth"},{"year":2032,"amount":0,"accountType":"OccupationalPension"},{"year":2032,"amount":0,"accountType":"ThirdPillar"},{"year":2033,"amount":-636825.21500000000000000000000,"accountType":"Exogenous"},{"year":2033,"amount":0,"accountType":"Income"},{"year":2033,"amount":636825.21500000000000000000000,"accountType":"Wealth"},{"year":2033,"amount":0,"accountType":"OccupationalPension"},{"year":2033,"amount":0,"accountType":"ThirdPillar"},{"year":2034,"amount":-641908.82000000000000000000000,"accountType":"Exogenous"},{"year":2034,"amount":0.00,"accountType":"Income"},{"year":2034,"amount":641908.82000000000000000000000,"accountType":"Wealth"},{"year":2034,"amount":0,"accountType":"OccupationalPension"},{"year":2034,"amount":0,"accountType":"ThirdPillar"}]} +{"startingYear":2023,"numberOfPeriods":12,"accounts":[{"year":2023,"amount":-530000.00,"accountType":"Exogenous"},{"year":2023,"amount":0,"accountType":"Income"},{"year":2023,"amount":512574.6350,"accountType":"Wealth"},{"year":2023,"amount":0,"accountType":"Investment"},{"year":2023,"amount":0,"accountType":"OccupationalPension"},{"year":2023,"amount":0,"accountType":"ThirdPillar"},{"year":2023,"amount":17425.3650,"accountType":"Tax"},{"year":2024,"amount":-560000.00,"accountType":"Exogenous"},{"year":2024,"amount":0,"accountType":"Income"},{"year":2024,"amount":525122.990000,"accountType":"Wealth"},{"year":2024,"amount":0,"accountType":"Investment"},{"year":2024,"amount":0,"accountType":"OccupationalPension"},{"year":2024,"amount":0,"accountType":"ThirdPillar"},{"year":2024,"amount":34877.010000,"accountType":"Tax"},{"year":2025,"amount":-590000.00,"accountType":"Exogenous"},{"year":2025,"amount":0,"accountType":"Income"},{"year":2025,"amount":537642.87500000,"accountType":"Wealth"},{"year":2025,"amount":0,"accountType":"Investment"},{"year":2025,"amount":0,"accountType":"OccupationalPension"},{"year":2025,"amount":0,"accountType":"ThirdPillar"},{"year":2025,"amount":52357.12500000,"accountType":"Tax"},{"year":2026,"amount":-620000.00,"accountType":"Exogenous"},{"year":2026,"amount":0,"accountType":"Income"},{"year":2026,"amount":550136.4800000000,"accountType":"Wealth"},{"year":2026,"amount":0,"accountType":"Investment"},{"year":2026,"amount":0,"accountType":"OccupationalPension"},{"year":2026,"amount":0,"accountType":"ThirdPillar"},{"year":2026,"amount":69863.5200000000,"accountType":"Tax"},{"year":2027,"amount":-650000.00,"accountType":"Exogenous"},{"year":2027,"amount":0,"accountType":"Income"},{"year":2027,"amount":562601.615000000000,"accountType":"Wealth"},{"year":2027,"amount":0,"accountType":"Investment"},{"year":2027,"amount":0,"accountType":"OccupationalPension"},{"year":2027,"amount":0,"accountType":"ThirdPillar"},{"year":2027,"amount":87398.385000000000,"accountType":"Tax"},{"year":2028,"amount":-680000.00,"accountType":"Exogenous"},{"year":2028,"amount":0,"accountType":"Income"},{"year":2028,"amount":575040.47000000000000,"accountType":"Wealth"},{"year":2028,"amount":0,"accountType":"Investment"},{"year":2028,"amount":0,"accountType":"OccupationalPension"},{"year":2028,"amount":0,"accountType":"ThirdPillar"},{"year":2028,"amount":104959.53000000000000,"accountType":"Tax"},{"year":2029,"amount":-710000.00,"accountType":"Exogenous"},{"year":2029,"amount":0,"accountType":"Income"},{"year":2029,"amount":587450.8550000000000000,"accountType":"Wealth"},{"year":2029,"amount":0,"accountType":"Investment"},{"year":2029,"amount":0,"accountType":"OccupationalPension"},{"year":2029,"amount":0,"accountType":"ThirdPillar"},{"year":2029,"amount":122549.1450000000000000,"accountType":"Tax"},{"year":2030,"amount":-740000.00,"accountType":"Exogenous"},{"year":2030,"amount":0,"accountType":"Income"},{"year":2030,"amount":599834.960000000000000000,"accountType":"Wealth"},{"year":2030,"amount":0,"accountType":"Investment"},{"year":2030,"amount":0,"accountType":"OccupationalPension"},{"year":2030,"amount":0,"accountType":"ThirdPillar"},{"year":2030,"amount":140165.040000000000000000,"accountType":"Tax"},{"year":2031,"amount":-770000.00,"accountType":"Exogenous"},{"year":2031,"amount":0,"accountType":"Income"},{"year":2031,"amount":612192.78500000000000000000,"accountType":"Wealth"},{"year":2031,"amount":0,"accountType":"Investment"},{"year":2031,"amount":0,"accountType":"OccupationalPension"},{"year":2031,"amount":0,"accountType":"ThirdPillar"},{"year":2031,"amount":157807.21500000000000000000,"accountType":"Tax"},{"year":2032,"amount":-800000.00,"accountType":"Exogenous"},{"year":2032,"amount":0,"accountType":"Income"},{"year":2032,"amount":624522.1400000000000000000000,"accountType":"Wealth"},{"year":2032,"amount":0,"accountType":"Investment"},{"year":2032,"amount":0,"accountType":"OccupationalPension"},{"year":2032,"amount":0,"accountType":"ThirdPillar"},{"year":2032,"amount":175477.8600000000000000000000,"accountType":"Tax"},{"year":2033,"amount":-830000.00,"accountType":"Exogenous"},{"year":2033,"amount":0,"accountType":"Income"},{"year":2033,"amount":636825.21500000000000000000000,"accountType":"Wealth"},{"year":2033,"amount":0,"accountType":"Investment"},{"year":2033,"amount":0,"accountType":"OccupationalPension"},{"year":2033,"amount":0,"accountType":"ThirdPillar"},{"year":2033,"amount":193174.78500000000000000000000,"accountType":"Tax"},{"year":2034,"amount":-837500.0000,"accountType":"Exogenous"},{"year":2034,"amount":0.00,"accountType":"Income"},{"year":2034,"amount":641908.82000000000000000000000,"accountType":"Wealth"},{"year":2034,"amount":0,"accountType":"Investment"},{"year":2034,"amount":0,"accountType":"OccupationalPension"},{"year":2034,"amount":0,"accountType":"ThirdPillar"},{"year":2034,"amount":195591.18000000000000000000000,"accountType":"Tax"}]} diff --git a/src/Calculators.CashFlow.Integration.Tests/__snapshots__/MultiPeriodCalculatorsIntegrationTests.Calculate_Wealth_Only.snap b/src/Calculators.CashFlow.Integration.Tests/__snapshots__/MultiPeriodCalculatorsIntegrationTests.Calculate_Wealth_Only.snap index b6a52aac..483dcba7 100644 --- a/src/Calculators.CashFlow.Integration.Tests/__snapshots__/MultiPeriodCalculatorsIntegrationTests.Calculate_Wealth_Only.snap +++ b/src/Calculators.CashFlow.Integration.Tests/__snapshots__/MultiPeriodCalculatorsIntegrationTests.Calculate_Wealth_Only.snap @@ -1,31 +1 @@ -{ - "startingYear": 2023, - "numberOfPeriods": 1, - "accounts": [ - { - "year": 2023, - "amount": -499302.575, - "accountType": "Exogenous" - }, - { - "year": 2023, - "amount": 0, - "accountType": "Income" - }, - { - "year": 2023, - "amount": 499302.575, - "accountType": "Wealth" - }, - { - "year": 2023, - "amount": 0, - "accountType": "OccupationalPension" - }, - { - "year": 2023, - "amount": 0, - "accountType": "ThirdPillar" - } - ] -} +{"startingYear":2023,"numberOfPeriods":1,"accounts":[{"year":2023,"amount":-500000,"accountType":"Exogenous"},{"year":2023,"amount":0,"accountType":"Income"},{"year":2023,"amount":499302.575,"accountType":"Wealth"},{"year":2023,"amount":0,"accountType":"Investment"},{"year":2023,"amount":0,"accountType":"OccupationalPension"},{"year":2023,"amount":0,"accountType":"ThirdPillar"},{"year":2023,"amount":697.425,"accountType":"Tax"}]} diff --git a/src/Calculators.CashFlow.Tests/Calculators.CashFlow.Tests.csproj b/src/Calculators.CashFlow.Tests/Calculators.CashFlow.Tests.csproj index 36a69dd6..7bb68685 100644 --- a/src/Calculators.CashFlow.Tests/Calculators.CashFlow.Tests.csproj +++ b/src/Calculators.CashFlow.Tests/Calculators.CashFlow.Tests.csproj @@ -1,28 +1,28 @@  - net7.0 + net8.0 false - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/src/Calculators.CashFlow.Tests/CashFlowFixture.cs b/src/Calculators.CashFlow.Tests/CashFlowFixture.cs index df54fe6a..d5230435 100644 --- a/src/Calculators.CashFlow.Tests/CashFlowFixture.cs +++ b/src/Calculators.CashFlow.Tests/CashFlowFixture.cs @@ -3,53 +3,56 @@ using System.IO; using System.Linq; using System.Reflection; +using Application.Features.FullTaxCalculation; +using Application.Features.PensionVersusCapital; +using Application.MultiPeriodCalculator; +using Infrastructure.Configuration; +using Infrastructure.Tax.Data; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using PensionCoach.Tools.TaxCalculator; -using Tax.Data; -namespace Calculators.CashFlow.Tests +namespace Calculators.CashFlow.Tests; + +public class CashFlowFixture + where T : class { - public class CashFlowFixture - where T : class - { - public ServiceProvider Provider { get; } + public ServiceProvider Provider { get; } - public T Calculator { get; } + public T Calculator { get; } - public T Service { get; } + public T Service { get; } - public CashFlowFixture() - { - var projectPath = Assembly.GetExecutingAssembly() - .Location.Split("src", StringSplitOptions.RemoveEmptyEntries) - .First(); + public CashFlowFixture() + { + var projectPath = Assembly.GetExecutingAssembly() + .Location.Split("src", StringSplitOptions.RemoveEmptyEntries) + .First(); - var dbFile = Path.Combine(projectPath, @"src\Tax.Data\files\TaxDb.db"); + var dbFile = Path.Combine(projectPath, @"src\Infrastructure\files\TaxDb.db"); - var configurationDict = new Dictionary - { - {"ConnectionStrings:TaxDb", dbFile} - }; + var configurationDict = new Dictionary + { + {"ConnectionStrings:TaxDb", dbFile} + }; - IConfiguration configuration = new ConfigurationBuilder() - .AddInMemoryCollection(configurationDict) - .Build(); + IConfiguration configuration = new ConfigurationBuilder() + .AddInMemoryCollection(configurationDict) + .Build(); - ServiceCollection coll = new ServiceCollection(); - coll.AddSingleton(configuration); - coll.AddOptions(); - coll.AddLogging(); - coll.AddCashFlowCalculators(); - coll.AddTaxCalculators(configuration); - coll.AddTaxData(configuration); + ServiceCollection coll = new ServiceCollection(); + coll.AddSingleton(configuration); + coll.AddOptions(); + coll.AddLogging(); + coll.AddCashFlowCalculators(); + coll.AddToolsCalculators(); + coll.AddTaxCalculators(configuration.GetApplicationMode()); + coll.AddTaxData(configuration); - Provider = coll.BuildServiceProvider(); + Provider = coll.BuildServiceProvider(); - Calculator = Provider.GetRequiredService(); + Calculator = Provider.GetRequiredService(); - Service = Provider.GetRequiredService(); - } + Service = Provider.GetRequiredService(); } } diff --git a/src/Calculators.CashFlow.Tests/DomainModelSerializationTests.cs b/src/Calculators.CashFlow.Tests/DomainModelSerializationTests.cs index 3731863e..0d309b79 100644 --- a/src/Calculators.CashFlow.Tests/DomainModelSerializationTests.cs +++ b/src/Calculators.CashFlow.Tests/DomainModelSerializationTests.cs @@ -1,40 +1,35 @@ using System; using System.Collections.Generic; -using System.Linq; -using Calculators.CashFlow.Models; +using Domain.Enums; +using Domain.Models.Municipality; using Newtonsoft.Json; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.MultiPeriod; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; using Snapshooter.Xunit; using Xunit; -namespace Calculators.CashFlow.Tests +namespace Calculators.CashFlow.Tests; + +[Trait("Domain Model", "Serialization")] +public class DomainModelSerializationTests { - [Trait("Domain Model", "Serialization")] - public class DomainModelSerializationTests + [Fact(DisplayName = "Deserialize Municipality")] + public void Deserialize_MunicipalityModel() { - [Fact(DisplayName = "Deserialize Municipality")] - public void Deserialize_MunicipalityModel() + // given + MunicipalityModel model = new MunicipalityModel { - // given - MunicipalityModel model = new MunicipalityModel - { - BfsNumber = 261, - Canton = Canton.ZH, - Name = "Zürich", - MutationId = 1911, - SuccessorId = 33, - DateOfMutation = new DateTime(2020,1,1) - }; - string json = JsonConvert.SerializeObject(new[]{ model }); + BfsNumber = 261, + Canton = Canton.ZH, + Name = "Zürich", + MutationId = 1911, + SuccessorId = 33, + DateOfMutation = new DateTime(2020,1,1) + }; + string json = JsonConvert.SerializeObject(new[]{ model }); - // when - var result = JsonConvert.DeserializeObject>(json); + // when + var result = JsonConvert.DeserializeObject>(json); - // then - Snapshot.Match(result); - } + // then + Snapshot.Match(result); } } diff --git a/src/Calculators.CashFlow.Tests/GenericCashFlowTests.cs b/src/Calculators.CashFlow.Tests/GenericCashFlowTests.cs index 70fa396a..9a205ddb 100644 --- a/src/Calculators.CashFlow.Tests/GenericCashFlowTests.cs +++ b/src/Calculators.CashFlow.Tests/GenericCashFlowTests.cs @@ -1,141 +1,141 @@ using System; using System.Collections.Generic; using System.Linq; -using Calculators.CashFlow.Models; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.MultiPeriod; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; +using Application.MultiPeriodCalculator; +using Domain.Enums; +using Domain.Models.Cashflows; +using Domain.Models.MultiPeriod; +using Domain.Models.MultiPeriod.Definitions; using PensionCoach.Tools.CommonTypes.Tax; using Snapshooter.Xunit; using Xunit; -namespace Calculators.CashFlow.Tests +namespace Calculators.CashFlow.Tests; + +[Trait("Higher Level Calculators", "Cash-Flow")] +public class GenericCashFlowTests { - [Trait("Higher Level Calculators", "Cash-Flow")] - public class GenericCashFlowTests + [Fact(DisplayName = "Single Allocation for 10 Years")] + public void Generate_Single_Allocation_For_10_Years() { - [Fact(DisplayName = "Single Allocation for 10 Years")] - public void Generate_Single_Allocation_For_10_Years() + // given + StaticGenericCashFlowDefinition definition = new StaticGenericCashFlowDefinition { - // given - StaticGenericCashFlowDefinition definition = new StaticGenericCashFlowDefinition + Header = new CashFlowHeader + { + Id = "test", + Name = "Test", + }, + DateOfProcess = new DateTime(2021, 1, 1), + NetGrowthRate = 0, + InitialAmount = 9_500, + RecurringInvestment = new RecurringInvestment { - Header = new CashFlowHeader - { - Id = "test", - Name = "Test", - }, - DateOfProcess = new DateTime(2021, 1, 1), - NetGrowthRate = 0, - InitialAmount = 9_500, - RecurringInvestment = new RecurringInvestment - { - Amount = 500, - Frequency = FrequencyType.Yearly, - }, - Flow = new FlowPair(AccountType.Exogenous, AccountType.Wealth), - InvestmentPeriod = new InvestmentPeriod - { - Year = 2021, - NumberOfPeriods = 10 - }, - IsTaxable = true, - TaxType = TaxType.Wealth - }; + Amount = 500, + Frequency = FrequencyType.Yearly, + }, + Flow = new FlowPair(AccountType.Exogenous, AccountType.Wealth), + InvestmentPeriod = new InvestmentPeriod + { + Year = 2021, + NumberOfPeriods = 10 + }, + IsTaxable = true, + TaxType = TaxType.Wealth + }; - // when - List result = definition.GenerateCashFlow().ToList(); + // when + List result = definition.GenerateCashFlow().ToList(); - // then - Snapshot.Match(result); - } + // then + Snapshot.Match(result); + } - [Fact(DisplayName = "Aggregate Multiple Cash-Flows")] - public void Aggregate_Multiple_CashFlows() + [Fact(DisplayName = "Aggregate Multiple Cash-Flows")] + public void Aggregate_Multiple_CashFlows() + { + // given + StaticGenericCashFlowDefinition definition1 = new StaticGenericCashFlowDefinition { - // given - StaticGenericCashFlowDefinition definition1 = new StaticGenericCashFlowDefinition + Header = new CashFlowHeader + { + Id = "test1", + Name = "Test", + }, + DateOfProcess = new DateTime(2021, 1, 1), + NetGrowthRate = 0, + InitialAmount = 9_500, + RecurringInvestment = new RecurringInvestment + { + Amount = 500, + Frequency = FrequencyType.Yearly, + }, + Flow = new FlowPair(AccountType.Exogenous, AccountType.Wealth), + InvestmentPeriod = new InvestmentPeriod { - Header = new CashFlowHeader - { - Id = "test1", - Name = "Test", - }, - DateOfProcess = new DateTime(2021, 1, 1), - NetGrowthRate = 0, - InitialAmount = 9_500, - RecurringInvestment = new RecurringInvestment - { - Amount = 500, - Frequency = FrequencyType.Yearly, - }, - Flow = new FlowPair(AccountType.Exogenous, AccountType.Wealth), - InvestmentPeriod = new InvestmentPeriod - { - Year = 2021, - NumberOfPeriods = 10 - }, - IsTaxable = true, - TaxType = TaxType.Wealth - }; + Year = 2021, + NumberOfPeriods = 10 + }, + IsTaxable = true, + TaxType = TaxType.Wealth + }; - StaticGenericCashFlowDefinition definition2 = new StaticGenericCashFlowDefinition + StaticGenericCashFlowDefinition definition2 = new StaticGenericCashFlowDefinition + { + Header = new CashFlowHeader + { + Id = "test2", + Name = "Test 2", + }, + DateOfProcess = new DateTime(2021, 1, 1), + NetGrowthRate = 0, + InitialAmount = 19_500, + RecurringInvestment = new RecurringInvestment + { + Amount = 500, + Frequency = FrequencyType.Yearly, + }, + Flow = new FlowPair(AccountType.Exogenous, AccountType.Wealth), + InvestmentPeriod = new InvestmentPeriod { - Header = new CashFlowHeader - { - Id = "test2", - Name = "Test 2", - }, - DateOfProcess = new DateTime(2021, 1, 1), - NetGrowthRate = 0, - InitialAmount = 19_500, - RecurringInvestment = new RecurringInvestment - { - Amount = 500, - Frequency = FrequencyType.Yearly, - }, - Flow = new FlowPair(AccountType.Exogenous, AccountType.Wealth), - InvestmentPeriod = new InvestmentPeriod - { - Year = 2021, - NumberOfPeriods = 5 - }, - IsTaxable = true, - TaxType = TaxType.Wealth, - }; + Year = 2021, + NumberOfPeriods = 5 + }, + IsTaxable = true, + TaxType = TaxType.Wealth, + }; - StaticGenericCashFlowDefinition definition3 = new StaticGenericCashFlowDefinition + StaticGenericCashFlowDefinition definition3 = new StaticGenericCashFlowDefinition + { + Header = new CashFlowHeader + { + Id = "test3", + Name = "Test 3", + }, + DateOfProcess = new DateTime(2021, 1, 1), + NetGrowthRate = 0, + InitialAmount = 50_000, + RecurringInvestment = new RecurringInvestment + { + Amount = 0, + Frequency = FrequencyType.Yearly, + }, + Flow = new FlowPair(AccountType.Income, AccountType.OccupationalPension), + InvestmentPeriod = new InvestmentPeriod { - Header = new CashFlowHeader - { - Id = "test3", - Name = "Test 3", - }, - DateOfProcess = new DateTime(2021, 1, 1), - NetGrowthRate = 0, - InitialAmount = 50_000, - RecurringInvestment = new RecurringInvestment - { - Amount = 0, - Frequency = FrequencyType.Yearly, - }, - Flow = new FlowPair(AccountType.Income, AccountType.OccupationalPension), - InvestmentPeriod = new InvestmentPeriod - { - Year = 2021, - NumberOfPeriods = 0 - }, - TaxType = TaxType.Undefined, - IsTaxable = false, - }; + Year = 2021, + NumberOfPeriods = 0 + }, + TaxType = TaxType.Undefined, + IsTaxable = false, + }; - // when - IEnumerable result = new[] { definition1, definition2, definition3 } - .SelectMany(d => d.GenerateCashFlow()) - .AggregateCashFlows(); + // when + IEnumerable result = new[] { definition1, definition2, definition3 } + .SelectMany(d => d.GenerateCashFlow()) + .AggregateCashFlows(); - // then - Snapshot.Match(result); - } + // then + Snapshot.Match(result); } } diff --git a/src/Calculators.CashFlow.Tests/InvestmentPortfolioTests.cs b/src/Calculators.CashFlow.Tests/InvestmentPortfolioTests.cs new file mode 100644 index 00000000..c1aaeb1c --- /dev/null +++ b/src/Calculators.CashFlow.Tests/InvestmentPortfolioTests.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using Application.MultiPeriodCalculator; +using Domain.Contracts; +using Domain.Enums; +using Domain.Models.MultiPeriod; +using Domain.Models.MultiPeriod.Definitions; +using PensionCoach.Tools.CommonTypes; +using PensionCoach.Tools.CommonTypes.MultiPeriod; +using Snapshooter.Xunit; +using Xunit; + +namespace Calculators.CashFlow.Tests; + +[Trait("Higher Level Calculators", "Cash-Flow")] +public class InvestmentPortfolioTests +{ + [Fact(DisplayName = "Portfolio Growth")] + public void Calculate_Growth_Of_Investment_Portfolio() + { + // given + InvestmentPortfolioDefinition definition = new () + { + Header = new CashFlowHeader + { + Id = "test", + Name = "Test", + }, + DateOfProcess = new DateTime(2021, 1, 1), + NetCapitalGrowthRate = 0.02M, + NetIncomeRate = 0.01M, + InitialInvestment = 100_000, + RecurringInvestment = new RecurringInvestment + { + Amount = 6723, + Frequency = FrequencyType.Yearly, + }, + InvestmentPeriod = new InvestmentPeriod + { + Year = 2021, + NumberOfPeriods = 10 + }, + }; + + // when + IEnumerable result = definition.CreateGenericDefinition(); + + // then + Snapshot.Match(result); + } +} diff --git a/src/Calculators.CashFlow.Tests/MultiPeriodCashFlowCalculatorTests.cs b/src/Calculators.CashFlow.Tests/MultiPeriodCashFlowCalculatorTests.cs index fc037459..af704c67 100644 --- a/src/Calculators.CashFlow.Tests/MultiPeriodCashFlowCalculatorTests.cs +++ b/src/Calculators.CashFlow.Tests/MultiPeriodCashFlowCalculatorTests.cs @@ -2,13 +2,15 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Calculators.CashFlow.Models; +using Application.Extensions; +using Application.MultiPeriodCalculator; +using Domain.Contracts; +using Domain.Enums; +using Domain.Models.MultiPeriod; +using Domain.Models.MultiPeriod.Actions; +using Domain.Models.MultiPeriod.Definitions; using LanguageExt; -using PensionCoach.Tools.BvgCalculator; -using PensionCoach.Tools.CommonTypes; using PensionCoach.Tools.CommonTypes.MultiPeriod; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Actions; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; using Snapshooter.Xunit; using Xunit; @@ -52,6 +54,80 @@ public async Task Calculate_Zero_Simulation() Snapshot.Match(result, opt => opt.IgnoreFields("$..Id")); } + [Fact(DisplayName = "Investment Portfolio Simulation")] + public async Task Calculate_Investment_Portfolio() + { + // given + int startingYear = 2021; + int numberOfPeriods = 0; + int municipalityId = 261; + Canton canton = Canton.ZH; + decimal initialInvestmentAmount = 93277; + MultiPeriodOptions options = new(); + MultiPeriodCalculatorPerson person = GetMarriedPerson(canton, municipalityId) with + { + Income = 100_000, + Wealth = 500_000, + CapitalBenefits = (decimal.Zero, decimal.Zero) + }; + + // when + var result = await _fixture.Service.CalculateAsync( + startingYear, + numberOfPeriods, + person, + new CashFlowDefinitionHolder + { + InvestmentDefinitions = CreateInvestmentPortfolios().ToList(), + Composites = CreateComposites(person, initialInvestmentAmount).ToList() + }, + options); + + // then + Snapshot.Match(result, opt => opt.IgnoreFields("$..Id")); + + static IEnumerable CreateInvestmentPortfolios() + { + yield return new InvestmentPortfolioDefinition + { + Header = new CashFlowHeader() + { + Id = Guid.NewGuid().ToString(), + Name = "Investment Portfolio", + }, + DateOfProcess = new DateTime(2021, 1, 1), + NetCapitalGrowthRate = 0.02M, + NetIncomeRate = 0.01M, + RecurringInvestment = new RecurringInvestment + { + Amount = 6723, + Frequency = FrequencyType.Yearly, + }, + InvestmentPeriod = new InvestmentPeriod + { + Year = 2021, + NumberOfPeriods = 10 + }, + }; + } + + static IEnumerable CreateComposites( + MultiPeriodCalculatorPerson person, decimal initialInvestmentAmount) + { + yield return new SalaryPaymentsDefinition + { + YearlyAmount = person.Income, + DateOfEndOfPeriod = new DateTime(2021, 1, 1).AddYears(10) + }; + + yield return new SetupAccountDefinition + { + InitialWealth = person.Wealth, + InitialInvestmentAssets = initialInvestmentAmount + }; + } + } + [Fact(DisplayName = "Wealth Only Simulation")] public async Task Calculate_Wealth_Only_Simulation() { diff --git a/src/Calculators.CashFlow.Tests/ScenarioCapitalBenefitTransferInsTests.cs b/src/Calculators.CashFlow.Tests/ScenarioCapitalBenefitTransferInsTests.cs deleted file mode 100644 index 1de0771f..00000000 --- a/src/Calculators.CashFlow.Tests/ScenarioCapitalBenefitTransferInsTests.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxComparison; -using Snapshooter.Xunit; -using Xunit; - -namespace Calculators.CashFlow.Tests -{ - [Trait("Higher Level Calculators", "Transfer-Ins")] - public class ScenarioCapitalBenefitTransferInsTests : IClassFixture> - { - private readonly CashFlowFixture fixture; - - public ScenarioCapitalBenefitTransferInsTests(CashFlowFixture fixture) - { - this.fixture = fixture; - } - - [Fact(DisplayName = "Single Transfer-In With Withdrawal in 10 Years")] - public async Task Calculate_Single_TransferIn_With_Withdrawal_In_10_Years() - { - // given - int calculationYear = 2022; - int bfsMunicipalityId = 261; - TaxPerson person = new() - { - Name = "Unit Test Purchase", - CivilStatus = CivilStatus.Married, - ReligiousGroupType = ReligiousGroupType.Other, - PartnerReligiousGroupType = ReligiousGroupType.Other, - TaxableIncome = 150_000, - TaxableFederalIncome = 100_000, - TaxableWealth = 500_000 - }; - - TransferInCapitalBenefitsScenarioModel scenarioModel = new() - { - NetReturnCapitalBenefits = 0.0M, - TransferIns = new List - { - new(10000, new DateTime(2022, 1, 1)), - }, - WithCapitalBenefitWithdrawal = true, - CapitalBenefitsBeforeWithdrawal = 800_000, - Withdrawals = new List - { - new(0.5M, new DateTime(2032, 12, 31)), - new(1M, new DateTime(2033, 12, 31)), - }, - }; - - // when - var result = await fixture.Calculator.TransferInCapitalBenefitsAsync( - calculationYear, bfsMunicipalityId, person, scenarioModel); - - // then - Snapshot.Match(result, opt => opt.IgnoreFields("$..Id")); - } - } -} diff --git a/src/Calculators.CashFlow.Tests/Scenarios/PensionVersusCapitalComparisonTests.cs b/src/Calculators.CashFlow.Tests/Scenarios/PensionVersusCapitalComparisonTests.cs new file mode 100644 index 00000000..c5279f04 --- /dev/null +++ b/src/Calculators.CashFlow.Tests/Scenarios/PensionVersusCapitalComparisonTests.cs @@ -0,0 +1,49 @@ +using System.Threading.Tasks; +using Application.Features.TaxScenarios; +using Domain.Enums; +using Domain.Models.Tax; +using Snapshooter.Xunit; +using Xunit; + +namespace Calculators.CashFlow.Tests.Scenarios; + +[Trait("Scenario Calculators", "Pension vs Capital")] +public class PensionVersusCapitalComparisonTests : IClassFixture> +{ + private readonly CashFlowFixture fixture; + + public PensionVersusCapitalComparisonTests(CashFlowFixture fixture) + { + this.fixture = fixture; + } + + [Fact(DisplayName = "City of Zurich")] + public async Task Calculate_Comparison_For_Zurich() + { + // given + var calculationYear = 2019; + var bfsMunicipalityId = 261; + decimal retirementPension = 50_000; + decimal retirementCapital = 500_000; + decimal yearConsumptionAmount = 78_800; + decimal netWealthReturn = 0.03m; + + TaxPerson person = new() + { + Name = "Unit Test Purchase", + CivilStatus = CivilStatus.Single, + ReligiousGroupType = ReligiousGroupType.Other, + PartnerReligiousGroupType = ReligiousGroupType.Other, + TaxableIncome = 28_800, + TaxableFederalIncome = 28_800, + TaxableWealth = 500000 + }; + + // when + var result = await fixture.Calculator.PensionVersusCapitalComparisonAsync( + calculationYear, bfsMunicipalityId, yearConsumptionAmount, retirementPension, retirementCapital, netWealthReturn, person); + + // then + Snapshot.Match(result, opt => opt.IgnoreFields("$..Id")); + } +} diff --git a/src/Calculators.CashFlow.Tests/Scenarios/PurchaseInsuranceYearsTests.cs b/src/Calculators.CashFlow.Tests/Scenarios/PurchaseInsuranceYearsTests.cs new file mode 100644 index 00000000..188bcdd9 --- /dev/null +++ b/src/Calculators.CashFlow.Tests/Scenarios/PurchaseInsuranceYearsTests.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Application.Features.TaxScenarios; +using Domain.Enums; +using Domain.Models.Scenarios; +using Domain.Models.Tax; +using Snapshooter.Xunit; +using Xunit; + +namespace Calculators.CashFlow.Tests.Scenarios; + +[Trait("Scenario Calculators", "Purchase Insurance Years")] +public class PurchaseInsuranceYearsTests : IClassFixture> +{ + private readonly CashFlowFixture fixture; + + public PurchaseInsuranceYearsTests(CashFlowFixture fixture) + { + this.fixture = fixture; + } + + [Fact(DisplayName = "Single Purchase With Withdrawal in 10 Years")] + public async Task Calculate_Single_Purchase_With_Withdrawal_In_10_Years() + { + // given + var calculationYear = 2022; + var bfsMunicipalityId = 261; + TaxPerson person = new() + { + Name = "Unit Test Purchase", + CivilStatus = CivilStatus.Married, + ReligiousGroupType = ReligiousGroupType.Other, + PartnerReligiousGroupType = ReligiousGroupType.Other, + TaxableIncome = 150_000, + TaxableFederalIncome = 100_000, + TaxableWealth = 500_000 + }; + + CapitalBenefitTransferInsScenarioModel scenarioModel = new() + { + NetReturnCapitalBenefits = 0.0M, + TransferIns = new List + { + new(10000, new DateTime(2022, 1, 1)), + }, + WithCapitalBenefitWithdrawal = true, + CapitalBenefitsBeforeWithdrawal = 800_000, + Withdrawals = new List + { + new(0.5M, new DateTime(2032, 12, 31)), + new(1M, new DateTime(2033, 12, 31)), + }, + }; + + // when + var result = await fixture.Calculator.CapitalBenefitTransferInsAsync( + calculationYear, bfsMunicipalityId, person, scenarioModel); + + // then + Snapshot.Match(result, opt => opt.IgnoreFields("$..Id")); + } +} diff --git a/src/Calculators.CashFlow.Tests/Scenarios/ThirdPillarVersusSelfInvestmentTests.cs b/src/Calculators.CashFlow.Tests/Scenarios/ThirdPillarVersusSelfInvestmentTests.cs new file mode 100644 index 00000000..68e5bb66 --- /dev/null +++ b/src/Calculators.CashFlow.Tests/Scenarios/ThirdPillarVersusSelfInvestmentTests.cs @@ -0,0 +1,55 @@ +using System.Threading.Tasks; +using Application.Features.TaxScenarios; +using Domain.Enums; +using Domain.Models.Scenarios; +using Domain.Models.Tax; +using Snapshooter.Xunit; +using Xunit; + +namespace Calculators.CashFlow.Tests.Scenarios; + +[Trait("Scenario Calculators", "3a vs Anlagekonto")] +public class ThirdPillarVersusSelfInvestmentTests : IClassFixture> +{ + private readonly CashFlowFixture fixture; + + public ThirdPillarVersusSelfInvestmentTests(CashFlowFixture fixture) + { + this.fixture = fixture; + } + + [Fact(DisplayName = "Excess Return on Self-Investment")] + public async Task Calculate_Excess_Return_On_SelfInvestment() + { + // given + var calculationYear = 2022; + var finalYear = calculationYear + 20; + var bfsMunicipalityId = 261; + TaxPerson person = new() + { + Name = "Unit Test Scenario", + CivilStatus = CivilStatus.Married, + ReligiousGroupType = ReligiousGroupType.Other, + PartnerReligiousGroupType = ReligiousGroupType.Other, + TaxableIncome = 150_000, + TaxableFederalIncome = 100_000, + TaxableWealth = 500_000 + }; + + ThirdPillarVersusSelfInvestmentScenarioModel scenarioModel = new() + { + FinalYear = finalYear, + InvestmentNetGrowthRate = 0.02M, + InvestmentNetIncomeYield = 0.01M, + ThirdPillarNetGrowthRate = 0.00M, + InvestmentAmount = 7056, + }; + + // when + var result = + await fixture.Calculator.ThirdPillarVersusSelfInvestmentAsync(calculationYear, bfsMunicipalityId, person, scenarioModel); + + // then + Snapshot.Match(result, opt => opt.IgnoreFields("$..Id")); + } +} diff --git a/src/Calculators.CashFlow.Tests/Scenarios/__snapshots__/PensionVersusCapitalComparisonTests.Calculate_Comparison_For_Zurich.snap b/src/Calculators.CashFlow.Tests/Scenarios/__snapshots__/PensionVersusCapitalComparisonTests.Calculate_Comparison_For_Zurich.snap new file mode 100644 index 00000000..e00ec595 --- /dev/null +++ b/src/Calculators.CashFlow.Tests/Scenarios/__snapshots__/PensionVersusCapitalComparisonTests.Calculate_Comparison_For_Zurich.snap @@ -0,0 +1,1099 @@ +[ + { + "State": "IsRight", + "Right": { + "StartingYear": 2019, + "NumberOfPeriods": 26, + "DeltaSeries": [ + { + "Year": 2019, + "Amount": 413573.3814000, + "AccountType": "Exogenous" + }, + { + "Year": 2020, + "Amount": 382680.262842000000, + "AccountType": "Exogenous" + }, + { + "Year": 2021, + "Amount": 350958.90072726000000000, + "AccountType": "Exogenous" + }, + { + "Year": 2022, + "Amount": 318389.9227490778000000000000, + "AccountType": "Exogenous" + }, + { + "Year": 2023, + "Amount": 284948.9954315501340000000000, + "AccountType": "Exogenous" + }, + { + "Year": 2024, + "Amount": 250615.43529449663802000000000, + "AccountType": "Exogenous" + }, + { + "Year": 2025, + "Amount": 215365.74835333153716060000000, + "AccountType": "Exogenous" + }, + { + "Year": 2026, + "Amount": 179170.26080393148327541800000, + "AccountType": "Exogenous" + }, + { + "Year": 2027, + "Amount": 141989.64862804942777368054000, + "AccountType": "Exogenous" + }, + { + "Year": 2028, + "Amount": 103779.02808689091060689095620, + "AccountType": "Exogenous" + }, + { + "Year": 2029, + "Amount": 64507.49892949763792509768489, + "AccountType": "Exogenous" + }, + { + "Year": 2030, + "Amount": 24145.42389738256706285061544, + "AccountType": "Exogenous" + }, + { + "Year": 2031, + "Amount": -17337.72338569595592526386609, + "AccountType": "Exogenous" + }, + { + "Year": 2032, + "Amount": -59971.19508726683460302178207, + "AccountType": "Exogenous" + }, + { + "Year": 2033, + "Amount": -103785.12093988483964111243553, + "AccountType": "Exogenous" + }, + { + "Year": 2034, + "Amount": -148814.91456808138483034580859, + "AccountType": "Exogenous" + }, + { + "Year": 2035, + "Amount": -195097.05200512382637525618285, + "AccountType": "Exogenous" + }, + { + "Year": 2036, + "Amount": -242660.34356527754116651386834, + "AccountType": "Exogenous" + }, + { + "Year": 2037, + "Amount": -291565.12387223586740150928439, + "AccountType": "Exogenous" + }, + { + "Year": 2038, + "Amount": -341876.82258840294342355456292, + "AccountType": "Exogenous" + }, + { + "Year": 2039, + "Amount": -393634.36226605503172626119981, + "AccountType": "Exogenous" + }, + { + "Year": 2040, + "Amount": -446881.11813403668267804903580, + "AccountType": "Exogenous" + }, + { + "Year": 2041, + "Amount": -501658.48167805778315839050687, + "AccountType": "Exogenous" + }, + { + "Year": 2042, + "Amount": -558040.84112839951665314222208, + "AccountType": "Exogenous" + }, + { + "Year": 2043, + "Amount": -616101.53136225150215273648874, + "AccountType": "Exogenous" + }, + { + "Year": 2044, + "Amount": -633624.26230311904721731858341, + "AccountType": "Exogenous" + } + ], + "BenchmarkSeries": [ + { + "Year": 2019, + "Amount": 503185.2050, + "AccountType": "Wealth" + }, + { + "Year": 2020, + "Amount": 506459.39615000, + "AccountType": "Wealth" + }, + { + "Year": 2021, + "Amount": 509825.243034500000, + "AccountType": "Wealth" + }, + { + "Year": 2022, + "Amount": 513283.3053255350000000, + "AccountType": "Wealth" + }, + { + "Year": 2023, + "Amount": 516838.53948530105000000000, + "AccountType": "Wealth" + }, + { + "Year": 2024, + "Amount": 520491.67066986008150000000000, + "AccountType": "Wealth" + }, + { + "Year": 2025, + "Amount": 524245.63578995588394500000000, + "AccountType": "Wealth" + }, + { + "Year": 2026, + "Amount": 528105.64986365456046335000000, + "AccountType": "Wealth" + }, + { + "Year": 2027, + "Amount": 532072.70435956419727725050000, + "AccountType": "Wealth" + }, + { + "Year": 2028, + "Amount": 536147.82049035112319556801500, + "AccountType": "Wealth" + }, + { + "Year": 2029, + "Amount": 540336.43010506165689143505545, + "AccountType": "Wealth" + }, + { + "Year": 2030, + "Amount": 544641.93800821350659817810711, + "AccountType": "Wealth" + }, + { + "Year": 2031, + "Amount": 549067.85114845991179612345032, + "AccountType": "Wealth" + }, + { + "Year": 2032, + "Amount": 553615.59168291370915000715383, + "AccountType": "Wealth" + }, + { + "Year": 2033, + "Amount": 558288.81443340112042450736844, + "AccountType": "Wealth" + }, + { + "Year": 2034, + "Amount": 563091.28386640315403724258949, + "AccountType": "Wealth" + }, + { + "Year": 2035, + "Amount": 568029.06738239524865835986718, + "AccountType": "Wealth" + }, + { + "Year": 2036, + "Amount": 573101.84440386710611811066320, + "AccountType": "Wealth" + }, + { + "Year": 2037, + "Amount": 578315.85473598311930165398310, + "AccountType": "Wealth" + }, + { + "Year": 2038, + "Amount": 583675.33537806261288070360259, + "AccountType": "Wealth" + }, + { + "Year": 2039, + "Amount": 589182.46043940449126712471067, + "AccountType": "Wealth" + }, + { + "Year": 2040, + "Amount": 594843.84925258662600513845199, + "AccountType": "Wealth" + }, + { + "Year": 2041, + "Amount": 600661.93973016422478529260555, + "AccountType": "Wealth" + }, + { + "Year": 2042, + "Amount": 606641.43292206915152885138372, + "AccountType": "Wealth" + }, + { + "Year": 2043, + "Amount": 612787.17090973122607471692523, + "AccountType": "Wealth" + }, + { + "Year": 2044, + "Amount": 630186.47103702316285695843299, + "AccountType": "Wealth" + } + ], + "ScenarioSeries": [ + { + "Year": 2019, + "Amount": 916758.5864000, + "AccountType": "Wealth" + }, + { + "Year": 2020, + "Amount": 889139.658992000000, + "AccountType": "Wealth" + }, + { + "Year": 2021, + "Amount": 860784.14376176000000000, + "AccountType": "Wealth" + }, + { + "Year": 2022, + "Amount": 831673.2280746128000000000000, + "AccountType": "Wealth" + }, + { + "Year": 2023, + "Amount": 801787.5349168511840000000000, + "AccountType": "Wealth" + }, + { + "Year": 2024, + "Amount": 771107.10596435671952000000000, + "AccountType": "Wealth" + }, + { + "Year": 2025, + "Amount": 739611.38414328742110560000000, + "AccountType": "Wealth" + }, + { + "Year": 2026, + "Amount": 707275.91066758604373876800000, + "AccountType": "Wealth" + }, + { + "Year": 2027, + "Amount": 674062.35298761362505093104000, + "AccountType": "Wealth" + }, + { + "Year": 2028, + "Amount": 639926.84857724203380245897120, + "AccountType": "Wealth" + }, + { + "Year": 2029, + "Amount": 604843.92903455929481653274034, + "AccountType": "Wealth" + }, + { + "Year": 2030, + "Amount": 568787.36190559607366102872255, + "AccountType": "Wealth" + }, + { + "Year": 2031, + "Amount": 531730.12776276395587085958423, + "AccountType": "Wealth" + }, + { + "Year": 2032, + "Amount": 493644.39659564687454698537176, + "AccountType": "Wealth" + }, + { + "Year": 2033, + "Amount": 454503.69349351628078339493291, + "AccountType": "Wealth" + }, + { + "Year": 2034, + "Amount": 414276.36929832176920689678090, + "AccountType": "Wealth" + }, + { + "Year": 2035, + "Amount": 372932.01537727142228310368433, + "AccountType": "Wealth" + }, + { + "Year": 2036, + "Amount": 330441.50083858956495159679486, + "AccountType": "Wealth" + }, + { + "Year": 2037, + "Amount": 286750.73086374725190014469871, + "AccountType": "Wealth" + }, + { + "Year": 2038, + "Amount": 241798.51278965966945714903967, + "AccountType": "Wealth" + }, + { + "Year": 2039, + "Amount": 195548.09817334945954086351086, + "AccountType": "Wealth" + }, + { + "Year": 2040, + "Amount": 147962.73111854994332708941619, + "AccountType": "Wealth" + }, + { + "Year": 2041, + "Amount": 99003.45805210644162690209868, + "AccountType": "Wealth" + }, + { + "Year": 2042, + "Amount": 48600.591793669634875709161640, + "AccountType": "Wealth" + }, + { + "Year": 2043, + "Amount": -3314.3604525202760780195635108, + "AccountType": "Wealth" + }, + { + "Year": 2044, + "Amount": -3437.7912660958843603601504161, + "AccountType": "Wealth" + } + ], + "BenchmarkTransactions": { + "ExogenousAccount": null, + "IncomeAccount": null, + "WealthAccount": { + "Id": "22678fef-7787-4784-b966-86b201958dde", + "Name": "Wealth Account", + "Transactions": [ + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2019-01-01T00:00:00", + "Amount": 500000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2020-01-01T00:00:00", + "Amount": 15000.00 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2020-01-01T00:00:00", + "Amount": -11814.7950 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 15095.556150 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -11821.36500000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 15193.7818845000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -11827.935000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 15294.75729103500000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -11836.6950000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 15398.499159766050000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -11843.26500000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 15505.1561845590315000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -11852.025000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 15614.750120095802445000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -11860.785000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 15727.369073698676518350000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -11867.355000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 15843.169495909636813900500000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -11876.115000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 15962.181130786925918317515000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -11887.065000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 16084.434614710533695867040450 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -11895.825000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 16210.092903151849706743051664 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -11904.585000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 16339.258140246405197945343213 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -11913.345000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": 16472.035534453797353883703510 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": -11924.295000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2034-01-01T00:00:00", + "Amount": 16608.467750487411274500214615 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2034-01-01T00:00:00", + "Amount": -11935.245000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2035-01-01T00:00:00", + "Amount": 16748.664433002033612735221053 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2035-01-01T00:00:00", + "Amount": -11946.195000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2036-01-01T00:00:00", + "Amount": 16892.738515992094621117277685 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2036-01-01T00:00:00", + "Amount": -11954.955000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2037-01-01T00:00:00", + "Amount": 17040.872021471857459750796015 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2037-01-01T00:00:00", + "Amount": -11968.095000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2038-01-01T00:00:00", + "Amount": 17193.055332116013183543319896 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2038-01-01T00:00:00", + "Amount": -11979.045000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2039-01-01T00:00:00", + "Amount": 17349.475642079493579049619493 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2039-01-01T00:00:00", + "Amount": -11989.995000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2040-01-01T00:00:00", + "Amount": 17510.260061341878386421108078 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2040-01-01T00:00:00", + "Amount": -12003.135000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2041-01-01T00:00:00", + "Amount": 17675.473813182134738013741320 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2041-01-01T00:00:00", + "Amount": -12014.085000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2042-01-01T00:00:00", + "Amount": 17845.315477577598780154153560 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2042-01-01T00:00:00", + "Amount": -12027.225000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2043-01-01T00:00:00", + "Amount": 18019.858191904926743558778166 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2043-01-01T00:00:00", + "Amount": -12040.365000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2044-01-01T00:00:00", + "Amount": 18199.242987662074545865541512 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2044-01-01T00:00:00", + "Amount": -12053.505000000000000000000000 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2045-01-01T00:00:00", + "Amount": 18383.615127291936782241507757 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2045-01-01T00:00:00", + "Amount": -984.3150000000000000000000000 + } + ] + }, + "InvestmentAccount": null, + "OccupationalPensionAccount": null, + "ThirdPillarAccount": null, + "TaxAccount": null + }, + "ScenarioTransactions": { + "ExogenousAccount": null, + "IncomeAccount": null, + "WealthAccount": { + "Id": "7476bed3-4f04-48e3-8d20-2d9fa5a1679d", + "Name": "Wealth Account", + "Transactions": [ + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2019-01-01T00:00:00", + "Amount": 500000.0 + }, + { + "Description": "Simple cash-flow: inflow from Occupational Pension Account", + "ValutaDate": "2019-01-02T00:00:00", + "Amount": 500000.0 + }, + { + "Description": "Tax payment: outflow to Tax Account", + "ValutaDate": "2019-01-02T00:00:00", + "Amount": -56337.62 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2019-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2020-01-01T00:00:00", + "Amount": 26809.8714 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2020-01-01T00:00:00", + "Amount": -3713.6650000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2020-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 26002.757592000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -3621.685000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2021-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 25174.18976976000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -3529.70500000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2022-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 24323.5243128528000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -3434.4400000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2023-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 23450.196842238384000000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -3335.8900000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2024-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 22553.626047505535520000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -3234.0550000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2025-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 21633.213178930701585600000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -3128.9350000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2026-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 20688.341524298622633168000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -3023.8150000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2027-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 19718.277320027581312163040000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -2931.8350000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2028-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 18721.870589628408751527931200 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -2857.3750000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2029-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 17697.805457317261014073769136 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -2780.7250000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2030-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 16645.317871036778844495982210 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -2701.8850000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2031-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 15563.620857167882209830861676 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -2620.8550000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2032-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": 14451.903832882918676125787527 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": -2537.6350000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2033-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2034-01-01T00:00:00", + "Amount": 13309.331897869406236409561153 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2034-01-01T00:00:00", + "Amount": -2450.0350000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2034-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2035-01-01T00:00:00", + "Amount": 12135.110804805488423501847987 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2035-01-01T00:00:00", + "Amount": -2362.4350000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2035-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2036-01-01T00:00:00", + "Amount": 10928.291078949653076206903427 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2036-01-01T00:00:00", + "Amount": -2272.6450000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2036-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2037-01-01T00:00:00", + "Amount": 9687.960461318142668493110530 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2037-01-01T00:00:00", + "Amount": -2178.4750000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2037-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2038-01-01T00:00:00", + "Amount": 8413.245025157686948547903846 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2038-01-01T00:00:00", + "Amount": -2104.0150000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2038-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2039-01-01T00:00:00", + "Amount": 7102.5219259124175570043409613 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2039-01-01T00:00:00", + "Amount": -2054.7400000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2039-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2040-01-01T00:00:00", + "Amount": 5753.9553836897900837144711901 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2040-01-01T00:00:00", + "Amount": -2004.3700000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2040-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2041-01-01T00:00:00", + "Amount": 4366.4429452004837862259053258 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2041-01-01T00:00:00", + "Amount": -1951.8100000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2041-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2042-01-01T00:00:00", + "Amount": 2938.8819335564982998126824857 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2042-01-01T00:00:00", + "Amount": -1898.1550000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2042-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2043-01-01T00:00:00", + "Amount": 1470.1037415631932488070629604 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2043-01-01T00:00:00", + "Amount": -1872.97 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2043-12-31T00:00:00", + "Amount": -50000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2044-01-01T00:00:00", + "Amount": -41.98224618991095372872515080 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2044-01-01T00:00:00", + "Amount": -1872.97 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2045-01-01T00:00:00", + "Amount": -99.43081357560828234058690532 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2045-01-01T00:00:00", + "Amount": -24.00 + } + ] + }, + "InvestmentAccount": null, + "OccupationalPensionAccount": null, + "ThirdPillarAccount": null, + "TaxAccount": null + } + }, + "Left": null + } +] diff --git a/src/Calculators.CashFlow.Tests/__snapshots__/ScenarioCapitalBenefitTransferInsTests.Calculate_Single_TransferIn_With_Withdrawal_In_10_Years.snap b/src/Calculators.CashFlow.Tests/Scenarios/__snapshots__/PurchaseInsuranceYearsTests.Calculate_Single_Purchase_With_Withdrawal_In_10_Years.snap similarity index 98% rename from src/Calculators.CashFlow.Tests/__snapshots__/ScenarioCapitalBenefitTransferInsTests.Calculate_Single_TransferIn_With_Withdrawal_In_10_Years.snap rename to src/Calculators.CashFlow.Tests/Scenarios/__snapshots__/PurchaseInsuranceYearsTests.Calculate_Single_Purchase_With_Withdrawal_In_10_Years.snap index f87a87dd..55d6c41d 100644 --- a/src/Calculators.CashFlow.Tests/__snapshots__/ScenarioCapitalBenefitTransferInsTests.Calculate_Single_TransferIn_With_Withdrawal_In_10_Years.snap +++ b/src/Calculators.CashFlow.Tests/Scenarios/__snapshots__/PurchaseInsuranceYearsTests.Calculate_Single_Purchase_With_Withdrawal_In_10_Years.snap @@ -309,7 +309,9 @@ "Amount": 0.0, "AccountType": "OccupationalPension" } - ] + ], + "BenchmarkTransactions": null, + "ScenarioTransactions": null }, "Left": null } diff --git a/src/Calculators.CashFlow.Tests/Scenarios/__snapshots__/ThirdPillarVersusSelfInvestmentTests.Calculate_Excess_Return_On_SelfInvestment.snap b/src/Calculators.CashFlow.Tests/Scenarios/__snapshots__/ThirdPillarVersusSelfInvestmentTests.Calculate_Excess_Return_On_SelfInvestment.snap new file mode 100644 index 00000000..8dd21731 --- /dev/null +++ b/src/Calculators.CashFlow.Tests/Scenarios/__snapshots__/ThirdPillarVersusSelfInvestmentTests.Calculate_Excess_Return_On_SelfInvestment.snap @@ -0,0 +1,648 @@ +[ + { + "State": "IsRight", + "Right": { + "StartingYear": 2022, + "NumberOfPeriods": 21, + "DeltaSeries": [ + { + "Year": 2022, + "Amount": 2120.4400, + "AccountType": "Exogenous" + }, + { + "Year": 2023, + "Amount": 4051.326000, + "AccountType": "Exogenous" + }, + { + "Year": 2024, + "Amount": 5814.38180800, + "AccountType": "Exogenous" + }, + { + "Year": 2025, + "Amount": 7398.2052808000, + "AccountType": "Exogenous" + }, + { + "Year": 2026, + "Amount": 8758.254118155200, + "AccountType": "Exogenous" + }, + { + "Year": 2027, + "Amount": 9914.96119620968000, + "AccountType": "Exogenous" + }, + { + "Year": 2028, + "Amount": 10855.5894256959908800, + "AccountType": "Exogenous" + }, + { + "Year": 2029, + "Amount": 11566.926119938891496000, + "AccountType": "Exogenous" + }, + { + "Year": 2030, + "Amount": 12035.26685723851954827200, + "AccountType": "Exogenous" + }, + { + "Year": 2031, + "Amount": 12246.3988217311656682600000, + "AccountType": "Exogenous" + }, + { + "Year": 2032, + "Amount": 12185.58360633410098251843680, + "AccountType": "Exogenous" + }, + { + "Year": 2033, + "Amount": 11870.24946087414436308883944, + "AccountType": "Exogenous" + }, + { + "Year": 2034, + "Amount": 11251.84296797738945209825115, + "AccountType": "Exogenous" + }, + { + "Year": 2035, + "Amount": 10346.65012875927230894028014, + "AccountType": "Exogenous" + }, + { + "Year": 2036, + "Amount": 9104.93683979946287495315163, + "AccountType": "Exogenous" + }, + { + "Year": 2037, + "Amount": 7541.76874231440740588130253, + "AccountType": "Exogenous" + }, + { + "Year": 2038, + "Amount": 5638.86042385121948563088908, + "AccountType": "Exogenous" + }, + { + "Year": 2039, + "Amount": 3377.26395221948352492442617, + "AccountType": "Exogenous" + }, + { + "Year": 2040, + "Amount": 737.34672075185003449126159, + "AccountType": "Exogenous" + }, + { + "Year": 2041, + "Amount": -2268.52141666049682057851588, + "AccountType": "Exogenous" + }, + { + "Year": 2042, + "Amount": -11248.01174297591212842247698, + "AccountType": "Exogenous" + } + ], + "BenchmarkSeries": [ + { + "Year": 2022, + "Amount": -33252.5400, + "AccountType": "Wealth" + }, + { + "Year": 2022, + "Amount": 7267.68, + "AccountType": "Investment" + }, + { + "Year": 2022, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2023, + "Amount": -66678.910000, + "AccountType": "Wealth" + }, + { + "Year": 2023, + "Amount": 14898.7440, + "AccountType": "Investment" + }, + { + "Year": 2023, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2024, + "Amount": -100314.64240000, + "AccountType": "Wealth" + }, + { + "Year": 2024, + "Amount": 22907.000592, + "AccountType": "Investment" + }, + { + "Year": 2024, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2025, + "Amount": -134162.6160480000, + "AccountType": "Wealth" + }, + { + "Year": 2025, + "Amount": 31306.73076720, + "AccountType": "Investment" + }, + { + "Year": 2025, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2026, + "Amount": -168193.057368960000, + "AccountType": "Wealth" + }, + { + "Year": 2026, + "Amount": 40112.7032508048, + "AccountType": "Investment" + }, + { + "Year": 2026, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2027, + "Amount": -202441.67151633920000, + "AccountType": "Wealth" + }, + { + "Year": 2027, + "Amount": 49340.190320129520, + "AccountType": "Investment" + }, + { + "Year": 2027, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2028, + "Amount": -236911.5135466659840000, + "AccountType": "Wealth" + }, + { + "Year": 2028, + "Amount": 59004.98412096999312, + "AccountType": "Investment" + }, + { + "Year": 2028, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2029, + "Amount": -271605.699617599303680000, + "AccountType": "Wealth" + }, + { + "Year": 2029, + "Amount": 69123.4134976604121840, + "AccountType": "Investment" + }, + { + "Year": 2029, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2030, + "Amount": -306527.40820995128975360000, + "AccountType": "Wealth" + }, + { + "Year": 2030, + "Amount": 79712.361352712770205328, + "AccountType": "Investment" + }, + { + "Year": 2030, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2031, + "Amount": -341679.8813741503155486720000, + "AccountType": "Wealth" + }, + { + "Year": 2031, + "Amount": 90789.28255241914988041200, + "AccountType": "Investment" + }, + { + "Year": 2031, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2032, + "Amount": -377066.42600163332185964544000, + "AccountType": "Wealth" + }, + { + "Year": 2032, + "Amount": 102372.2223952992208771270032, + "AccountType": "Investment" + }, + { + "Year": 2032, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2033, + "Amount": -412723.12512166598829683834880, + "AccountType": "Wealth" + }, + { + "Year": 2033, + "Amount": 114479.83566079184393374950936, + "AccountType": "Investment" + }, + { + "Year": 2033, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2034, + "Amount": -448620.70922409930806277511578, + "AccountType": "Wealth" + }, + { + "Year": 2034, + "Amount": 127131.40625612191861067686463, + "AccountType": "Investment" + }, + { + "Year": 2034, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2035, + "Amount": -484795.39760858129422403061810, + "AccountType": "Wealth" + }, + { + "Year": 2035, + "Amount": 140346.86747982202191509033796, + "AccountType": "Investment" + }, + { + "Year": 2035, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2036, + "Amount": -521218.05976075292010851123046, + "AccountType": "Wealth" + }, + { + "Year": 2036, + "Amount": 154146.82292095345723355807883, + "AccountType": "Investment" + }, + { + "Year": 2036, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2037, + "Amount": -557925.05675596797851068145507, + "AccountType": "Wealth" + }, + { + "Year": 2037, + "Amount": 168552.56801365357110480015254, + "AccountType": "Investment" + }, + { + "Year": 2037, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2038, + "Amount": -594920.11269108733808089508417, + "AccountType": "Wealth" + }, + { + "Year": 2038, + "Amount": 183586.11226723611859526419509, + "AccountType": "Investment" + }, + { + "Year": 2038, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2039, + "Amount": -632207.02614490908484251298585, + "AccountType": "Wealth" + }, + { + "Year": 2039, + "Amount": 199270.20219268960131758855968, + "AccountType": "Investment" + }, + { + "Year": 2039, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2040, + "Amount": -669789.67166780726653936324557, + "AccountType": "Wealth" + }, + { + "Year": 2040, + "Amount": 215628.34494705541650487198398, + "AccountType": "Investment" + }, + { + "Year": 2040, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2041, + "Amount": -707704.71130116341187015051048, + "AccountType": "Wealth" + }, + { + "Year": 2041, + "Amount": 232684.83271782390869072902636, + "AccountType": "Investment" + }, + { + "Year": 2041, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2042, + "Amount": -718237.55612718668010755352069, + "AccountType": "Wealth" + }, + { + "Year": 2042, + "Amount": 250464.76787016259223597599767, + "AccountType": "Investment" + }, + { + "Year": 2042, + "Amount": 0.0, + "AccountType": "ThirdPillar" + } + ], + "ScenarioSeries": [ + { + "Year": 2022, + "Amount": -30920.42, + "AccountType": "Wealth" + }, + { + "Year": 2022, + "Amount": 7056.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2023, + "Amount": -61840.84, + "AccountType": "Wealth" + }, + { + "Year": 2023, + "Amount": 14112.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2024, + "Amount": -92761.26, + "AccountType": "Wealth" + }, + { + "Year": 2024, + "Amount": 21168.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2025, + "Amount": -123681.68, + "AccountType": "Wealth" + }, + { + "Year": 2025, + "Amount": 28224.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2026, + "Amount": -154602.10, + "AccountType": "Wealth" + }, + { + "Year": 2026, + "Amount": 35280.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2027, + "Amount": -185522.52, + "AccountType": "Wealth" + }, + { + "Year": 2027, + "Amount": 42336.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2028, + "Amount": -216442.94, + "AccountType": "Wealth" + }, + { + "Year": 2028, + "Amount": 49392.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2029, + "Amount": -247363.36, + "AccountType": "Wealth" + }, + { + "Year": 2029, + "Amount": 56448.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2030, + "Amount": -278283.78, + "AccountType": "Wealth" + }, + { + "Year": 2030, + "Amount": 63504.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2031, + "Amount": -309204.20, + "AccountType": "Wealth" + }, + { + "Year": 2031, + "Amount": 70560.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2032, + "Amount": -340124.62, + "AccountType": "Wealth" + }, + { + "Year": 2032, + "Amount": 77616.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2033, + "Amount": -371045.04, + "AccountType": "Wealth" + }, + { + "Year": 2033, + "Amount": 84672.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2034, + "Amount": -401965.46, + "AccountType": "Wealth" + }, + { + "Year": 2034, + "Amount": 91728.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2035, + "Amount": -432885.88, + "AccountType": "Wealth" + }, + { + "Year": 2035, + "Amount": 98784.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2036, + "Amount": -463806.30, + "AccountType": "Wealth" + }, + { + "Year": 2036, + "Amount": 105840.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2037, + "Amount": -494726.72, + "AccountType": "Wealth" + }, + { + "Year": 2037, + "Amount": 112896.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2038, + "Amount": -525647.14, + "AccountType": "Wealth" + }, + { + "Year": 2038, + "Amount": 119952.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2039, + "Amount": -556567.56, + "AccountType": "Wealth" + }, + { + "Year": 2039, + "Amount": 127008.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2040, + "Amount": -587487.98, + "AccountType": "Wealth" + }, + { + "Year": 2040, + "Amount": 134064.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2041, + "Amount": -618408.40, + "AccountType": "Wealth" + }, + { + "Year": 2041, + "Amount": 141120.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2042, + "Amount": -479020.800, + "AccountType": "Wealth" + }, + { + "Year": 2042, + "Amount": 0.0, + "AccountType": "ThirdPillar" + } + ], + "BenchmarkTransactions": null, + "ScenarioTransactions": null + }, + "Left": null + } +] diff --git a/src/Calculators.CashFlow.Tests/__snapshots__/InvestmentPortfolioTests.Calculate_Growth_Of_Investment_Portfolio.snap b/src/Calculators.CashFlow.Tests/__snapshots__/InvestmentPortfolioTests.Calculate_Growth_Of_Investment_Portfolio.snap new file mode 100644 index 00000000..69415d47 --- /dev/null +++ b/src/Calculators.CashFlow.Tests/__snapshots__/InvestmentPortfolioTests.Calculate_Growth_Of_Investment_Portfolio.snap @@ -0,0 +1,25 @@ +[ + { + "Header": { + "Id": "test", + "Name": "Test" + }, + "DateOfProcess": "2021-01-01T00:00:00", + "InitialAmount": 0.0, + "RecurringInvestment": { + "Amount": 6723.0, + "Frequency": "Yearly" + }, + "NetGrowthRate": 0.02, + "InvestmentPeriod": { + "Year": 2021, + "NumberOfPeriods": 10 + }, + "Flow": { + "Source": "Wealth", + "Target": "Investment" + }, + "IsTaxable": false, + "TaxType": "Undefined" + } +] diff --git a/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Full_MultiPeriod_Simulation.snap b/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Full_MultiPeriod_Simulation.snap index 172121d1..fa980e61 100644 --- a/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Full_MultiPeriod_Simulation.snap +++ b/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Full_MultiPeriod_Simulation.snap @@ -7,7 +7,7 @@ "Accounts": [ { "Year": 2021, - "Amount": -1015233.2250, + "Amount": -1024935.10, "AccountType": "Exogenous" }, { @@ -20,6 +20,11 @@ "Amount": 498350.2250, "AccountType": "Wealth" }, + { + "Year": 2021, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2021, "Amount": 510000.0, @@ -30,9 +35,14 @@ "Amount": 6883.0, "AccountType": "ThirdPillar" }, + { + "Year": 2021, + "Amount": 9701.8750, + "AccountType": "Tax" + }, { "Year": 2022, - "Amount": -1030577.530000, + "Amount": -1050170.2000, "AccountType": "Exogenous" }, { @@ -45,6 +55,11 @@ "Amount": 496811.530000, "AccountType": "Wealth" }, + { + "Year": 2022, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2022, "Amount": 520000.0, @@ -55,9 +70,14 @@ "Amount": 13766.0, "AccountType": "ThirdPillar" }, + { + "Year": 2022, + "Amount": 19592.670000, + "AccountType": "Tax" + }, { "Year": 2023, - "Amount": -1046033.72500000, + "Amount": -1075708.300000, "AccountType": "Exogenous" }, { @@ -70,6 +90,11 @@ "Amount": 495384.72500000, "AccountType": "Wealth" }, + { + "Year": 2023, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2023, "Amount": 530000.0, @@ -80,9 +105,14 @@ "Amount": 20649.0, "AccountType": "ThirdPillar" }, + { + "Year": 2023, + "Amount": 29674.57500000, + "AccountType": "Tax" + }, { "Year": 2024, - "Amount": -1061604.8400000000, + "Amount": -1101552.43000000, "AccountType": "Exogenous" }, { @@ -95,6 +125,11 @@ "Amount": 494072.8400000000, "AccountType": "Wealth" }, + { + "Year": 2024, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2024, "Amount": 540000.0, @@ -105,9 +140,14 @@ "Amount": 27532.0, "AccountType": "ThirdPillar" }, + { + "Year": 2024, + "Amount": 39947.5900000000, + "AccountType": "Tax" + }, { "Year": 2025, - "Amount": -1077293.935300000000, + "Amount": -1127705.6503000000, "AccountType": "Exogenous" }, { @@ -120,6 +160,11 @@ "Amount": 492878.935300000000, "AccountType": "Wealth" }, + { + "Year": 2025, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2025, "Amount": 550000.0, @@ -130,9 +175,14 @@ "Amount": 34415.0, "AccountType": "ThirdPillar" }, + { + "Year": 2025, + "Amount": 50411.715000000000, + "AccountType": "Tax" + }, { "Year": 2026, - "Amount": -1093910.71180300000000, + "Amount": -1157171.051803000000, "AccountType": "Exogenous" }, { @@ -145,6 +195,11 @@ "Amount": 502612.71180300000000, "AccountType": "Wealth" }, + { + "Year": 2026, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2026, "Amount": 550000.0, @@ -155,9 +210,14 @@ "Amount": 41298.0, "AccountType": "ThirdPillar" }, + { + "Year": 2026, + "Amount": 63260.34000000000000, + "AccountType": "Tax" + }, { "Year": 2027, - "Amount": -1110595.6913210300000000, + "Amount": -1186951.75632103000000, "AccountType": "Exogenous" }, { @@ -170,6 +230,11 @@ "Amount": 512414.6913210300000000, "AccountType": "Wealth" }, + { + "Year": 2027, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2027, "Amount": 550000.0, @@ -180,9 +245,14 @@ "Amount": 48181.0, "AccountType": "ThirdPillar" }, + { + "Year": 2027, + "Amount": 76356.0650000000000000, + "AccountType": "Tax" + }, { "Year": 2028, - "Amount": -1127329.506884240300000000, + "Amount": -1217050.9168842403000000, "AccountType": "Exogenous" }, { @@ -195,6 +265,11 @@ "Amount": 522265.506884240300000000, "AccountType": "Wealth" }, + { + "Year": 2028, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2028, "Amount": 550000.0, @@ -205,9 +280,14 @@ "Amount": 55064.0, "AccountType": "ThirdPillar" }, + { + "Year": 2028, + "Amount": 89721.410000000000000000, + "AccountType": "Tax" + }, { "Year": 2029, - "Amount": -1141033.8474932284169978980000, + "Amount": -1245471.718053082703000000, "AccountType": "Exogenous" }, { @@ -220,6 +300,11 @@ "Amount": 529086.84749322841699789800000, "AccountType": "Wealth" }, + { + "Year": 2029, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2029, "Amount": 550000.0, @@ -230,9 +315,14 @@ "Amount": 61947.0, "AccountType": "ThirdPillar" }, + { + "Year": 2029, + "Amount": 104437.87055985428600210200000, + "AccountType": "Tax" + }, { "Year": 2030, - "Amount": -1156820.0370712755547839061983, + "Amount": -1276217.37623361353003000000, "AccountType": "Exogenous" }, { @@ -245,6 +335,11 @@ "Amount": 537990.03707127555478390619827, "AccountType": "Wealth" }, + { + "Year": 2030, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2030, "Amount": 550000.0, @@ -255,9 +350,14 @@ "Amount": 68830.0, "AccountType": "ThirdPillar" }, + { + "Year": 2030, + "Amount": 119397.33916233797524609380173, + "AccountType": "Tax" + }, { "Year": 2031, - "Amount": -1173174.0048749490675404570342, + "Amount": -1309356.0399959496653303000000, "AccountType": "Exogenous" }, { @@ -270,6 +370,11 @@ "Amount": 554344.00487494906754045703418, "AccountType": "Wealth" }, + { + "Year": 2031, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2031, "Amount": 550000.0, @@ -280,9 +385,14 @@ "Amount": 68830.0, "AccountType": "ThirdPillar" }, + { + "Year": 2031, + "Amount": 136182.03512100059778984296582, + "AccountType": "Tax" + }, { "Year": 2032, - "Amount": -1189574.4618090945281843186622, + "Amount": -1342826.0903959091619836030000, "AccountType": "Exogenous" }, { @@ -295,6 +405,11 @@ "Amount": 570744.46180909452818431866222, "AccountType": "Wealth" }, + { + "Year": 2032, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2032, "Amount": 550000.0, @@ -305,9 +420,14 @@ "Amount": 68830.0, "AccountType": "ThirdPillar" }, + { + "Year": 2032, + "Amount": 153251.62858681463379928433778, + "AccountType": "Tax" + }, { "Year": 2033, - "Amount": -1205992.2307165969936737472250, + "Amount": -1376630.8412998682536034390300, "AccountType": "Exogenous" }, { @@ -320,6 +440,11 @@ "Amount": 587162.23071659699367374722501, "AccountType": "Wealth" }, + { + "Year": 2033, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2033, "Amount": 550000.0, @@ -330,9 +455,14 @@ "Amount": 68830.0, "AccountType": "ThirdPillar" }, + { + "Year": 2033, + "Amount": 170638.61058327125992969180499, + "AccountType": "Tax" + }, { "Year": 2034, - "Amount": -623553.86591471619959747157703, + "Amount": -806630.8412998682536034390300, "AccountType": "Exogenous" }, { @@ -345,6 +475,11 @@ "Amount": 623553.86591471619959747157704, "AccountType": "Wealth" }, + { + "Year": 2034, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2034, "Amount": 0.0, @@ -354,927 +489,935 @@ "Year": 2034, "Amount": 0.0, "AccountType": "ThirdPillar" + }, + { + "Year": 2034, + "Amount": 183076.97538515205400596745296, + "AccountType": "Tax" } ], - "ExogenousAccount": { - "Id": "b5d44b77-cbb6-4130-af4b-9d167cdf643a", - "Name": "Exogenous Account", - "Balance": -623553.86591471619959747157703, - "InterestRate": 0.0, - "Transactions": [ - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": -100000.0 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": 16883.0 - }, - { - "Description": "Simple cash-flow: outflow to Wealth Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": -500000.0 - }, - { - "Description": "Simple cash-flow: outflow to Occupational Pension Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": -500000.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -24935.10 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 9701.8750 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 83117.0 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -101000.00 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 16883.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": -25235.1000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": 9890.795000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": 84117.00 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": -102010.0000 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": 16883.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": -25538.100000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": 10081.90500000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": 85127.0000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": -103030.100000 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": 16883.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": -25844.13000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": 10273.0150000000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": 86147.100000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": -104060.40100000 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": 16883.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": -26153.2203000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": 10464.125000000000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": 87177.40100000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": -105101.0050100000 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": -29465.401503000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": 12848.62500000000000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": 98218.0050100000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": -106152.015060100000 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": -29780.70451803000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": 13095.7250000000000000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": 99269.015060100000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": -107213.53521070100000 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": -30099.1605632103000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": 13365.345000000000000000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": 100330.53521070100000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": -108285.6705628080100000 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Residence change costs: inflow from Wealth Account", - "ValutaDate": "2029-07-01T00:00:00", - "Amount": 2000.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": -30420.801168842403000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": 14716.460559854286002102000000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": 101402.6705628080100000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": -109368.527268436090100000 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": -30745.65818053082703000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": 14959.468602483689243991801732 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": 102485.527268436090100000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": -110462.21254112045100100000 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": -33138.6637623361353003000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": 16784.695958662622543749164085 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": 110462.21254112045100100000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": -111566.8346665316555110100000 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2033-01-01T00:00:00", - "Amount": -33470.050399959496653303000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2033-01-01T00:00:00", - "Amount": 17069.593465814036009441371958 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2033-01-01T00:00:00", - "Amount": 111566.8346665316555110100000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2033-01-01T00:00:00", - "Amount": -112682.50301319697206612010000 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2034-01-01T00:00:00", - "Amount": -33804.750903959091619836030000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2034-01-01T00:00:00", - "Amount": 17386.981996456626130407467213 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2034-01-01T00:00:00", - "Amount": 112682.50301319697206612010000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2034-01-01T00:00:00", - "Amount": -25000.00 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2034-04-01T00:00:00", - "Amount": -63400.0 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2034-04-01T00:00:00", - "Amount": 20000.0 - }, - { - "Description": "Tax payment: inflow from Wealth Account", - "ValutaDate": "2034-04-01T00:00:00", - "Amount": 137.60 - }, - { - "Description": "Simple cash-flow: inflow from Occupational Pension Account", - "ValutaDate": "2034-04-01T00:00:00", - "Amount": 550000.0 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2035-01-01T00:00:00", - "Amount": 12300.764801880794076275647971 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2035-01-01T00:00:00", - "Amount": 88400.00000000000000000000000 - } - ] - }, - "IncomeAccount": { - "Id": "8ecba42d-ddd2-4b5a-a8d7-019e68929293", - "Name": "Income Account", - "Balance": 0.00000000000000000000000, - "InterestRate": 0.0, - "Transactions": [ - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": 100000.0 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Simple cash-flow: outflow to Occupational Pension Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": -10000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -83117.0 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 101000.00 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Simple cash-flow: outflow to Occupational Pension Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -10000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": -84117.00 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": 102010.0000 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Simple cash-flow: outflow to Occupational Pension Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": -10000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": -85127.0000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": 103030.100000 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Simple cash-flow: outflow to Occupational Pension Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": -10000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": -86147.100000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": 104060.40100000 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Simple cash-flow: outflow to Occupational Pension Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": -10000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": -87177.40100000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": 105101.0050100000 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": -98218.0050100000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": 106152.015060100000 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": -99269.015060100000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": 107213.53521070100000 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": -100330.53521070100000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": 108285.6705628080100000 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": -101402.6705628080100000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": 109368.527268436090100000 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": -102485.527268436090100000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": 110462.21254112045100100000 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": -110462.21254112045100100000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": 111566.8346665316555110100000 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2033-01-01T00:00:00", - "Amount": -111566.8346665316555110100000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2033-01-01T00:00:00", - "Amount": 112682.50301319697206612010000 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2034-01-01T00:00:00", - "Amount": -112682.50301319697206612010000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2034-01-01T00:00:00", - "Amount": 25000.00 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2034-04-01T00:00:00", - "Amount": 63400.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2035-01-01T00:00:00", - "Amount": -88400.00000000000000000000000 - } - ] - }, - "WealthAccount": { - "Id": "f3a2a82c-bbf4-4818-9085-fb196c7bedfe", - "Name": "Wealth Account", - "Balance": 623553.86591471619959747157704, - "NetGrowthRate": 0.0, - "Transactions": [ - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": -16883.0 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": 500000.0 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 24935.10 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -9701.8750 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -16883.0 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": 25235.1000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": -9890.795000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": -16883.0 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": 25538.100000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": -10081.90500000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": -16883.0 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": 25844.13000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": -10273.0150000000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": -16883.0 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": 26153.2203000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": -10464.125000000000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": 29465.401503000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": -12848.62500000000000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": 29780.70451803000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": -13095.7250000000000000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": 30099.1605632103000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": -13365.345000000000000000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Residence change costs: outflow to Exogenous Account", - "ValutaDate": "2029-07-01T00:00:00", - "Amount": -2000.0 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": 30420.801168842403000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": -14716.460559854286002102000000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": 30745.65818053082703000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": -14959.468602483689243991801732 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": 33138.6637623361353003000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": -16784.695958662622543749164085 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2033-01-01T00:00:00", - "Amount": 33470.050399959496653303000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2033-01-01T00:00:00", - "Amount": -17069.593465814036009441371958 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2034-01-01T00:00:00", - "Amount": 33804.750903959091619836030000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2034-01-01T00:00:00", - "Amount": -17386.981996456626130407467213 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2034-04-01T00:00:00", - "Amount": -20000.0 - }, - { - "Description": "Simple cash-flow: inflow from Third Pillar Account", - "ValutaDate": "2034-04-01T00:00:00", - "Amount": 68830.0 - }, - { - "Description": "Tax payment: outflow to Exogenous Account", - "ValutaDate": "2034-04-01T00:00:00", - "Amount": -137.60 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2035-01-01T00:00:00", - "Amount": -12300.764801880794076275647971 - } - ] - }, - "OccupationalPensionAccount": { - "Id": "6511f6a7-2b81-4018-971f-fc6ba041257d", - "Name": "Occupational Pension Account", - "Balance": 0.0, - "NetGrowthRate": 0.0, - "Transactions": [ - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": 10000.0 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": 500000.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 10000.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": 10000.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": 10000.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": 10000.0 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2034-04-01T00:00:00", - "Amount": -550000.0 - } - ] - }, - "ThirdPillarAccount": { - "Id": "627b72cf-bea6-42af-a791-0a64738ef9f3", - "Name": "Third Pillar Account", - "Balance": 0.0, - "NetGrowthRate": 0.0, - "Transactions": [ - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Simple cash-flow: outflow to Wealth Account", - "ValutaDate": "2034-04-01T00:00:00", - "Amount": -68830.0 - } - ] + "Transactions": { + "ExogenousAccount": { + "Id": "d07f5033-3b54-4f0a-899c-973818412400", + "Name": "Exogenous Account", + "Transactions": [ + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 16883.0 + }, + { + "Description": "Simple cash-flow: outflow to Wealth Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -500000.0 + }, + { + "Description": "Simple cash-flow: outflow to Occupational Pension Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -500000.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -24935.10 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 83117.0 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -101000.00 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 16883.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -25235.1000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 84117.00 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -102010.0000 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 16883.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -25538.100000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 85127.0000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -103030.100000 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 16883.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -25844.13000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 86147.100000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -104060.40100000 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 16883.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -26153.2203000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 87177.40100000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -105101.0050100000 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -29465.401503000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 98218.0050100000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -106152.015060100000 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -29780.70451803000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 99269.015060100000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -107213.53521070100000 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -30099.1605632103000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 100330.53521070100000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -108285.6705628080100000 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Residence change costs: inflow from Wealth Account", + "ValutaDate": "2029-07-01T00:00:00", + "Amount": 2000.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -30420.801168842403000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 101402.6705628080100000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -109368.527268436090100000 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -30745.65818053082703000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 102485.527268436090100000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -110462.21254112045100100000 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -33138.6637623361353003000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 110462.21254112045100100000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -111566.8346665316555110100000 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": -33470.050399959496653303000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": 111566.8346665316555110100000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": -112682.50301319697206612010000 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2034-01-01T00:00:00", + "Amount": -33804.750903959091619836030000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2034-01-01T00:00:00", + "Amount": 112682.50301319697206612010000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2034-01-01T00:00:00", + "Amount": -25000.00 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2034-04-01T00:00:00", + "Amount": -63400.0 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2034-04-01T00:00:00", + "Amount": 20000.0 + }, + { + "Description": "Simple cash-flow: inflow from Occupational Pension Account", + "ValutaDate": "2034-04-01T00:00:00", + "Amount": 550000.0 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2035-01-01T00:00:00", + "Amount": 88400.00000000000000000000000 + } + ] + }, + "IncomeAccount": { + "Id": "e14b0208-8d6f-45d0-b2a6-3bf1f035ae62", + "Name": "Income Account", + "Transactions": [ + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Simple cash-flow: outflow to Occupational Pension Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -10000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -83117.0 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 101000.00 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Simple cash-flow: outflow to Occupational Pension Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -10000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -84117.00 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 102010.0000 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Simple cash-flow: outflow to Occupational Pension Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -10000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -85127.0000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 103030.100000 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Simple cash-flow: outflow to Occupational Pension Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -10000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -86147.100000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 104060.40100000 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Simple cash-flow: outflow to Occupational Pension Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -10000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -87177.40100000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 105101.0050100000 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -98218.0050100000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 106152.015060100000 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -99269.015060100000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 107213.53521070100000 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -100330.53521070100000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 108285.6705628080100000 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -101402.6705628080100000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 109368.527268436090100000 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -102485.527268436090100000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 110462.21254112045100100000 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -110462.21254112045100100000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 111566.8346665316555110100000 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": -111566.8346665316555110100000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": 112682.50301319697206612010000 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2034-01-01T00:00:00", + "Amount": -112682.50301319697206612010000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2034-01-01T00:00:00", + "Amount": 25000.00 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2034-04-01T00:00:00", + "Amount": 63400.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2035-01-01T00:00:00", + "Amount": -88400.00000000000000000000000 + } + ] + }, + "WealthAccount": { + "Id": "8867c095-72cb-4f52-87d6-48e378f1dd61", + "Name": "Wealth Account", + "Transactions": [ + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -16883.0 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 500000.0 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 24935.10 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -9701.8750 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -16883.0 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 25235.1000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -9890.795000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -16883.0 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 25538.100000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -10081.90500000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -16883.0 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 25844.13000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -10273.0150000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -16883.0 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 26153.2203000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -10464.125000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 29465.401503000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -12848.62500000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 29780.70451803000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -13095.7250000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 30099.1605632103000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -13365.345000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Residence change costs: outflow to Exogenous Account", + "ValutaDate": "2029-07-01T00:00:00", + "Amount": -2000.0 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 30420.801168842403000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -14716.460559854286002102000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 30745.65818053082703000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -14959.468602483689243991801732 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 33138.6637623361353003000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -16784.695958662622543749164085 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": 33470.050399959496653303000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": -17069.593465814036009441371958 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2034-01-01T00:00:00", + "Amount": 33804.750903959091619836030000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2034-01-01T00:00:00", + "Amount": -17386.981996456626130407467213 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2034-04-01T00:00:00", + "Amount": -20000.0 + }, + { + "Description": "Simple cash-flow: inflow from Third Pillar Account", + "ValutaDate": "2034-04-01T00:00:00", + "Amount": 68830.0 + }, + { + "Description": "Tax payment: outflow to Tax Account", + "ValutaDate": "2034-04-01T00:00:00", + "Amount": -137.60 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2035-01-01T00:00:00", + "Amount": -12300.764801880794076275647971 + } + ] + }, + "InvestmentAccount": { + "Id": "b159e878-a5ea-42f9-be95-fa65436bfb11", + "Name": "Investment Account", + "Transactions": [] + }, + "OccupationalPensionAccount": { + "Id": "a6968f6a-a5d8-4ea5-a4d8-aa50b4c4c440", + "Name": "Occupational Pension Account", + "Transactions": [ + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 10000.0 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 500000.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 10000.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 10000.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 10000.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 10000.0 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2034-04-01T00:00:00", + "Amount": -550000.0 + } + ] + }, + "ThirdPillarAccount": { + "Id": "61e2f9d3-31db-45d2-ae9d-f6bcb7141306", + "Name": "Third Pillar Account", + "Transactions": [ + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Simple cash-flow: outflow to Wealth Account", + "ValutaDate": "2034-04-01T00:00:00", + "Amount": -68830.0 + } + ] + }, + "TaxAccount": { + "Id": "521ef61f-88f8-400f-a551-615043a73801", + "Name": "Tax Account", + "Transactions": [ + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 9701.8750 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 9890.795000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 10081.90500000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 10273.0150000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 10464.125000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 12848.62500000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 13095.7250000000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 13365.345000000000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 14716.460559854286002102000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 14959.468602483689243991801732 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 16784.695958662622543749164085 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": 17069.593465814036009441371958 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2034-01-01T00:00:00", + "Amount": 17386.981996456626130407467213 + }, + { + "Description": "Tax payment: inflow from Wealth Account", + "ValutaDate": "2034-04-01T00:00:00", + "Amount": 137.60 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2035-01-01T00:00:00", + "Amount": 12300.764801880794076275647971 + } + ] + } } }, "Left": null diff --git a/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Investment_Portfolio.snap b/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Investment_Portfolio.snap new file mode 100644 index 00000000..0781aa89 --- /dev/null +++ b/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Investment_Portfolio.snap @@ -0,0 +1,1319 @@ +[ + { + "State": "IsRight", + "Right": { + "StartingYear": 2021, + "NumberOfPeriods": 11, + "Accounts": [ + { + "Year": 2021, + "Amount": -626877.0000, + "AccountType": "Exogenous" + }, + { + "Year": 2021, + "Amount": 0.00, + "AccountType": "Income" + }, + { + "Year": 2021, + "Amount": 509929.525000, + "AccountType": "Wealth" + }, + { + "Year": 2021, + "Amount": 103000.00, + "AccountType": "Investment" + }, + { + "Year": 2021, + "Amount": 0.0, + "AccountType": "OccupationalPension" + }, + { + "Year": 2021, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2021, + "Amount": 13947.475000, + "AccountType": "Tax" + }, + { + "Year": 2022, + "Amount": -660831.868560, + "AccountType": "Exogenous" + }, + { + "Year": 2022, + "Amount": 0.0000, + "AccountType": "Income" + }, + { + "Year": 2022, + "Amount": 519717.41476000, + "AccountType": "Wealth" + }, + { + "Year": 2022, + "Amount": 113153.1838, + "AccountType": "Investment" + }, + { + "Year": 2022, + "Amount": 0.0, + "AccountType": "OccupationalPension" + }, + { + "Year": 2022, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2022, + "Amount": 27961.27000000, + "AccountType": "Tax" + }, + { + "Year": 2023, + "Amount": -695157.18910800, + "AccountType": "Exogenous" + }, + { + "Year": 2023, + "Amount": 0.000000, + "AccountType": "Income" + }, + { + "Year": 2023, + "Amount": 529316.3473180000, + "AccountType": "Wealth" + }, + { + "Year": 2023, + "Amount": 123752.226790, + "AccountType": "Investment" + }, + { + "Year": 2023, + "Amount": 0.0, + "AccountType": "OccupationalPension" + }, + { + "Year": 2023, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2023, + "Amount": 42088.6150000000, + "AccountType": "Tax" + }, + { + "Year": 2024, + "Amount": -729869.1113222640, + "AccountType": "Exogenous" + }, + { + "Year": 2024, + "Amount": 0.00000000, + "AccountType": "Income" + }, + { + "Year": 2024, + "Amount": 538750.981303044000, + "AccountType": "Wealth" + }, + { + "Year": 2024, + "Amount": 134813.33001922, + "AccountType": "Investment" + }, + { + "Year": 2024, + "Amount": 0.0, + "AccountType": "OccupationalPension" + }, + { + "Year": 2024, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2024, + "Amount": 56304.800000000000, + "AccountType": "Tax" + }, + { + "Year": 2025, + "Amount": -764984.370093776400, + "AccountType": "Exogenous" + }, + { + "Year": 2025, + "Amount": 0.0000000000, + "AccountType": "Income" + }, + { + "Year": 2025, + "Amount": 548019.11801994940000, + "AccountType": "Wealth" + }, + { + "Year": 2025, + "Amount": 146353.2370738270, + "AccountType": "Investment" + }, + { + "Year": 2025, + "Amount": 0.0, + "AccountType": "OccupationalPension" + }, + { + "Year": 2025, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2025, + "Amount": 70612.01500000000000, + "AccountType": "Tax" + }, + { + "Year": 2026, + "Amount": -800520.30509707106160, + "AccountType": "Exogenous" + }, + { + "Year": 2026, + "Amount": 0.000000000000, + "AccountType": "Income" + }, + { + "Year": 2026, + "Amount": 557120.7936139182436000, + "AccountType": "Wealth" + }, + { + "Year": 2026, + "Amount": 158389.251483152818, + "AccountType": "Investment" + }, + { + "Year": 2026, + "Amount": 0.0, + "AccountType": "OccupationalPension" + }, + { + "Year": 2026, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2026, + "Amount": 85010.2600000000000000, + "AccountType": "Tax" + }, + { + "Year": 2027, + "Amount": -836494.8809884741904400, + "AccountType": "Exogenous" + }, + { + "Year": 2027, + "Amount": 0.00000000000000, + "AccountType": "Income" + }, + { + "Year": 2027, + "Amount": 566031.381317773559740000, + "AccountType": "Wealth" + }, + { + "Year": 2027, + "Amount": 170939.25467070063070, + "AccountType": "Investment" + }, + { + "Year": 2027, + "Amount": 0.0, + "AccountType": "OccupationalPension" + }, + { + "Year": 2027, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2027, + "Amount": 99524.245000000000000000, + "AccountType": "Tax" + }, + { + "Year": 2028, + "Amount": -872926.708251389233085040, + "AccountType": "Exogenous" + }, + { + "Year": 2028, + "Amount": 0.0000000000000000, + "AccountType": "Income" + }, + { + "Year": 2028, + "Amount": 574769.248784653290740840000, + "AccountType": "Wealth" + }, + { + "Year": 2028, + "Amount": 184021.7244667359423442, + "AccountType": "Investment" + }, + { + "Year": 2028, + "Amount": 0.0, + "AccountType": "OccupationalPension" + }, + { + "Year": 2028, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2028, + "Amount": 114135.735000000000000000000, + "AccountType": "Tax" + }, + { + "Year": 2029, + "Amount": -909835.06470885694334806800, + "AccountType": "Exogenous" + }, + { + "Year": 2029, + "Amount": 0.000000000000000000, + "AccountType": "Income" + }, + { + "Year": 2029, + "Amount": 583289.25550908634415587800000, + "AccountType": "Wealth" + }, + { + "Year": 2029, + "Amount": 197655.754199770599192190, + "AccountType": "Investment" + }, + { + "Year": 2029, + "Amount": 0.0, + "AccountType": "OccupationalPension" + }, + { + "Year": 2029, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2029, + "Amount": 128890.05500000000000000000000, + "AccountType": "Tax" + }, + { + "Year": 2030, + "Amount": -947239.9177242472055843963760, + "AccountType": "Exogenous" + }, + { + "Year": 2030, + "Amount": 0.00000000000000000000, + "AccountType": "Income" + }, + { + "Year": 2030, + "Amount": 591588.35533947025826722339600, + "AccountType": "Wealth" + }, + { + "Year": 2030, + "Amount": 211861.07238477694731717298, + "AccountType": "Investment" + }, + { + "Year": 2030, + "Amount": 0.0, + "AccountType": "OccupationalPension" + }, + { + "Year": 2030, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2030, + "Amount": 143790.49000000000000000000000, + "AccountType": "Tax" + }, + { + "Year": 2031, + "Amount": -954866.9163300991756878146033, + "AccountType": "Exogenous" + }, + { + "Year": 2031, + "Amount": 0.0000000000000000000000, + "AccountType": "Income" + }, + { + "Year": 2031, + "Amount": 591580.74177377891995112643388, + "AccountType": "Wealth" + }, + { + "Year": 2031, + "Amount": 218216.9045563202557366881694, + "AccountType": "Investment" + }, + { + "Year": 2031, + "Amount": 0.0, + "AccountType": "OccupationalPension" + }, + { + "Year": 2031, + "Amount": 0.0, + "AccountType": "ThirdPillar" + }, + { + "Year": 2031, + "Amount": 145069.27000000000000000000000, + "AccountType": "Tax" + } + ], + "Transactions": { + "ExogenousAccount": { + "Id": "c2c6335a-8990-44d2-987e-749336699c5f", + "Name": "Exogenous Account", + "Transactions": [ + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Simple cash-flow: outflow to Wealth Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -500000.0 + }, + { + "Description": "Simple cash-flow: outflow to Investment Portfolio", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -93277.0 + }, + { + "Description": "Capital Gains: outflow to Investment Portfolio", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -2000.00 + }, + { + "Description": "Income: outflow to Investment Portfolio", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -1000.00 + }, + { + "Description": "Investement Income: outflow to Income Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -2000.00 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -30600.0000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 102000.00 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Capital Gains: outflow to Investment Portfolio", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -2197.1492 + }, + { + "Description": "Income: outflow to Investment Portfolio", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -1098.5746 + }, + { + "Description": "Investement Income: outflow to Income Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -2197.1492 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -30659.144760 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 102197.1492 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Capital Gains: outflow to Investment Portfolio", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -2402.955860 + }, + { + "Description": "Income: outflow to Investment Portfolio", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -1201.477930 + }, + { + "Description": "Investement Income: outflow to Income Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -2402.955860 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -30720.88675800 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 102402.955860 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Capital Gains: outflow to Investment Portfolio", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -2617.73456348 + }, + { + "Description": "Income: outflow to Investment Portfolio", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -1308.86728174 + }, + { + "Description": "Investement Income: outflow to Income Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -2617.73456348 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -30785.3203690440 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 102617.73456348 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Capital Gains: outflow to Investment Portfolio", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -2841.8104286180 + }, + { + "Description": "Income: outflow to Investment Portfolio", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -1420.9052143090 + }, + { + "Description": "Investement Income: outflow to Income Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -2841.8104286180 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -30852.543128585400 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 102841.8104286180 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Capital Gains: outflow to Investment Portfolio", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -3075.519446274812 + }, + { + "Description": "Income: outflow to Investment Portfolio", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -1537.759723137406 + }, + { + "Description": "Investement Income: outflow to Income Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -3075.519446274812 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -30922.65583388244360 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 103075.519446274812 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Capital Gains: outflow to Investment Portfolio", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -3319.20882855729380 + }, + { + "Description": "Income: outflow to Investment Portfolio", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -1659.60441427864690 + }, + { + "Description": "Investement Income: outflow to Income Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -3319.20882855729380 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -30995.7626485671881400 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 103319.20882855729380 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Capital Gains: outflow to Investment Portfolio", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -3573.2373682861348028 + }, + { + "Description": "Income: outflow to Investment Portfolio", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -1786.6186841430674014 + }, + { + "Description": "Investement Income: outflow to Income Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -3573.2373682861348028 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -31071.971210485840440840 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 103573.2373682861348028 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Capital Gains: outflow to Investment Portfolio", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -3837.975809704283479460 + }, + { + "Description": "Income: outflow to Investment Portfolio", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -1918.987904852141739730 + }, + { + "Description": "Investement Income: outflow to Income Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -3837.975809704283479460 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -31151.39274291128504383800 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 103837.975809704283479460 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Capital Gains: outflow to Investment Portfolio", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -4113.80723077236790907132 + }, + { + "Description": "Income: outflow to Investment Portfolio", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -2056.90361538618395453566 + }, + { + "Description": "Investement Income: outflow to Income Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -4113.80723077236790907132 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -31234.1421692317103727213960 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 104113.80723077236790907132 + }, + { + "Description": "Capital Gains: outflow to Investment Portfolio", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -4237.2214476955389463434596 + }, + { + "Description": "Income: outflow to Investment Portfolio", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -2118.6107238477694731717298 + }, + { + "Description": "Investement Income: outflow to Income Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -4237.2214476955389463434596 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -1271.166434308661683903037880 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 4237.2214476955389463434596 + } + ] + }, + "IncomeAccount": { + "Id": "359a6cdc-1465-4bde-869c-3f8d7c6b4b57", + "Name": "Income Account", + "Transactions": [ + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Investement Income: inflow from Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 2000.00 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -102000.00 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Investement Income: inflow from Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 2197.1492 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -102197.1492 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Investement Income: inflow from Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 2402.955860 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -102402.955860 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Investement Income: inflow from Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 2617.73456348 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -102617.73456348 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Investement Income: inflow from Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 2841.8104286180 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -102841.8104286180 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Investement Income: inflow from Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 3075.519446274812 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -103075.519446274812 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Investement Income: inflow from Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 3319.20882855729380 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -103319.20882855729380 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Investement Income: inflow from Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 3573.2373682861348028 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -103573.2373682861348028 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Investement Income: inflow from Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 3837.975809704283479460 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -103837.975809704283479460 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Investement Income: inflow from Exogenous Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 4113.80723077236790907132 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -104113.80723077236790907132 + }, + { + "Description": "Investement Income: inflow from Exogenous Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 4237.2214476955389463434596 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -4237.2214476955389463434596 + } + ] + }, + "WealthAccount": { + "Id": "228cdcd8-9e9a-41c9-95d4-917c0400f0e2", + "Name": "Wealth Account", + "Transactions": [ + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 500000.0 + }, + { + "Description": "Simple cash-flow: outflow to Investment Portfolio", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -6723.0 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 30600.0000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -13947.475000 + }, + { + "Description": "Simple cash-flow: outflow to Investment Portfolio", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -6857.46 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 30659.144760 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -14013.79500000 + }, + { + "Description": "Simple cash-flow: outflow to Investment Portfolio", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -6994.6092 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 30720.88675800 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -14127.3450000000 + }, + { + "Description": "Simple cash-flow: outflow to Investment Portfolio", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -7134.501384 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 30785.3203690440 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -14216.185000000000 + }, + { + "Description": "Simple cash-flow: outflow to Investment Portfolio", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -7277.19141168 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 30852.543128585400 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -14307.21500000000000 + }, + { + "Description": "Simple cash-flow: outflow to Investment Portfolio", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -7422.7352399136 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 30922.65583388244360 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -14398.2450000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Investment Portfolio", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -7571.189944711872 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 30995.7626485671881400 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -14513.985000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Investment Portfolio", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -7722.61374360610944 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 31071.971210485840440840 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -14611.490000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Investment Portfolio", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -7877.0660184782316288 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 31151.39274291128504383800 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -14754.320000000000000000000000 + }, + { + "Description": "Simple cash-flow: outflow to Investment Portfolio", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -8034.607338847796261376 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 31234.1421692317103727213960 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -14900.435000000000000000000000 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 1271.166434308661683903037880 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -1278.7800000000000000000000000 + } + ] + }, + "InvestmentAccount": { + "Id": "072b075c-e7eb-4108-b418-6dc12ab2f73a", + "Name": "Investment Portfolio", + "Transactions": [ + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 93277.0 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 6723.0 + }, + { + "Description": "Capital Gains: inflow from Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 2000.00 + }, + { + "Description": "Income: inflow from Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 1000.00 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 6857.46 + }, + { + "Description": "Capital Gains: inflow from Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 2197.1492 + }, + { + "Description": "Income: inflow from Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 1098.5746 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 6994.6092 + }, + { + "Description": "Capital Gains: inflow from Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 2402.955860 + }, + { + "Description": "Income: inflow from Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 1201.477930 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 7134.501384 + }, + { + "Description": "Capital Gains: inflow from Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 2617.73456348 + }, + { + "Description": "Income: inflow from Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 1308.86728174 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 7277.19141168 + }, + { + "Description": "Capital Gains: inflow from Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 2841.8104286180 + }, + { + "Description": "Income: inflow from Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 1420.9052143090 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 7422.7352399136 + }, + { + "Description": "Capital Gains: inflow from Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 3075.519446274812 + }, + { + "Description": "Income: inflow from Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 1537.759723137406 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 7571.189944711872 + }, + { + "Description": "Capital Gains: inflow from Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 3319.20882855729380 + }, + { + "Description": "Income: inflow from Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 1659.60441427864690 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 7722.61374360610944 + }, + { + "Description": "Capital Gains: inflow from Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 3573.2373682861348028 + }, + { + "Description": "Income: inflow from Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 1786.6186841430674014 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 7877.0660184782316288 + }, + { + "Description": "Capital Gains: inflow from Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 3837.975809704283479460 + }, + { + "Description": "Income: inflow from Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 1918.987904852141739730 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 8034.607338847796261376 + }, + { + "Description": "Capital Gains: inflow from Exogenous Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 4113.80723077236790907132 + }, + { + "Description": "Income: inflow from Exogenous Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 2056.90361538618395453566 + }, + { + "Description": "Capital Gains: inflow from Exogenous Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 4237.2214476955389463434596 + }, + { + "Description": "Income: inflow from Exogenous Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 2118.6107238477694731717298 + } + ] + }, + "OccupationalPensionAccount": { + "Id": "1a7aae6b-122b-4cc3-aa9d-cbc791eeee5e", + "Name": "Occupational Pension Account", + "Transactions": [] + }, + "ThirdPillarAccount": { + "Id": "207c3c0f-a65e-47cb-b82d-f8578275d3f8", + "Name": "Third Pillar Account", + "Transactions": [] + }, + "TaxAccount": { + "Id": "beaf2021-845e-4b7c-9814-b0755c2aff05", + "Name": "Tax Account", + "Transactions": [ + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 13947.475000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 14013.79500000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 14127.3450000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 14216.185000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 14307.21500000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 14398.2450000000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 14513.985000000000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 14611.490000000000000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 14754.320000000000000000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 14900.435000000000000000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 1278.7800000000000000000000000 + } + ] + } + } + }, + "Left": null + } +] diff --git a/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_ThirdPillar_Simulation.snap b/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_ThirdPillar_Simulation.snap index a84b9c95..ce69d2b5 100644 --- a/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_ThirdPillar_Simulation.snap +++ b/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_ThirdPillar_Simulation.snap @@ -7,7 +7,7 @@ "Accounts": [ { "Year": 2021, - "Amount": -126772.6400, + "Amount": -137935.10, "AccountType": "Exogenous" }, { @@ -20,6 +20,11 @@ "Amount": 19889.6400, "AccountType": "Wealth" }, + { + "Year": 2021, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2021, "Amount": 100000.0, @@ -30,9 +35,14 @@ "Amount": 6883.0, "AccountType": "ThirdPillar" }, + { + "Year": 2021, + "Amount": 11162.4600, + "AccountType": "Tax" + }, { "Year": 2022, - "Amount": -143545.280000, + "Amount": -165870.200, "AccountType": "Exogenous" }, { @@ -45,6 +55,11 @@ "Amount": 29779.280000, "AccountType": "Wealth" }, + { + "Year": 2022, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2022, "Amount": 100000.0, @@ -55,9 +70,14 @@ "Amount": 13766.0, "AccountType": "ThirdPillar" }, + { + "Year": 2022, + "Amount": 22324.920000, + "AccountType": "Tax" + }, { "Year": 2023, - "Amount": -160317.92000000, + "Amount": -193805.3000, "AccountType": "Exogenous" }, { @@ -70,6 +90,11 @@ "Amount": 39668.92000000, "AccountType": "Wealth" }, + { + "Year": 2023, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2023, "Amount": 100000.0, @@ -80,9 +105,14 @@ "Amount": 20649.00, "AccountType": "ThirdPillar" }, + { + "Year": 2023, + "Amount": 33487.38000000, + "AccountType": "Tax" + }, { "Year": 2024, - "Amount": -177090.56000000, + "Amount": -221740.40000, "AccountType": "Exogenous" }, { @@ -95,6 +125,11 @@ "Amount": 49558.56000000, "AccountType": "Wealth" }, + { + "Year": 2024, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2024, "Amount": 100000.0, @@ -105,9 +140,14 @@ "Amount": 27532.000, "AccountType": "ThirdPillar" }, + { + "Year": 2024, + "Amount": 44649.84000000, + "AccountType": "Tax" + }, { "Year": 2025, - "Amount": -193863.20000000, + "Amount": -249675.500000, "AccountType": "Exogenous" }, { @@ -120,6 +160,11 @@ "Amount": 59448.20000000, "AccountType": "Wealth" }, + { + "Year": 2025, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2025, "Amount": 100000.0, @@ -130,9 +175,14 @@ "Amount": 34415.0000, "AccountType": "ThirdPillar" }, + { + "Year": 2025, + "Amount": 55812.30000000, + "AccountType": "Tax" + }, { "Year": 2026, - "Amount": -210635.84000000, + "Amount": -277610.6000000, "AccountType": "Exogenous" }, { @@ -145,6 +195,11 @@ "Amount": 69337.84000000, "AccountType": "Wealth" }, + { + "Year": 2026, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2026, "Amount": 100000.0, @@ -155,9 +210,14 @@ "Amount": 41298.00000, "AccountType": "ThirdPillar" }, + { + "Year": 2026, + "Amount": 66974.76000000, + "AccountType": "Tax" + }, { "Year": 2027, - "Amount": -227408.48000000, + "Amount": -305545.70000000, "AccountType": "Exogenous" }, { @@ -170,6 +230,11 @@ "Amount": 79227.48000000, "AccountType": "Wealth" }, + { + "Year": 2027, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2027, "Amount": 100000.0, @@ -180,9 +245,14 @@ "Amount": 48181.000000, "AccountType": "ThirdPillar" }, + { + "Year": 2027, + "Amount": 78137.22000000, + "AccountType": "Tax" + }, { "Year": 2028, - "Amount": -244181.120000000, + "Amount": -333480.800000000, "AccountType": "Exogenous" }, { @@ -195,6 +265,11 @@ "Amount": 89117.120000000, "AccountType": "Wealth" }, + { + "Year": 2028, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2028, "Amount": 100000.0, @@ -205,9 +280,14 @@ "Amount": 55064.0000000, "AccountType": "ThirdPillar" }, + { + "Year": 2028, + "Amount": 89299.680000000, + "AccountType": "Tax" + }, { "Year": 2029, - "Amount": -260953.7600000000, + "Amount": -361415.9000000000, "AccountType": "Exogenous" }, { @@ -220,6 +300,11 @@ "Amount": 99006.7600000000, "AccountType": "Wealth" }, + { + "Year": 2029, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2029, "Amount": 100000.0, @@ -230,9 +315,14 @@ "Amount": 61947.00000000, "AccountType": "ThirdPillar" }, + { + "Year": 2029, + "Amount": 100462.1400000000, + "AccountType": "Tax" + }, { "Year": 2030, - "Amount": -277726.40000000000, + "Amount": -389351.00000000000, "AccountType": "Exogenous" }, { @@ -245,6 +335,11 @@ "Amount": 108896.40000000000, "AccountType": "Wealth" }, + { + "Year": 2030, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2030, "Amount": 100000.0, @@ -255,9 +350,14 @@ "Amount": 68830.000000000, "AccountType": "ThirdPillar" }, + { + "Year": 2030, + "Amount": 111624.60000000000, + "AccountType": "Tax" + }, { "Year": 2031, - "Amount": -295010.06000000000, + "Amount": -419351.00000000000, "AccountType": "Exogenous" }, { @@ -270,6 +370,11 @@ "Amount": 126180.06000000000, "AccountType": "Wealth" }, + { + "Year": 2031, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2031, "Amount": 100000.0, @@ -280,9 +385,14 @@ "Amount": 68830.000000000, "AccountType": "ThirdPillar" }, + { + "Year": 2031, + "Amount": 124340.94000000000, + "AccountType": "Tax" + }, { "Year": 2032, - "Amount": -312291.53000000000000, + "Amount": -449351.00000000000, "AccountType": "Exogenous" }, { @@ -295,6 +405,11 @@ "Amount": 143461.53000000000000, "AccountType": "Wealth" }, + { + "Year": 2032, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2032, "Amount": 100000.0, @@ -305,9 +420,14 @@ "Amount": 68830.000000000, "AccountType": "ThirdPillar" }, + { + "Year": 2032, + "Amount": 137059.47000000000000, + "AccountType": "Tax" + }, { "Year": 2033, - "Amount": -312243.53000000000000, + "Amount": -449351.00000000000, "AccountType": "Exogenous" }, { @@ -320,6 +440,11 @@ "Amount": 143413.53000000000000, "AccountType": "Wealth" }, + { + "Year": 2033, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2033, "Amount": 100000.0, @@ -329,757 +454,765 @@ "Year": 2033, "Amount": 68830.000000000, "AccountType": "ThirdPillar" + }, + { + "Year": 2033, + "Amount": 137107.47000000000000, + "AccountType": "Tax" } ], - "ExogenousAccount": { - "Id": "6f79f688-f596-4378-bf52-5a69b2bf3ab0", - "Name": "Exogenous Account", - "Balance": -312243.53000000000000, - "InterestRate": 0.0, - "Transactions": [ - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": -100000.0 - }, - { - "Description": "Simple cash-flow: outflow to Wealth Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": -10000.0 - }, - { - "Description": "Simple cash-flow: outflow to Occupational Pension Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": -100000.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -27935.10 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 11162.4600 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 93117.0 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -100000.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": -27935.100 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": 11162.460000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": 93117.0 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": 6883.00 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": -100000.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": -27935.1000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": 11162.46000000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": 93117.00 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": 6883.000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": -100000.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": -27935.10000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": 11162.46000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": 93117.000 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": 6883.0000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": -100000.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": -27935.100000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": 11162.460000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": 93117.0000 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": 6883.00000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": -100000.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": -27935.1000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": 11162.4600000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": 93117.00000 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": 6883.000000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": -100000.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": -27935.10000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": 11162.46000000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": 93117.000000 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": 6883.0000000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": -100000.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": -27935.100000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": 11162.460000000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": 93117.0000000 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": 6883.00000000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": -100000.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": -27935.1000000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": 11162.4600000000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": 93117.00000000 - }, - { - "Description": "Simple cash-flow: inflow from Wealth Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": 6883.000000000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": -100000.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": -27935.10000000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": 11162.46000000000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": 93117.000000000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": -100000.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": -30000.00000000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": 12716.34000000000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": 100000.000000000 - }, - { - "Description": "Simple cash-flow: outflow to Income Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": -100000.0 - }, - { - "Description": "Savings Quota: outflow to Wealth Account", - "ValutaDate": "2033-01-01T00:00:00", - "Amount": -30000.00000000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2033-01-01T00:00:00", - "Amount": 12718.53000000000000 - }, - { - "Description": "Clear income account: inflow from Income Account", - "ValutaDate": "2033-01-01T00:00:00", - "Amount": 100000.000000000 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2034-01-01T00:00:00", - "Amount": 48.00000000000 - } - ] - }, - "IncomeAccount": { - "Id": "7df170da-401e-46a7-897f-38b1dc26a1eb", - "Name": "Income Account", - "Balance": 0.000000000, - "InterestRate": 0.0, - "Transactions": [ - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": 100000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -93117.0 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 100000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": -93117.0 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": -6883.00 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": 100000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": -93117.00 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": -6883.000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": 100000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": -93117.000 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": -6883.0000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": 100000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": -93117.0000 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": -6883.00000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": 100000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": -93117.00000 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": -6883.000000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": 100000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": -93117.000000 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": -6883.0000000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": 100000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": -93117.0000000 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": -6883.00000000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": 100000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": -93117.00000000 - }, - { - "Description": "Simple cash-flow: outflow to Third Pillar Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": -6883.000000000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": 100000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": -93117.000000000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": 100000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": -100000.000000000 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": 100000.0 - }, - { - "Description": "Clear income account: outflow to Exogenous Account", - "ValutaDate": "2033-01-01T00:00:00", - "Amount": -100000.000000000 - } - ] - }, - "WealthAccount": { - "Id": "8c3bc969-53e2-4e3d-8de0-8806225bd47d", - "Name": "Wealth Account", - "Balance": 143413.53000000000000, - "NetGrowthRate": 0.0, - "Transactions": [ - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": 10000.0 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 27935.10 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -11162.4600 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -6883.0 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": 27935.100 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": -11162.460000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": -6883.00 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": 27935.1000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": -11162.46000000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": -6883.000 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": 27935.10000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": -11162.46000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": -6883.0000 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": 27935.100000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": -11162.460000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": -6883.00000 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": 27935.1000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": -11162.4600000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": -6883.000000 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": 27935.10000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": -11162.46000000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": -6883.0000000 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": 27935.100000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": -11162.460000000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": -6883.00000000 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": 27935.1000000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": -11162.4600000000 - }, - { - "Description": "Simple cash-flow: outflow to Exogenous Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": -6883.000000000 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": 27935.10000000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2031-01-01T00:00:00", - "Amount": -11162.46000000000 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": 30000.00000000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2032-01-01T00:00:00", - "Amount": -12716.34000000000 - }, - { - "Description": "Savings Quota: inflow from Exogenous Account", - "ValutaDate": "2033-01-01T00:00:00", - "Amount": 30000.00000000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2033-01-01T00:00:00", - "Amount": -12718.53000000000000 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2034-01-01T00:00:00", - "Amount": -48.00000000000 - } - ] - }, - "OccupationalPensionAccount": { - "Id": "d660baa9-ffc3-4d19-9280-ad9a4b396ed7", - "Name": "Occupational Pension Account", - "Balance": 100000.0, - "NetGrowthRate": 0.0, - "Transactions": [ - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": 100000.0 - } - ] - }, - "ThirdPillarAccount": { - "Id": "d2364888-aa79-4138-bd6a-b494677a12cf", - "Name": "Third Pillar Account", - "Balance": 68830.000000000, - "NetGrowthRate": 0.0, - "Transactions": [ - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 6883.0 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2023-01-01T00:00:00", - "Amount": 6883.00 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2024-01-01T00:00:00", - "Amount": 6883.000 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2025-01-01T00:00:00", - "Amount": 6883.0000 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2026-01-01T00:00:00", - "Amount": 6883.00000 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2027-01-01T00:00:00", - "Amount": 6883.000000 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2028-01-01T00:00:00", - "Amount": 6883.0000000 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2029-01-01T00:00:00", - "Amount": 6883.00000000 - }, - { - "Description": "Simple cash-flow: inflow from Income Account", - "ValutaDate": "2030-01-01T00:00:00", - "Amount": 6883.000000000 - } - ] + "Transactions": { + "ExogenousAccount": { + "Id": "39c8a150-782e-4c46-8790-e58a6a826a54", + "Name": "Exogenous Account", + "Transactions": [ + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Simple cash-flow: outflow to Wealth Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -10000.0 + }, + { + "Description": "Simple cash-flow: outflow to Occupational Pension Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -27935.10 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 93117.0 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -27935.100 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 93117.0 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 6883.00 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -27935.1000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 93117.00 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 6883.000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -27935.10000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 93117.000 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 6883.0000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -27935.100000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 93117.0000 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 6883.00000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -27935.1000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 93117.00000 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 6883.000000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -27935.10000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 93117.000000 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 6883.0000000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -27935.100000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 93117.0000000 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 6883.00000000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -27935.1000000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 93117.00000000 + }, + { + "Description": "Simple cash-flow: inflow from Wealth Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 6883.000000000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -27935.10000000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 93117.000000000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -30000.00000000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 100000.000000000 + }, + { + "Description": "Simple cash-flow: outflow to Income Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -100000.0 + }, + { + "Description": "Savings Quota: outflow to Wealth Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": -30000.00000000000 + }, + { + "Description": "Clear income account: inflow from Income Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": 100000.000000000 + } + ] + }, + "IncomeAccount": { + "Id": "78ae7997-5b8e-4f27-9c0c-4fa3431f3733", + "Name": "Income Account", + "Transactions": [ + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -93117.0 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -93117.0 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -6883.00 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -93117.00 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -6883.000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -93117.000 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -6883.0000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -93117.0000 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -6883.00000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -93117.00000 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -6883.000000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -93117.000000 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -6883.0000000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -93117.0000000 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -6883.00000000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -93117.00000000 + }, + { + "Description": "Simple cash-flow: outflow to Third Pillar Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -6883.000000000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -93117.000000000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -100000.000000000 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 100000.0 + }, + { + "Description": "Clear income account: outflow to Exogenous Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": -100000.000000000 + } + ] + }, + "WealthAccount": { + "Id": "79171560-80e1-476a-b834-a4e20ad4866c", + "Name": "Wealth Account", + "Transactions": [ + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 10000.0 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 27935.10 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -11162.4600 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -6883.0 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 27935.100 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -11162.460000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": -6883.00 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 27935.1000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -11162.46000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": -6883.000 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 27935.10000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -11162.46000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": -6883.0000 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 27935.100000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -11162.460000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": -6883.00000 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 27935.1000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -11162.4600000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": -6883.000000 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 27935.10000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -11162.46000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": -6883.0000000 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 27935.100000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -11162.460000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": -6883.00000000 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 27935.1000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -11162.4600000000 + }, + { + "Description": "Simple cash-flow: outflow to Exogenous Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": -6883.000000000 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 27935.10000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": -11162.46000000000 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 30000.00000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": -12716.34000000000 + }, + { + "Description": "Savings Quota: inflow from Exogenous Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": 30000.00000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": -12718.53000000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2034-01-01T00:00:00", + "Amount": -48.00000000000 + } + ] + }, + "InvestmentAccount": { + "Id": "cbd9ad2a-7e09-4468-a4ba-6a5ea54bd179", + "Name": "Investment Account", + "Transactions": [] + }, + "OccupationalPensionAccount": { + "Id": "bb2e8a04-330a-4c3a-bb69-57395ebdb316", + "Name": "Occupational Pension Account", + "Transactions": [ + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 100000.0 + } + ] + }, + "ThirdPillarAccount": { + "Id": "ba364241-07ec-4d71-a531-76e83f432fde", + "Name": "Third Pillar Account", + "Transactions": [ + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 6883.0 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 6883.00 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 6883.000 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 6883.0000 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 6883.00000 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 6883.000000 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 6883.0000000 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 6883.00000000 + }, + { + "Description": "Simple cash-flow: inflow from Income Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 6883.000000000 + } + ] + }, + "TaxAccount": { + "Id": "f22ac619-e94c-4521-ad9f-8f6bd59d31ef", + "Name": "Tax Account", + "Transactions": [ + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 11162.4600 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2023-01-01T00:00:00", + "Amount": 11162.460000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2024-01-01T00:00:00", + "Amount": 11162.46000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2025-01-01T00:00:00", + "Amount": 11162.46000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2026-01-01T00:00:00", + "Amount": 11162.460000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2027-01-01T00:00:00", + "Amount": 11162.4600000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2028-01-01T00:00:00", + "Amount": 11162.46000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2029-01-01T00:00:00", + "Amount": 11162.460000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2030-01-01T00:00:00", + "Amount": 11162.4600000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2031-01-01T00:00:00", + "Amount": 11162.46000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2032-01-01T00:00:00", + "Amount": 12716.34000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2033-01-01T00:00:00", + "Amount": 12718.53000000000000 + }, + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2034-01-01T00:00:00", + "Amount": 48.00000000000 + } + ] + } } }, "Left": null diff --git a/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Wealth_Only_Simulation.snap b/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Wealth_Only_Simulation.snap index d6af908c..cc767110 100644 --- a/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Wealth_Only_Simulation.snap +++ b/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Wealth_Only_Simulation.snap @@ -7,7 +7,7 @@ "Accounts": [ { "Year": 2021, - "Amount": -504436.2550, + "Amount": -505000.00, "AccountType": "Exogenous" }, { @@ -20,6 +20,11 @@ "Amount": 504436.2550, "AccountType": "Wealth" }, + { + "Year": 2021, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2021, "Amount": 0.0, @@ -29,74 +34,82 @@ "Year": 2021, "Amount": 0.0, "AccountType": "ThirdPillar" + }, + { + "Year": 2021, + "Amount": 563.7450, + "AccountType": "Tax" } ], - "ExogenousAccount": { - "Id": "b5cc39fe-d40e-407a-9bb0-c70f541007bd", - "Name": "Exogenous Account", - "Balance": -504436.2550, - "InterestRate": 0.0, - "Transactions": [ - { - "Description": "Simple cash-flow: outflow to Wealth Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": -500000.0 - }, - { - "Description": "Compound Return Wealth: outflow to Wealth Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -5000.00 - }, - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 563.7450 - } - ] - }, - "IncomeAccount": { - "Id": "29c99d31-c54c-4602-b560-874844f05c64", - "Name": "Income Account", - "Balance": 0.0, - "InterestRate": 0.0, - "Transactions": [] - }, - "WealthAccount": { - "Id": "281a023f-7a3a-4e1f-9158-90d1092d15d2", - "Name": "Wealth Account", - "Balance": 504436.2550, - "NetGrowthRate": 0.0, - "Transactions": [ - { - "Description": "Simple cash-flow: inflow from Exogenous Account", - "ValutaDate": "2021-01-01T00:00:00", - "Amount": 500000.0 - }, - { - "Description": "Compound Return Wealth: inflow from Exogenous Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 5000.00 - }, - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -563.7450 - } - ] - }, - "OccupationalPensionAccount": { - "Id": "e0f0758d-fcf7-4620-b8cd-53fe9891143c", - "Name": "Occupational Pension Account", - "Balance": 0.0, - "NetGrowthRate": 0.0, - "Transactions": [] - }, - "ThirdPillarAccount": { - "Id": "af38a140-0566-4458-8026-3f42b801b532", - "Name": "Third Pillar Account", - "Balance": 0.0, - "NetGrowthRate": 0.0, - "Transactions": [] + "Transactions": { + "ExogenousAccount": { + "Id": "f38cbd4b-5a01-49b5-9a60-3f89efb8bebe", + "Name": "Exogenous Account", + "Transactions": [ + { + "Description": "Simple cash-flow: outflow to Wealth Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": -500000.0 + }, + { + "Description": "Compound Return Wealth: outflow to Wealth Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -5000.00 + } + ] + }, + "IncomeAccount": { + "Id": "ef2f59c5-47a0-4b1b-a39d-c56f1b08ec98", + "Name": "Income Account", + "Transactions": [] + }, + "WealthAccount": { + "Id": "74c9475c-2efd-42c5-83f0-492d6d599e70", + "Name": "Wealth Account", + "Transactions": [ + { + "Description": "Simple cash-flow: inflow from Exogenous Account", + "ValutaDate": "2021-01-01T00:00:00", + "Amount": 500000.0 + }, + { + "Description": "Compound Return Wealth: inflow from Exogenous Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 5000.00 + }, + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -563.7450 + } + ] + }, + "InvestmentAccount": { + "Id": "236f7341-c5e7-45e8-8203-c93986c1ee80", + "Name": "Investment Account", + "Transactions": [] + }, + "OccupationalPensionAccount": { + "Id": "85496fce-9a73-4f00-954f-3b13049c0c5d", + "Name": "Occupational Pension Account", + "Transactions": [] + }, + "ThirdPillarAccount": { + "Id": "14c35e7a-4198-4357-aa22-136bcbd46a2c", + "Name": "Third Pillar Account", + "Transactions": [] + }, + "TaxAccount": { + "Id": "3a7075f4-bc63-493b-8cd7-262773b9ca63", + "Name": "Tax Account", + "Transactions": [ + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 563.7450 + } + ] + } } }, "Left": null diff --git a/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Zero_Simulation.snap b/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Zero_Simulation.snap index 2033997e..4ed5c1e1 100644 --- a/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Zero_Simulation.snap +++ b/src/Calculators.CashFlow.Tests/__snapshots__/MultiPeriodCashFlowCalculatorTests.Calculate_Zero_Simulation.snap @@ -7,7 +7,7 @@ "Accounts": [ { "Year": 2021, - "Amount": 48.00, + "Amount": 0.0, "AccountType": "Exogenous" }, { @@ -20,6 +20,11 @@ "Amount": -48.00, "AccountType": "Wealth" }, + { + "Year": 2021, + "Amount": 0.0, + "AccountType": "Investment" + }, { "Year": 2021, "Amount": 0.0, @@ -29,54 +34,61 @@ "Year": 2021, "Amount": 0.0, "AccountType": "ThirdPillar" + }, + { + "Year": 2021, + "Amount": 48.00, + "AccountType": "Tax" } ], - "ExogenousAccount": { - "Id": "28a78100-f3bf-4b0f-bf32-03ca26842a15", - "Name": "Exogenous Account", - "Balance": 48.00, - "InterestRate": 0.0, - "Transactions": [ - { - "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": 48.00 - } - ] - }, - "IncomeAccount": { - "Id": "3e733805-8f31-4ade-852d-d1b009f486f5", - "Name": "Income Account", - "Balance": 0.0, - "InterestRate": 0.0, - "Transactions": [] - }, - "WealthAccount": { - "Id": "13028d7d-b413-4f77-8b1c-76af3f63625e", - "Name": "Wealth Account", - "Balance": -48.00, - "NetGrowthRate": 0.0, - "Transactions": [ - { - "Description": "Yearly Income and Wealth Tax: outflow to Exogenous Account", - "ValutaDate": "2022-01-01T00:00:00", - "Amount": -48.00 - } - ] - }, - "OccupationalPensionAccount": { - "Id": "6be3407d-01b4-42d7-9e80-06cae0a14758", - "Name": "Occupational Pension Account", - "Balance": 0.0, - "NetGrowthRate": 0.0, - "Transactions": [] - }, - "ThirdPillarAccount": { - "Id": "4b6813b5-b06f-4d43-b3ee-a4d26a2645c3", - "Name": "Third Pillar Account", - "Balance": 0.0, - "NetGrowthRate": 0.0, - "Transactions": [] + "Transactions": { + "ExogenousAccount": { + "Id": "0b752015-5089-4a63-a810-813ec1c3de55", + "Name": "Exogenous Account", + "Transactions": [] + }, + "IncomeAccount": { + "Id": "90165b60-aa4e-4cb9-a73b-7ced6b3aa7b7", + "Name": "Income Account", + "Transactions": [] + }, + "WealthAccount": { + "Id": "feb957b6-874d-4a94-b6ff-0a098ab4fab3", + "Name": "Wealth Account", + "Transactions": [ + { + "Description": "Yearly Income and Wealth Tax: outflow to Tax Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": -48.00 + } + ] + }, + "InvestmentAccount": { + "Id": "e5530baf-aae3-4a28-940c-79f4edbe038f", + "Name": "Investment Account", + "Transactions": [] + }, + "OccupationalPensionAccount": { + "Id": "ba3f2657-26ae-46aa-82fa-e4d2c73962eb", + "Name": "Occupational Pension Account", + "Transactions": [] + }, + "ThirdPillarAccount": { + "Id": "04085a38-35da-4886-8981-330446692b65", + "Name": "Third Pillar Account", + "Transactions": [] + }, + "TaxAccount": { + "Id": "6113b825-8eb6-4960-a8d8-7bb5eaad2011", + "Name": "Tax Account", + "Transactions": [ + { + "Description": "Yearly Income and Wealth Tax: inflow from Wealth Account", + "ValutaDate": "2022-01-01T00:00:00", + "Amount": 48.00 + } + ] + } } }, "Left": null diff --git a/src/Calculators.CashFlow/Calculators.CashFlow.csproj b/src/Calculators.CashFlow/Calculators.CashFlow.csproj deleted file mode 100644 index ebdede5f..00000000 --- a/src/Calculators.CashFlow/Calculators.CashFlow.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - net7.0 - - - - - - - - - - - - - - - - - diff --git a/src/Calculators.CashFlow/CashFlowCalculatorsServiceCollectionExtensions.cs b/src/Calculators.CashFlow/CashFlowCalculatorsServiceCollectionExtensions.cs deleted file mode 100644 index ccf2cdfa..00000000 --- a/src/Calculators.CashFlow/CashFlowCalculatorsServiceCollectionExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Calculators.CashFlow -{ - public static class CashFlowCalculatorsServiceCollectionExtensions - { - public static void AddCashFlowCalculators(this IServiceCollection collection) - { - collection.AddTransient(); - collection.AddTransient(); - } - } -} diff --git a/src/Calculators.CashFlow/MultiPeriodCashFlowCalculator.cs b/src/Calculators.CashFlow/MultiPeriodCashFlowCalculator.cs deleted file mode 100644 index e68c192a..00000000 --- a/src/Calculators.CashFlow/MultiPeriodCashFlowCalculator.cs +++ /dev/null @@ -1,371 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Calculators.CashFlow.Accounts; -using Calculators.CashFlow.Models; -using LanguageExt; -using Microsoft.Extensions.Logging; -using PensionCoach.Tools.BvgCalculator; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.MultiPeriod; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Actions; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; - -namespace Calculators.CashFlow; - -public class MultiPeriodCashFlowCalculator : IMultiPeriodCashFlowCalculator -{ - private readonly IFullWealthAndIncomeTaxCalculator _fullTaxCalculator; - private readonly IFullCapitalBenefitTaxCalculator _capitalBenefitCalculator; - private readonly IMunicipalityConnector municipalityConnector; - private readonly ILogger _logger; - - public MultiPeriodCashFlowCalculator( - IFullWealthAndIncomeTaxCalculator fullTaxCalculator, - IFullCapitalBenefitTaxCalculator capitalBenefitCalculator, - IMunicipalityConnector municipalityConnector, - ILogger logger) - { - _fullTaxCalculator = fullTaxCalculator; - _capitalBenefitCalculator = capitalBenefitCalculator; - this.municipalityConnector = municipalityConnector; - _logger = logger; - } - - - /// - public async Task> CalculateAsync( - int startingYear, - int minimumNumberOfPeriods, - MultiPeriodCalculatorPerson person, - CashFlowDefinitionHolder cashFlowDefinitionHolder, - MultiPeriodOptions options) - { - DateTime dateOfStart = new DateTime(startingYear, 1, 1); - - ExogenousAccount exogenousAccount = new() { Id = Guid.NewGuid(), Name = "Exogenous Account", }; - - IncomeAccount incomeAccount = new() { Id = Guid.NewGuid(), Name = "Income Account", }; - - WealthAccount wealthAccount = new() { Id = Guid.NewGuid(), Name = "Wealth Account", }; - - OccupationalPensionAccount occupationalPensionAccount = new() - { - Id = Guid.NewGuid(), Name = "Occupational Pension Account", - }; - - ThirdPillarAccount thirdPillarAccount = new() - { - Id = Guid.NewGuid(), - Name = "Third Pillar Account", - }; - - IEnumerable definitionFromComposites = cashFlowDefinitionHolder.Composites - .SelectMany(composite => composite.CreateGenericDefinition(person, dateOfStart)); - - IEnumerable staticCashFlowsFromComposites = definitionFromComposites - .OfType() - .SelectMany(d => d.GenerateCashFlow()) - .AggregateCashFlows(); - - IEnumerable staticCashFlowsFromGenerics = cashFlowDefinitionHolder.StaticGenericCashFlowDefinitions - .SelectMany(d => d.GenerateCashFlow()) - .AggregateCashFlows(); - - IEnumerable staticCashFlows = staticCashFlowsFromGenerics - .Concat(staticCashFlowsFromComposites) - .ToList(); - - var combinedList = definitionFromComposites - .Concat(cashFlowDefinitionHolder.CashFlowActions).ToList(); - - int finalYear = combinedList.Count > 0 - ? combinedList.Max(d => d.DateOfProcess.Year) - : startingYear + minimumNumberOfPeriods; - - List singlePeriodCalculationResults = - Enumerable.Empty().ToList(); - - Dictionary currentAccounts = new Dictionary - { - { AccountType.Exogenous, exogenousAccount }, - { AccountType.Income, incomeAccount }, - { AccountType.Wealth, wealthAccount }, - { AccountType.OccupationalPension, occupationalPensionAccount }, - { AccountType.ThirdPillar, thirdPillarAccount } - }; - - MultiPeriodCalculatorPerson currentPerson = person; - for (int currentYear = startingYear; currentYear <= finalYear; currentYear++) - { - DateOnly startingDate = new DateOnly(currentYear, 1, 1); - DateOnly finalDate = new DateOnly(currentYear, 1, 1).AddYears(1); - - // all days in the current year - for (DateOnly currentDate = startingDate; currentDate < finalDate; currentDate = currentDate.AddDays(1)) - { - var currentDateAsDateTime = currentDate.ToDateTime(TimeOnly.MinValue); - - List currentDateStaticCashFlows = staticCashFlows - .Where(item => item.DateOfProcess == currentDate) - .ToList(); - - List currentDateChangeResidenceActions = cashFlowDefinitionHolder - .ChangeResidenceActions - .Where(item => item.DateOfProcess == currentDateAsDateTime) - .ToList(); - - List currentDateDynamicCashFlows = definitionFromComposites - .Concat(cashFlowDefinitionHolder.CashFlowActions) - .OfType() - .Where(item => item.DateOfProcess == currentDateAsDateTime) - .SelectMany(item => item.CreateGenericDefinition(currentAccounts)) - .SelectMany(item => item.GenerateCashFlow()) - .ToList(); - - // 1. change residence - foreach (var changeAction in currentDateChangeResidenceActions.OfType()) - { - currentPerson = currentPerson with - { - MunicipalityId = changeAction.DestinationMunicipalityId, - Canton = changeAction.DestinationCanton, - }; - - currentAccounts = ProcessResidenceChangeAction(currentAccounts, changeAction); - } - - // 2. process simple cash-flow: move amount from source to target account - foreach (CashFlowModel cashFlow in currentDateStaticCashFlows.Concat(currentDateDynamicCashFlows)) - { - currentAccounts = await ProcessSimpleCashFlowAsync(currentAccounts, cashFlow, person); - } - } - - currentAccounts = await ProcessEndOfYearSettlementAsync(currentAccounts, currentPerson, finalDate, options); - - // collect calculation results - int year = currentYear; - currentAccounts - .Select(pair => new SinglePeriodCalculationResult - { - Year = year, Amount = pair.Value.Balance, AccountType = pair.Key - }) - .Iter(item => singlePeriodCalculationResults.Add(item)); - } - - return new MultiPeriodCalculationResult - { - StartingYear = startingYear, - NumberOfPeriods = finalYear - startingYear + 1, - Accounts = singlePeriodCalculationResults, - ExogenousAccount = exogenousAccount, - IncomeAccount = incomeAccount, - WealthAccount = wealthAccount, - OccupationalPensionAccount = occupationalPensionAccount, - ThirdPillarAccount = thirdPillarAccount - }; - } - - public Task> CalculateWithSetupsAsync( - int startingYear, - int minimumNumberOfPeriods, - MultiPeriodCalculatorPerson person, - CashFlowDefinitionHolder cashFlowDefinitionHolder, - MultiPeriodOptions options) - { - ICompositeCashFlowDefinition accountSetupDefinition = new SetupAccountDefinition - { - InitialOccupationalPensionAssets = person.CapitalBenefits.PensionPlan + person.CapitalBenefits.Pillar3a, - InitialWealth = person.Wealth - }; - - CashFlowDefinitionHolder extendedDefinitionHolder = cashFlowDefinitionHolder with - { - Composites = cashFlowDefinitionHolder.Composites - .Concat(new[] { accountSetupDefinition }) - .ToList() - }; - - return CalculateAsync(startingYear, minimumNumberOfPeriods, person, extendedDefinitionHolder, options); - } - - private async Task> ProcessSimpleCashFlowAsync( - Dictionary currentAccounts, CashFlowModel cashFlow, MultiPeriodCalculatorPerson person) - { - ICashFlowAccount creditAccount = currentAccounts[cashFlow.Target]; - ICashFlowAccount debitAccount = currentAccounts[cashFlow.Source]; - - ExecuteTransaction(debitAccount, creditAccount, "Simple cash-flow", cashFlow.DateOfProcess.ToDateTime(TimeOnly.MinValue), cashFlow.Amount); - - if (cashFlow.IsTaxable) - { - // Tax reduces wealth as transaction is taxable. - if (cashFlow.TaxType == TaxType.CapitalBenefits) - { - var taxPaymentAmount = await CalculateCapitalBenefitsTaxAsync(cashFlow.DateOfProcess.Year, person, cashFlow.Amount); - - ICashFlowAccount wealthAccount = currentAccounts[AccountType.Wealth]; - ICashFlowAccount exogenousAccount = currentAccounts[AccountType.Exogenous]; - - ExecuteTransaction(wealthAccount, exogenousAccount, "Tax payment", cashFlow.DateOfProcess.ToDateTime(TimeOnly.MinValue), taxPaymentAmount); - } - } - - return currentAccounts; - } - - private Dictionary ProcessResidenceChangeAction( - Dictionary currentAccounts, ChangeResidenceAction action) - { - ICashFlowAccount wealthAccount = currentAccounts[AccountType.Wealth]; - ICashFlowAccount exogenousAccount = currentAccounts[AccountType.Exogenous]; - - ExecuteTransaction(wealthAccount, exogenousAccount, "Residence change costs", action.DateOfProcess, action.ChangeCost); - - return currentAccounts; - } - - private async Task> ProcessEndOfYearSettlementAsync( - Dictionary currentAccounts, - MultiPeriodCalculatorPerson person, - DateOnly finalDate, - MultiPeriodOptions options) - { - ICashFlowAccount incomeAccount = currentAccounts[AccountType.Income]; - ICashFlowAccount wealthAccount = currentAccounts[AccountType.Wealth]; - ICashFlowAccount exogenousAccount = currentAccounts[AccountType.Exogenous]; - ICashFlowAccount occupationalPensionAccount = currentAccounts[AccountType.OccupationalPension]; - ICashFlowAccount thirdPillarAccount = currentAccounts[AccountType.ThirdPillar]; - - DateTime finalDateAsDateTime = finalDate.ToDateTime(TimeOnly.MinValue); - - // compound wealth and capital benefits accounts - // todo: instead of assuming account balance is compounded a full year a time-weighted calculation should be used (TWR). - // This can be achieved by the ordered list of transaction on the accounts. - decimal wealthCompoundedReturn = wealthAccount.Balance * options.WealthNetGrowthRate; - ExecuteTransaction(exogenousAccount, wealthAccount, "Compound Return Wealth", finalDateAsDateTime, wealthCompoundedReturn); - - decimal occupationalPensionCompoundedReturn = occupationalPensionAccount.Balance * options.CapitalBenefitsNetGrowthRate; - ExecuteTransaction(exogenousAccount, occupationalPensionAccount, "Compound Return Occupational Pension", finalDateAsDateTime, occupationalPensionCompoundedReturn); - - decimal thirdPillarCompoundedReturn = thirdPillarAccount.Balance * options.CapitalBenefitsNetGrowthRate; - ExecuteTransaction(exogenousAccount, thirdPillarAccount, "Compound Return Third Pillar", finalDateAsDateTime, thirdPillarCompoundedReturn); - - // savings quota: take share from current income account and move it to wealth - decimal newSavings = incomeAccount.Balance * SavingsQuota(finalDate, options, person); - - // savings are subject to wealth tax but are not deducted fr - ExecuteTransaction(exogenousAccount, wealthAccount, "Savings Quota", finalDateAsDateTime, newSavings); - - // take each account amount, calculate tax, and deduct it from wealth - var totalTaxAmount = - await CalculateIncomeAndWealthTaxAsync(finalDate.Year, person, incomeAccount.Balance, wealthAccount.Balance); - - ExecuteTransaction(wealthAccount, exogenousAccount, "Yearly Income and Wealth Tax", finalDateAsDateTime, totalTaxAmount); - - // Clear income account as it begin at 0 in the new year - ExecuteTransaction(incomeAccount, exogenousAccount, "Clear income account", finalDateAsDateTime, incomeAccount.Balance); - - return currentAccounts; - } - - private async Task CalculateIncomeAndWealthTaxAsync( - int currentYear, MultiPeriodCalculatorPerson calculatorPerson, decimal income, decimal wealth) - { - TaxPerson taxPerson = new() - { - Name = calculatorPerson.Name, - CivilStatus = calculatorPerson.CivilStatus, - NumberOfChildren = calculatorPerson.NumberOfChildren, - ReligiousGroupType = calculatorPerson.ReligiousGroupType, - PartnerReligiousGroupType = calculatorPerson.PartnerReligiousGroupType, - TaxableWealth = wealth, - TaxableFederalIncome = income, - TaxableIncome = income - }; - - Either municipality = - await municipalityConnector.GetAsync(calculatorPerson.MunicipalityId, currentYear); - - Either result = await municipality - .BindAsync(m => _fullTaxCalculator.CalculateAsync(currentYear, m, taxPerson, true)); - - return result.Match( - Right: r => r.TotalTaxAmount, - Left: error => - { - _logger.LogError(error); - return decimal.Zero; - }); - } - - private async Task CalculateCapitalBenefitsTaxAsync( - int currentYear, MultiPeriodCalculatorPerson person, decimal amount) - { - CapitalBenefitTaxPerson taxPerson = new() - { - Name = person.Name, - CivilStatus = person.CivilStatus, - NumberOfChildren = person.NumberOfChildren, - ReligiousGroupType = person.ReligiousGroupType, - PartnerReligiousGroupType = person.PartnerReligiousGroupType, - TaxableCapitalBenefits = amount - }; - - Either municipality = - await municipalityConnector.GetAsync(person.MunicipalityId, currentYear); - - Either result = await municipality - .BindAsync(m => _capitalBenefitCalculator.CalculateAsync( - currentYear, m, taxPerson, true)); - - return result.Match( - Right: r => r.TotalTaxAmount, - Left: error => - { - _logger.LogError(error); - return decimal.Zero; - - }); - } - - private static void ExecuteTransaction( - ICashFlowAccount debitAccount, ICashFlowAccount creditAccount, string description, DateTime transactionDate, decimal amount) - { - if (amount == decimal.Zero) - { - return; - } - - AccountTransaction trxCreditAccount = - new($"{description}: inflow from {debitAccount.Name}", transactionDate, amount); - - creditAccount.Balance += amount; - creditAccount.Transactions.Add(trxCreditAccount); - - - AccountTransaction trxDebitAccount = - new($"{description}: outflow to {creditAccount.Name}", transactionDate, -amount); - - debitAccount.Balance -= amount; - debitAccount.Transactions.Add(trxDebitAccount); - } - - private decimal SavingsQuota(DateOnly dateOfValidity, MultiPeriodOptions options, MultiPeriodCalculatorPerson person) - { - DateOnly retirementDate = DateOnly.FromDateTime(person.DateOfBirth.GetRetirementDate(person.Gender)); - - if (dateOfValidity < retirementDate) - { - return options.SavingsQuota; - } - - return decimal.Zero; - } -} diff --git a/src/Calculators.CashFlow/TaxScenarioCalculator.cs b/src/Calculators.CashFlow/TaxScenarioCalculator.cs deleted file mode 100644 index 5cb55393..00000000 --- a/src/Calculators.CashFlow/TaxScenarioCalculator.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Calculators.CashFlow.Models; -using LanguageExt; -using PensionCoach.Tools.CommonTypes.MultiPeriod; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxComparison; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Actions; - -namespace Calculators.CashFlow; - -public class TaxScenarioCalculator : ITaxScenarioCalculator -{ - private readonly IMultiPeriodCashFlowCalculator multiPeriodCashFlowCalculator; - private readonly IMunicipalityConnector municipalityResolver; - - public TaxScenarioCalculator( - IMultiPeriodCashFlowCalculator multiPeriodCashFlowCalculator, IMunicipalityConnector municipalityResolver) - { - this.multiPeriodCashFlowCalculator = multiPeriodCashFlowCalculator; - this.municipalityResolver = municipalityResolver; - } - - public async Task> TransferInCapitalBenefitsAsync( - int startingYear, int bfsMunicipalityId, TaxPerson person, TransferInCapitalBenefitsScenarioModel scenarioModel) - { - var birthdate = new DateTime(1969, 3, 17); - - MultiPeriodOptions options = new(); - options.CapitalBenefitsNetGrowthRate = scenarioModel.NetReturnCapitalBenefits; - options.WealthNetGrowthRate = scenarioModel.NetReturnWealth; - options.SavingsQuota = decimal.Zero; - - CashFlowDefinitionHolder cashFlowDefinitionHolder = CreateScenarioDefinitions(); - - Either municipalityData = - await municipalityResolver.GetAsync(bfsMunicipalityId, startingYear); - - Either scenarioResult = await municipalityData - .BindAsync(m => - multiPeriodCashFlowCalculator.CalculateAsync(startingYear, 0,GetPerson(m, birthdate), cashFlowDefinitionHolder, options)); - - CashFlowDefinitionHolder benchmarkDefinitions = CreateBenchmarkDefinitions(); - - Either benchmarkResult = await municipalityData - .BindAsync(m => - multiPeriodCashFlowCalculator.CalculateAsync(startingYear, 0,GetPerson(m, birthdate), benchmarkDefinitions, options)); - - var benchmarkSeriesResult = benchmarkResult - .Map(r => r.Accounts - .Where(a => a.AccountType is AccountType.Wealth or AccountType.OccupationalPension)); - - var scenarioSeriesResult = scenarioResult - .Map(r => r.Accounts - .Where(a => a.AccountType is AccountType.Wealth or AccountType.OccupationalPension)); - - return from b in benchmarkSeriesResult - from s in scenarioSeriesResult - select CalculateDelta(b.ToList(), s.ToList()); - - CapitalBenefitsTransferInResult CalculateDelta( - IReadOnlyCollection benchmark, - IReadOnlyCollection scenario) - { - var sumBenchmarkSeries = - from w in benchmark.Where(item => item.AccountType == AccountType.Wealth) - from p in benchmark.Where(item => item.AccountType == AccountType.OccupationalPension) - where w.Year == p.Year - select new { Sum = w.Amount + p.Amount, w.Year }; - - var sumScenarioSeries = - from w in scenario.Where(item => item.AccountType == AccountType.Wealth) - from p in scenario.Where(item => item.AccountType == AccountType.OccupationalPension) - where w.Year == p.Year - select new { Sum = w.Amount + p.Amount, w.Year }; - - IEnumerable deltaResults = from bSum in sumBenchmarkSeries - from sSum in sumScenarioSeries - where bSum.Year == sSum.Year - select new SinglePeriodCalculationResult - { - Amount = sSum.Sum - bSum.Sum, - Year = bSum.Year, - AccountType = AccountType.Exogenous - }; - - List deltaSeries = deltaResults.ToList(); - - return new CapitalBenefitsTransferInResult - { - StartingYear = Math.Min(benchmark.Min(a => a.Year), scenario.Min(a => a.Year)), - NumberOfPeriods = deltaSeries.Count, - BenchmarkSeries = benchmark.ToList(), - ScenarioSeries = scenario.ToList(), - DeltaSeries = deltaSeries - }; - } - - CashFlowDefinitionHolder CreateBenchmarkDefinitions() - { - CashFlowDefinitionHolder holder = new CashFlowDefinitionHolder(); - holder.Composites = CreateDefaultComposites(person, scenarioModel).ToList(); - holder.CashFlowActions = GetClearAccountAction(scenarioModel).ToList(); - - return holder; - } - - CashFlowDefinitionHolder CreateScenarioDefinitions() - { - CashFlowDefinitionHolder holder = new CashFlowDefinitionHolder(); - holder.Composites = CreateTransferInDefinitions(scenarioModel) - .Concat(CreateDefaultComposites(person, scenarioModel)) - .ToList(); - holder.CashFlowActions = GetClearAccountAction(scenarioModel).ToList(); - - return holder; - } - - MultiPeriodCalculatorPerson GetPerson(MunicipalityModel municipality, DateTime birthday) - { - return new MultiPeriodCalculatorPerson - { - CivilStatus = person.CivilStatus, - DateOfBirth = birthday, - Gender = Gender.Male, - Name = "Purchase Scenario", - Canton = municipality.Canton, - MunicipalityId = municipality.BfsNumber, - Income = person.TaxableIncome, - Wealth = person.TaxableWealth, - CapitalBenefits = (0, 0), - NumberOfChildren = 0, - PartnerReligiousGroupType = person.PartnerReligiousGroupType, - ReligiousGroupType = person.ReligiousGroupType, - }; - } - } - - private IEnumerable GetClearAccountAction(TransferInCapitalBenefitsScenarioModel scenarioModel) - { - if (scenarioModel is { WithCapitalBenefitWithdrawal: false }) - { - yield break; - } - - foreach (var withdrawal in scenarioModel.Withdrawals) - { - DateTime withdrawalDate = new DateTime(withdrawal.DateOfTransferIn.Year, 12, 31); - - yield return new DynamicTransferAccountAction - { - Header = new CashFlowHeader { Id = Guid.NewGuid().ToString(), Name = "Capital Benefit Withdrawal" }, - DateOfProcess = withdrawalDate, - TransferRatio = withdrawal.Amount, - Flow = new FlowPair(AccountType.OccupationalPension, AccountType.Wealth), - IsTaxable = true, - TaxType = TaxType.CapitalBenefits - }; - } - } - - private static IEnumerable CreateTransferInDefinitions(TransferInCapitalBenefitsScenarioModel scenarioModel) - { - // one purchase transfer-in for each single transfer-in - // as they might not be continuously - foreach (var singleTransferIn in scenarioModel.TransferIns) - { - yield return new PurchaseInsuranceYearsPaymentsDefinition - { - DateOfStart = singleTransferIn.DateOfTransferIn, - NetGrowthRate = scenarioModel.NetReturnCapitalBenefits, - NumberOfInvestments = 1, - YearlyAmount = singleTransferIn.Amount, - }; - } - } - - private static IEnumerable CreateDefaultComposites( - TaxPerson person, TransferInCapitalBenefitsScenarioModel scenarioModel) - { - DateTime finalSalaryPaymentDate = scenarioModel.TransferIns.Max(t => t.DateOfTransferIn).AddYears(1); - - DateTime finalDate = scenarioModel.WithCapitalBenefitWithdrawal - ? scenarioModel.Withdrawals.Min(w => w.DateOfTransferIn) - : finalSalaryPaymentDate; - - yield return new SalaryPaymentsDefinition - { - YearlyAmount = person.TaxableIncome, - DateOfEndOfPeriod = finalDate, - NetGrowthRate = decimal.Zero, - }; - - yield return new SetupAccountDefinition - { - InitialOccupationalPensionAssets = decimal.Zero, - InitialWealth = person.TaxableWealth - }; - - yield return new FixedTransferAmountDefinition - { - DateOfProcess = new DateTime(finalDate.Year, 1, 1), - Flow = new FlowPair(AccountType.Exogenous, AccountType.OccupationalPension), - TransferAmount = scenarioModel.CapitalBenefitsBeforeWithdrawal, - TaxType = TaxType.Undefined, - IsTaxable = false, - }; - } -} diff --git a/src/Calculators.Cashflow.Abstractions/Accounts/AccountTransaction.cs b/src/Calculators.Cashflow.Abstractions/Accounts/AccountTransaction.cs deleted file mode 100644 index 5a18e5db..00000000 --- a/src/Calculators.Cashflow.Abstractions/Accounts/AccountTransaction.cs +++ /dev/null @@ -1,5 +0,0 @@ -using System; - -namespace Calculators.CashFlow.Accounts; - -public record AccountTransaction(string Description, DateTime ValutaDate, decimal Amount); diff --git a/src/Calculators.Cashflow.Abstractions/Accounts/ExogenousAccount.cs b/src/Calculators.Cashflow.Abstractions/Accounts/ExogenousAccount.cs deleted file mode 100644 index 4db5cebd..00000000 --- a/src/Calculators.Cashflow.Abstractions/Accounts/ExogenousAccount.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Calculators.CashFlow.Accounts; - -public class ExogenousAccount : ICashFlowAccount -{ - public Guid Id { get; set; } - - public string Name { get; set; } - - public decimal Balance { get; set; } - - public decimal InterestRate { get; set; } - - public List Transactions { get; set; } = new(); -} diff --git a/src/Calculators.Cashflow.Abstractions/Accounts/ICashFlowAccount.cs b/src/Calculators.Cashflow.Abstractions/Accounts/ICashFlowAccount.cs deleted file mode 100644 index 25b9a1f0..00000000 --- a/src/Calculators.Cashflow.Abstractions/Accounts/ICashFlowAccount.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Calculators.CashFlow.Accounts; - -public interface ICashFlowAccount -{ - Guid Id { get; set; } - - string Name { get; set; } - - decimal Balance { get; set; } - - List Transactions { get; set; } -} diff --git a/src/Calculators.Cashflow.Abstractions/Accounts/IncomeAccount.cs b/src/Calculators.Cashflow.Abstractions/Accounts/IncomeAccount.cs deleted file mode 100644 index 37d3611d..00000000 --- a/src/Calculators.Cashflow.Abstractions/Accounts/IncomeAccount.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Calculators.CashFlow.Accounts; - -public class IncomeAccount : ICashFlowAccount -{ - public Guid Id { get; set; } - - public string Name { get; set; } - - public decimal Balance { get; set; } - - public decimal InterestRate { get; set; } - - public List Transactions { get; set; } = new(); -} diff --git a/src/Calculators.Cashflow.Abstractions/Accounts/OccupationalPensionAccount.cs b/src/Calculators.Cashflow.Abstractions/Accounts/OccupationalPensionAccount.cs deleted file mode 100644 index 55479b26..00000000 --- a/src/Calculators.Cashflow.Abstractions/Accounts/OccupationalPensionAccount.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Calculators.CashFlow.Accounts; - -public class OccupationalPensionAccount : ICashFlowAccount -{ - public Guid Id { get; set; } - - public string Name { get; set; } - - public decimal Balance { get; set; } - - public decimal NetGrowthRate { get; set; } - - public List Transactions { get; set; } = new(); -} diff --git a/src/Calculators.Cashflow.Abstractions/Accounts/ThirdPillarAccount.cs b/src/Calculators.Cashflow.Abstractions/Accounts/ThirdPillarAccount.cs deleted file mode 100644 index 89b3756a..00000000 --- a/src/Calculators.Cashflow.Abstractions/Accounts/ThirdPillarAccount.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Calculators.CashFlow.Accounts; - -public class ThirdPillarAccount : ICashFlowAccount -{ - public Guid Id { get; set; } - - public string Name { get; set; } - - public decimal Balance { get; set; } - - public decimal NetGrowthRate { get; set; } - - public List Transactions { get; set; } = new(); -} diff --git a/src/Calculators.Cashflow.Abstractions/Accounts/WealthAccount.cs b/src/Calculators.Cashflow.Abstractions/Accounts/WealthAccount.cs deleted file mode 100644 index 3a05c3b7..00000000 --- a/src/Calculators.Cashflow.Abstractions/Accounts/WealthAccount.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Calculators.CashFlow.Accounts; - -public class WealthAccount : ICashFlowAccount -{ - public Guid Id { get; set; } - - public string Name { get; set; } - - public decimal Balance { get; set; } - - public decimal NetGrowthRate { get; set; } - - public List Transactions { get; set; } = new(); -} diff --git a/src/Calculators.Cashflow.Abstractions/Calculators.CashFlow.Abstractions.csproj b/src/Calculators.Cashflow.Abstractions/Calculators.CashFlow.Abstractions.csproj deleted file mode 100644 index 9c2c21b4..00000000 --- a/src/Calculators.Cashflow.Abstractions/Calculators.CashFlow.Abstractions.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net7.0 - Calculators.CashFlow.Abstractions - Calculators.CashFlow - - - - - - - - diff --git a/src/Calculators.Cashflow.Abstractions/IMultiPeriodCashFlowCalculator.cs b/src/Calculators.Cashflow.Abstractions/IMultiPeriodCashFlowCalculator.cs deleted file mode 100644 index 175b0475..00000000 --- a/src/Calculators.Cashflow.Abstractions/IMultiPeriodCashFlowCalculator.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Threading.Tasks; -using Calculators.CashFlow.Models; -using LanguageExt; -using PensionCoach.Tools.CommonTypes.MultiPeriod; - -namespace Calculators.CashFlow; - -public interface IMultiPeriodCashFlowCalculator -{ - /// - /// Calculates how taxable assets evolves over time. - /// Takes a list of cash-flow definitions sums them up by groups of target/source pairs along the timeline. - /// Then, iterates along the timeline and calculates for a given year: - /// 1. adds cash-flow amount for the given year to its associated asset type - /// 2. calculates tax amount for each target asset type - /// 3. deduct tax amount from asset values - /// 4. move flow asset types to its stock asset type (ie. salary does not stay after paying tax for it but - /// is moved to taxable wealth). - /// - /// - /// The minimum number of periods to simulate. - /// - /// - /// - /// - Task> CalculateAsync( - int startingYear, - int miniumNumberOfPeriods, - MultiPeriodCalculatorPerson person, - CashFlowDefinitionHolder cashFlowDefinitionHolder, - MultiPeriodOptions options); - -} diff --git a/src/Calculators.Cashflow.Abstractions/ITaxScenarioCalculator.cs b/src/Calculators.Cashflow.Abstractions/ITaxScenarioCalculator.cs deleted file mode 100644 index f5e561e6..00000000 --- a/src/Calculators.Cashflow.Abstractions/ITaxScenarioCalculator.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading.Tasks; -using Calculators.CashFlow.Models; -using LanguageExt; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxComparison; - -namespace Calculators.CashFlow; - -public interface ITaxScenarioCalculator -{ - /// - /// - /// - /// - /// - Task> TransferInCapitalBenefitsAsync( - int startingYear, - int bfsMunicipalityId, - TaxPerson person, - TransferInCapitalBenefitsScenarioModel scenarioModel); -} diff --git a/src/Calculators.Cashflow.Abstractions/Models/CashFlowModel.cs b/src/Calculators.Cashflow.Abstractions/Models/CashFlowModel.cs deleted file mode 100644 index 9d4a0446..00000000 --- a/src/Calculators.Cashflow.Abstractions/Models/CashFlowModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; - -namespace Calculators.CashFlow.Models -{ - public record CashFlowModel( - DateOnly DateOfProcess, - decimal Amount, - AccountType Source, - AccountType Target, - bool IsTaxable, - TaxType TaxType); -} diff --git a/src/Calculators.Cashflow.Abstractions/Models/MultiPeriodCalculationResult.cs b/src/Calculators.Cashflow.Abstractions/Models/MultiPeriodCalculationResult.cs deleted file mode 100644 index 8c6af95d..00000000 --- a/src/Calculators.Cashflow.Abstractions/Models/MultiPeriodCalculationResult.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using Calculators.CashFlow.Accounts; -using PensionCoach.Tools.CommonTypes.MultiPeriod; - -namespace Calculators.CashFlow.Models; - -public record MultiPeriodCalculationResult -{ - public int StartingYear { get; set; } - - public int NumberOfPeriods { get; set; } - - public IEnumerable Accounts{ get; set; } - - public ExogenousAccount ExogenousAccount { get; set; } - - public IncomeAccount IncomeAccount { get; set; } - - public WealthAccount WealthAccount { get; set; } - - public OccupationalPensionAccount OccupationalPensionAccount { get; set; } - - public ThirdPillarAccount ThirdPillarAccount { get; set; } -} diff --git a/src/Calculators.Cashflow.Abstractions/Models/MultiPeriodCalculatorPerson.cs b/src/Calculators.Cashflow.Abstractions/Models/MultiPeriodCalculatorPerson.cs deleted file mode 100644 index 00d120e2..00000000 --- a/src/Calculators.Cashflow.Abstractions/Models/MultiPeriodCalculatorPerson.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using PensionCoach.Tools.CommonTypes; - -namespace Calculators.CashFlow.Models -{ - public record MultiPeriodCalculatorPerson - { - public string Name { get; set; } - - public DateTime DateOfBirth { get; set; } - - public Gender Gender { get; set; } - - public CivilStatus CivilStatus { get; set; } - - public int NumberOfChildren { get; set; } - - public ReligiousGroupType ReligiousGroupType { get; set; } - - public ReligiousGroupType? PartnerReligiousGroupType { get; set; } - - public Canton Canton { get; set; } - - public int MunicipalityId { get; set; } - - public decimal Income { get; set; } - - public decimal Wealth { get; set; } - - public (decimal Pillar3a, decimal PensionPlan) CapitalBenefits { get; set; } - - - } -} diff --git a/src/CommonTypes/AccountType.cs b/src/CommonTypes/AccountType.cs deleted file mode 100644 index 383f11b8..00000000 --- a/src/CommonTypes/AccountType.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Text.Json.Serialization; - -namespace PensionCoach.Tools.CommonTypes -{ - [JsonConverter(typeof(JsonStringEnumConverter))] - public enum AccountType - { - Exogenous, - Wealth, - Income, - - /// - /// Capital benefit assets in second pillar account (BVG Vorsorgegelder, FZP) which - /// are subject to capital benefits tax scheme. - /// - OccupationalPension, - - /// - /// Capital benefit assets in third pillar account (3a) which - /// are subject to capital benefits tax scheme. - /// - ThirdPillar - } -} diff --git a/src/CommonTypes/Bvg/BvgCalculationRequest.cs b/src/CommonTypes/Bvg/BvgCalculationRequest.cs new file mode 100644 index 00000000..d7151734 --- /dev/null +++ b/src/CommonTypes/Bvg/BvgCalculationRequest.cs @@ -0,0 +1,25 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Domain.Enums; + +namespace PensionCoach.Tools.CommonTypes.Bvg; + +public class BvgCalculationRequest +{ + [MaxLength(50)] + public string Name { get; set; } + + [Range(typeof(int), "1970", "2099", ErrorMessage = "Valid calculation years start from 1970")] + public int CalculationYear { get; set; } + + [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] + public decimal RetirementCapitalEndOfYear { get; set; } + + [Range(typeof(DateTime), "1/1/1900", "1/1/2099", ErrorMessage = "No valid birthdate")] + public DateTime DateOfBirth { get; set; } + + [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] + public decimal Salary { get; set; } + + public Gender Gender { get; set; } +} diff --git a/src/CommonTypes/Canton.cs b/src/CommonTypes/Canton.cs deleted file mode 100644 index 3eb99627..00000000 --- a/src/CommonTypes/Canton.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Text.Json.Serialization; - -namespace PensionCoach.Tools.CommonTypes; - -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum Canton -{ - /// - /// Undefined is not a valid and is treated as an error - /// - Undefined = 0, - - AG = 1, - - AI = 2, - - AR = 3, - - /// - /// Bern - /// - BE = 4, - - BL = 5, - - BS = 6, - - FR = 7, - - GE = 8, - - GL = 9, - - GR = 10, - - JU = 11, - - LU = 12, - - NE = 13, - - NW = 14, - - OW = 15, - - SH = 16, - - /// - /// Solothurn - /// - SO = 17, - - /// - /// St.Gallen - /// - SG = 18, - - SZ = 19, - - TI = 20, - - TG = 21, - - UR = 22, - - VD = 23, - - VS = 24, - - ZG = 25, - - /// - /// Zürich - /// - ZH = 26, -} diff --git a/src/CommonTypes/CommonTypes.csproj b/src/CommonTypes/CommonTypes.csproj index 00120365..f3209122 100644 --- a/src/CommonTypes/CommonTypes.csproj +++ b/src/CommonTypes/CommonTypes.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 PensionCoach.Tools.CommonTypes PensionCoach.Tools.CommonTypes @@ -9,4 +9,8 @@ + + + + diff --git a/src/CommonTypes/DateOnlyJsonConverter.cs b/src/CommonTypes/DateOnlyJsonConverter.cs deleted file mode 100644 index 8d16e7e5..00000000 --- a/src/CommonTypes/DateOnlyJsonConverter.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Globalization; -using Newtonsoft.Json; - -namespace PensionCoach.Tools.CommonTypes; - -public class DateOnlyJsonConverter : JsonConverter -{ - private const string Format = "yyyy-MM-dd"; - - public override DateOnly ReadJson(JsonReader reader, - Type objectType, - DateOnly existingValue, - bool hasExistingValue, - JsonSerializer serializer) => - DateOnly.ParseExact((string)reader.Value ?? string.Empty, Format, CultureInfo.InvariantCulture); - - public override void WriteJson(JsonWriter writer, DateOnly value, JsonSerializer serializer) => - writer.WriteValue(value.ToString(Format, CultureInfo.InvariantCulture)); -} diff --git a/src/CommonTypes/Features/PensionVersusCapital/PensionVersusCapitalRequest.cs b/src/CommonTypes/Features/PensionVersusCapital/PensionVersusCapitalRequest.cs new file mode 100644 index 00000000..a3569952 --- /dev/null +++ b/src/CommonTypes/Features/PensionVersusCapital/PensionVersusCapitalRequest.cs @@ -0,0 +1,17 @@ +using Domain.Models.Tax; + +namespace PensionCoach.Tools.CommonTypes.Features.PensionVersusCapital; + +public record PensionVersusCapitalRequest +{ + public int CalculationYear { get; set; } + public int MunicipalityId { get; set; } + public decimal RetirementPension { get; set; } + public decimal RetirementCapital { get; set; } + + public decimal YearlyConsumptionAmount { get; set; } + + public decimal NetWealthReturn { get; set; } + + public TaxPerson TaxPerson { get; set; } +} diff --git a/src/CommonTypes/FrequencyType.cs b/src/CommonTypes/FrequencyType.cs deleted file mode 100644 index a88d6e7c..00000000 --- a/src/CommonTypes/FrequencyType.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Text.Json.Serialization; - -namespace PensionCoach.Tools.CommonTypes; - -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum FrequencyType -{ - Daily, - Monthly, - Yearly -} diff --git a/src/CommonTypes/MultiPeriod/Actions/ChangeResidenceAction.cs b/src/CommonTypes/MultiPeriod/Actions/ChangeResidenceAction.cs deleted file mode 100644 index 0cc2b9e7..00000000 --- a/src/CommonTypes/MultiPeriod/Actions/ChangeResidenceAction.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; - -namespace PensionCoach.Tools.CommonTypes.MultiPeriod.Actions; - -/// -/// Simulates the change of the residence. A change always happens at the end of a period. -/// -public record ChangeResidenceAction : ICompositeCashFlowDefinition -{ - /// - /// Gets or sets the header properties - /// - public CashFlowHeader Header { get; set; } - - /// - /// Gets the date of change of residence. - /// - /// - /// The change of residence at this year. - /// - public DateTime DateOfProcess { get; set; } - - /// - /// Gets the destination municipality identifier. - /// - /// - /// The destination municipality identifier. - /// - public int DestinationMunicipalityId { get; set; } - - /// - /// Gets or sets the destination canton. - /// - /// - /// The destination canton. - /// - public Canton DestinationCanton { get; set; } - - /// - /// Gets the change cost. The cost to change of the residence is deducted from wealth. - /// - /// - /// The change cost. - /// - public decimal ChangeCost { get; set; } -} diff --git a/src/CommonTypes/MultiPeriod/Actions/DynamicTransferAccountAction.cs b/src/CommonTypes/MultiPeriod/Actions/DynamicTransferAccountAction.cs deleted file mode 100644 index e9168c11..00000000 --- a/src/CommonTypes/MultiPeriod/Actions/DynamicTransferAccountAction.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; -using PensionCoach.Tools.CommonTypes.Tax; - -namespace PensionCoach.Tools.CommonTypes.MultiPeriod.Actions; - -public record DynamicTransferAccountAction : IDynamicCashFlowDefinition -{ - /// - /// Gets or sets the header properties - /// - public CashFlowHeader Header { get; set; } - - /// - /// Gets or sets the date of transfer. - /// - /// - /// The date of transfer. - /// - public DateTime DateOfProcess { get; set; } - - /// - /// How much in relative terms is transferred from the source to the target account. - /// - public decimal TransferRatio { get; set; } = decimal.One; - - public FlowPair Flow { get; set; } - - public bool IsTaxable { get; set; } - - public TaxType TaxType { get; set; } -} diff --git a/src/CommonTypes/MultiPeriod/Actions/OrdinaryRetirementAction.cs b/src/CommonTypes/MultiPeriod/Actions/OrdinaryRetirementAction.cs deleted file mode 100644 index 5ad5e915..00000000 --- a/src/CommonTypes/MultiPeriod/Actions/OrdinaryRetirementAction.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; - -namespace PensionCoach.Tools.CommonTypes.MultiPeriod.Actions; - -public record OrdinaryRetirementAction : ICompositeCashFlowDefinition -{ - /// - /// Gets or sets the header properties - /// - public CashFlowHeader Header { get; set; } - - public DateTime DateOfProcess { get; set; } - - public int NumberOfPeriods { get; set; } - - public decimal CapitalOptionFactor { get; set; } = decimal.Zero; - - public decimal RetirementPension { get; set; } - - public decimal CapitalConsumptionAmountPerYear { get; set; } - - public decimal AhvPensionAmount { get; set; } -} diff --git a/src/CommonTypes/MultiPeriod/CapitalBenefitsTransferInResponse.cs b/src/CommonTypes/MultiPeriod/CapitalBenefitsTransferInResponse.cs deleted file mode 100644 index 0d105af1..00000000 --- a/src/CommonTypes/MultiPeriod/CapitalBenefitsTransferInResponse.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; - -namespace PensionCoach.Tools.CommonTypes.MultiPeriod -{ - public class CapitalBenefitsTransferInResponse - { - public int StartingYear { get; set; } - - public int NumberOfPeriods { get; set; } - - public IEnumerable DeltaSeries { get; set; } - - public IEnumerable BenchmarkSeries { get; set; } - - public IEnumerable ScenarioSeries { get; set; } - } -} diff --git a/src/CommonTypes/MultiPeriod/CashFlowDefinitionHolder.cs b/src/CommonTypes/MultiPeriod/CashFlowDefinitionHolder.cs deleted file mode 100644 index 68887793..00000000 --- a/src/CommonTypes/MultiPeriod/CashFlowDefinitionHolder.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Actions; - -namespace PensionCoach.Tools.CommonTypes.MultiPeriod; - -public record CashFlowDefinitionHolder -{ - /// - /// Gets the change residence actions. - /// - /// - /// The change residence actions. - /// - public IReadOnlyCollection Composites { get; set; } = - Array.Empty().ToList(); - - /// - /// Gets the generic cash flow definitions. - /// - /// - /// The generic cash flow definitions. - /// - public IReadOnlyCollection StaticGenericCashFlowDefinitions { get; set; } = - Array.Empty().ToList(); - - /// - /// Collection of cash-flow actions. - /// - public IReadOnlyCollection CashFlowActions { get; set; } = - Array.Empty().ToList(); - - /// - /// Gets the change residence actions. - /// - /// - /// The change residence actions. - /// - public IReadOnlyCollection ChangeResidenceActions { get; set; } = - Array.Empty().ToList(); -} diff --git a/src/CommonTypes/MultiPeriod/CashFlowDefinitionRequest.cs b/src/CommonTypes/MultiPeriod/CashFlowDefinitionRequest.cs index 9306bbb8..3db2e6df 100644 --- a/src/CommonTypes/MultiPeriod/CashFlowDefinitionRequest.cs +++ b/src/CommonTypes/MultiPeriod/CashFlowDefinitionRequest.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Actions; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; +using Domain.Models.MultiPeriod.Actions; +using Domain.Models.MultiPeriod.Definitions; namespace PensionCoach.Tools.CommonTypes.MultiPeriod; diff --git a/src/CommonTypes/MultiPeriod/Definitions/CashFlowHeader.cs b/src/CommonTypes/MultiPeriod/Definitions/CashFlowHeader.cs deleted file mode 100644 index 8ec8513d..00000000 --- a/src/CommonTypes/MultiPeriod/Definitions/CashFlowHeader.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; - -public record CashFlowHeader -{ - /// - /// Gets or sets the identifier. - /// - /// - /// The identifier. - /// - public string Id { get; set; } - - /// - /// Gets the name. - /// - /// - /// The name. - /// - public string Name { get; set; } -} diff --git a/src/CommonTypes/MultiPeriod/Definitions/DynamicGenericCashFlowDefinition.cs b/src/CommonTypes/MultiPeriod/Definitions/DynamicGenericCashFlowDefinition.cs deleted file mode 100644 index 59d118a1..00000000 --- a/src/CommonTypes/MultiPeriod/Definitions/DynamicGenericCashFlowDefinition.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using PensionCoach.Tools.CommonTypes.Tax; - -namespace PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; - -public record DynamicGenericCashFlowDefinition : IStaticCashFlowDefinition -{ - /// - /// Gets or sets the header properties - /// - public CashFlowHeader Header { get; set; } - - /// - /// Gets or sets the date of process. - /// - /// - /// The date of process. - /// - public DateTime DateOfProcess { get; set; } - - public int NumberOfPeriods { get; set; } - - /// - /// Fraction of the available capital which is transferred from the source to the target account. - /// - public decimal TransferQuota { get; set; } - - public FrequencyType Frequency { get; set; } - - /// - /// Gets the flow. Source account is cleared and moved to target account. - /// - /// - /// The flow. - /// - public FlowPair Flow { get; set; } - - public bool IsTaxable { get; set; } - - public TaxType TaxType { get; set; } -} diff --git a/src/CommonTypes/MultiPeriod/Definitions/FixedTransferAmountDefinition.cs b/src/CommonTypes/MultiPeriod/Definitions/FixedTransferAmountDefinition.cs deleted file mode 100644 index 87e36a9f..00000000 --- a/src/CommonTypes/MultiPeriod/Definitions/FixedTransferAmountDefinition.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using PensionCoach.Tools.CommonTypes.Tax; - -namespace PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; - -public record FixedTransferAmountDefinition : ICompositeCashFlowDefinition -{ - /// - /// Gets or sets the header properties - /// - public CashFlowHeader Header { get; set; } - - /// - /// Gets or sets the date of transfer. - /// - /// - /// The date of transfer. - /// - public DateTime DateOfProcess { get; set; } - - /// - /// Amount which is transferred from the source to the target - /// - public decimal TransferAmount { get; set; } - - public FlowPair Flow { get; set; } - - public bool IsTaxable { get; set; } - - public TaxType TaxType { get; set; } -} diff --git a/src/CommonTypes/MultiPeriod/Definitions/PurchaseInsuranceYearsPaymentsDefinition.cs b/src/CommonTypes/MultiPeriod/Definitions/PurchaseInsuranceYearsPaymentsDefinition.cs deleted file mode 100644 index fbb4363b..00000000 --- a/src/CommonTypes/MultiPeriod/Definitions/PurchaseInsuranceYearsPaymentsDefinition.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; - -namespace PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; - -public record PurchaseInsuranceYearsPaymentsDefinition : ICompositeCashFlowDefinition -{ - /// - /// Gets or sets the date of start. - /// - /// - /// The date of start. - /// - public DateTime DateOfStart { get; set; } - - /// - /// Gets or sets the net growth rate. - /// - /// - /// The net growth rate. - /// - public decimal NetGrowthRate { get; set; } - - /// - /// Gets or sets the yearly amount. - /// - public decimal YearlyAmount { get; set; } - - /// - /// Gets or sets the number of investments (one per year). - /// - public int NumberOfInvestments { get; set; } -} diff --git a/src/CommonTypes/MultiPeriod/Definitions/RelativeTransferAmountDefinition.cs b/src/CommonTypes/MultiPeriod/Definitions/RelativeTransferAmountDefinition.cs deleted file mode 100644 index 68ade496..00000000 --- a/src/CommonTypes/MultiPeriod/Definitions/RelativeTransferAmountDefinition.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using PensionCoach.Tools.CommonTypes.Tax; - -namespace PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; - -public record RelativeTransferAmountDefinition : ICompositeCashFlowDefinition -{ - /// - /// Gets or sets the header properties - /// - public CashFlowHeader Header { get; set; } - - /// - /// Gets or sets the date of transfer. - /// - /// - /// The date of transfer. - /// - public DateTime DateOfProcess { get; set; } - - /// - /// Amount which is transferred from the source to the target - /// - public decimal TransferRatio { get; set; } - - public FlowPair Flow { get; set; } - - public bool IsTaxable { get; set; } - - public TaxType TaxType { get; set; } -} diff --git a/src/CommonTypes/MultiPeriod/Definitions/SalaryPaymentsDefinition.cs b/src/CommonTypes/MultiPeriod/Definitions/SalaryPaymentsDefinition.cs deleted file mode 100644 index ec00203c..00000000 --- a/src/CommonTypes/MultiPeriod/Definitions/SalaryPaymentsDefinition.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; - -public record SalaryPaymentsDefinition : ICompositeCashFlowDefinition -{ - /// - /// Gets or sets the net growth rate. - /// - /// - /// The net growth rate. - /// - public decimal NetGrowthRate { get; set; } - - /// - /// Gets or sets the yearly investment amount. - /// - public decimal YearlyAmount { get; set; } - - /// - /// Gets or sets the end of period. It marks not the final salary payment - /// but is used the calculate the pro-rated yearly amount in the last payment period. - /// - public DateTime DateOfEndOfPeriod { get; set; } -} diff --git a/src/CommonTypes/MultiPeriod/Definitions/SetupAccountDefinition.cs b/src/CommonTypes/MultiPeriod/Definitions/SetupAccountDefinition.cs deleted file mode 100644 index 4d6ae9e1..00000000 --- a/src/CommonTypes/MultiPeriod/Definitions/SetupAccountDefinition.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; - -public record SetupAccountDefinition : ICompositeCashFlowDefinition -{ - /// - /// Gets or sets the initial wealth. - /// - /// - /// The initial wealth. - /// - public decimal InitialWealth { get; set; } - - /// - /// Gets or sets the initial occupational pension assets (Berufliche Vorsorge). - /// - public decimal InitialOccupationalPensionAssets { get; set; } - - /// - /// Gets or sets the initial third pillar assets (3A Konto). - /// - public decimal InitialThirdPillarAssets { get; set; } -} diff --git a/src/CommonTypes/MultiPeriod/Definitions/StaticGenericCashFlowDefinition.cs b/src/CommonTypes/MultiPeriod/Definitions/StaticGenericCashFlowDefinition.cs deleted file mode 100644 index 540cb676..00000000 --- a/src/CommonTypes/MultiPeriod/Definitions/StaticGenericCashFlowDefinition.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using PensionCoach.Tools.CommonTypes.Tax; - -namespace PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; - -public record StaticGenericCashFlowDefinition : IStaticCashFlowDefinition -{ - /// - /// Gets or sets the header properties - /// - public CashFlowHeader Header { get; set; } - - /// - /// Gets or sets the date of process. - /// - /// - /// The date of process. - /// - public DateTime DateOfProcess { get; set; } - - /// - /// Gets or sets the initial amount. - /// - /// - /// The initial amount. - /// - public decimal InitialAmount { get; set; } - - /// - /// Gets the recurring investment. - /// - /// - /// The recurring investment. - /// - public RecurringInvestment RecurringInvestment { get; set; } - - /// - /// Gets or sets the net growth rate. - /// - /// - /// The net growth rate. - /// - public decimal NetGrowthRate { get; set; } - - /// - /// Get or sets the investment period beginning with the begin year and repeating count times. - /// - /// - /// The investment period. - /// - public InvestmentPeriod InvestmentPeriod { get; set; } - - /// - /// Gets the flow. Source account is cleared and moved to target account. - /// - /// - /// The flow. - /// - public FlowPair Flow { get; set; } - - public bool IsTaxable { get; set; } - - public TaxType TaxType { get; set; } -} diff --git a/src/CommonTypes/MultiPeriod/Definitions/ThirdPillarPaymentsDefinition.cs b/src/CommonTypes/MultiPeriod/Definitions/ThirdPillarPaymentsDefinition.cs deleted file mode 100644 index 02f7c7f9..00000000 --- a/src/CommonTypes/MultiPeriod/Definitions/ThirdPillarPaymentsDefinition.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; - -namespace PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; - -public record ThirdPillarPaymentsDefinition : ICompositeCashFlowDefinition -{ - /// - /// Gets or sets the date of start. - /// - /// - /// The date of start. - /// - public DateTime DateOfStart { get; set; } - - /// - /// Gets or sets the net growth rate. - /// - /// - /// The net growth rate. - /// - public decimal NetGrowthRate { get; set; } - - /// - /// Gets or sets the yearly investment amount. - /// - public decimal YearlyAmount { get; set; } - - /// - /// Gets or sets the number of investments (one per year). - /// - public int NumberOfInvestments { get; set; } -} diff --git a/src/CommonTypes/MultiPeriod/ICashFlowDefinition.cs b/src/CommonTypes/MultiPeriod/ICashFlowDefinition.cs deleted file mode 100644 index 0299c95b..00000000 --- a/src/CommonTypes/MultiPeriod/ICashFlowDefinition.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; - -namespace PensionCoach.Tools.CommonTypes.MultiPeriod; - -public interface ICashFlowDefinition -{ - /// - /// Gets or sets the header properties - /// - public CashFlowHeader Header { get; set; } - - /// - /// Gets or sets the date of process. - /// - /// - /// The date of process. - /// - public DateTime DateOfProcess { get; set; } -} diff --git a/src/CommonTypes/MultiPeriod/ICompositeCashFlowDefinition.cs b/src/CommonTypes/MultiPeriod/ICompositeCashFlowDefinition.cs deleted file mode 100644 index 9a8739a7..00000000 --- a/src/CommonTypes/MultiPeriod/ICompositeCashFlowDefinition.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace PensionCoach.Tools.CommonTypes.MultiPeriod; - -public interface ICompositeCashFlowDefinition { } diff --git a/src/CommonTypes/MultiPeriod/IDynamicCashFlowDefinition.cs b/src/CommonTypes/MultiPeriod/IDynamicCashFlowDefinition.cs deleted file mode 100644 index 836897ef..00000000 --- a/src/CommonTypes/MultiPeriod/IDynamicCashFlowDefinition.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace PensionCoach.Tools.CommonTypes.MultiPeriod; - -public interface IDynamicCashFlowDefinition : ICashFlowDefinition { } diff --git a/src/CommonTypes/MultiPeriod/IStaticCashFlowDefinition.cs b/src/CommonTypes/MultiPeriod/IStaticCashFlowDefinition.cs deleted file mode 100644 index 4ba16df9..00000000 --- a/src/CommonTypes/MultiPeriod/IStaticCashFlowDefinition.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace PensionCoach.Tools.CommonTypes.MultiPeriod; - -public interface IStaticCashFlowDefinition : ICashFlowDefinition { } diff --git a/src/CommonTypes/MultiPeriod/MultiPeriodOptions.cs b/src/CommonTypes/MultiPeriod/MultiPeriodOptions.cs index d850a8b0..3e205b52 100644 --- a/src/CommonTypes/MultiPeriod/MultiPeriodOptions.cs +++ b/src/CommonTypes/MultiPeriod/MultiPeriodOptions.cs @@ -5,12 +5,15 @@ public class MultiPeriodOptions private const decimal DefaultSalaryGrowthRate = 0.01M; private const decimal DefaultWealthGrowthRate = decimal.Zero; private const decimal DefaultCapitalBenefitsGrowthRate = decimal.Zero; + private const decimal DefaultInvestmentGrowthRate = 0.02M; private const decimal DefaultSavingsQuota = 0.30M; public decimal SalaryNetGrowthRate { get; set; } = DefaultSalaryGrowthRate; public decimal WealthNetGrowthRate { get; set; } = DefaultWealthGrowthRate; + public decimal InvestmentNetGrowthRate { get; set; } = DefaultInvestmentGrowthRate; + public decimal CapitalBenefitsNetGrowthRate { get; set; } = DefaultCapitalBenefitsGrowthRate; /// diff --git a/src/CommonTypes/MultiPeriod/MultiPeriodRequest.cs b/src/CommonTypes/MultiPeriod/MultiPeriodRequest.cs index 7b1d5c5b..69a3821b 100644 --- a/src/CommonTypes/MultiPeriod/MultiPeriodRequest.cs +++ b/src/CommonTypes/MultiPeriod/MultiPeriodRequest.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; +using Domain.Enums; namespace PensionCoach.Tools.CommonTypes.MultiPeriod { diff --git a/src/CommonTypes/MultiPeriod/MultiPeriodResponse.cs b/src/CommonTypes/MultiPeriod/MultiPeriodResponse.cs index e64d429e..59ade166 100644 --- a/src/CommonTypes/MultiPeriod/MultiPeriodResponse.cs +++ b/src/CommonTypes/MultiPeriod/MultiPeriodResponse.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Domain.Models.MultiPeriod; namespace PensionCoach.Tools.CommonTypes.MultiPeriod { diff --git a/src/Calculators.Cashflow.Abstractions/Models/CapitalBenefitsTransferInResult.cs b/src/CommonTypes/MultiPeriod/ScenarioCalculationResponse.cs similarity index 71% rename from src/Calculators.Cashflow.Abstractions/Models/CapitalBenefitsTransferInResult.cs rename to src/CommonTypes/MultiPeriod/ScenarioCalculationResponse.cs index 4c46aadb..bf43c0d9 100644 --- a/src/Calculators.Cashflow.Abstractions/Models/CapitalBenefitsTransferInResult.cs +++ b/src/CommonTypes/MultiPeriod/ScenarioCalculationResponse.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; -using PensionCoach.Tools.CommonTypes.MultiPeriod; +using Domain.Models.MultiPeriod; -namespace Calculators.CashFlow.Models; +namespace PensionCoach.Tools.CommonTypes.MultiPeriod; -public record CapitalBenefitsTransferInResult +public class ScenarioCalculationResponse { public int StartingYear { get; set; } diff --git a/src/CommonTypes/Tax/ApplicationMode.cs b/src/CommonTypes/Tax/ApplicationMode.cs deleted file mode 100644 index b4ef5f9f..00000000 --- a/src/CommonTypes/Tax/ApplicationMode.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace PensionCoach.Tools.CommonTypes.Tax; - -public enum ApplicationMode -{ - Proprietary = 0, - Estv = 1, - Mock = 2 -} diff --git a/src/Tax.Tools.Comparison.Domain/CapitalBenefitTaxComparerRequest.cs b/src/CommonTypes/Tax/CapitalBenefitTaxRequest.cs similarity index 52% rename from src/Tax.Tools.Comparison.Domain/CapitalBenefitTaxComparerRequest.cs rename to src/CommonTypes/Tax/CapitalBenefitTaxRequest.cs index 1fe9a1cb..ab2ff80b 100644 --- a/src/Tax.Tools.Comparison.Domain/CapitalBenefitTaxComparerRequest.cs +++ b/src/CommonTypes/Tax/CapitalBenefitTaxRequest.cs @@ -1,23 +1,25 @@ using System.ComponentModel.DataAnnotations; -using PensionCoach.Tools.CommonTypes; +using Domain.Enums; -namespace PensionCoach.Tools.TaxComparison; +namespace PensionCoach.Tools.CommonTypes.Tax; -public class CapitalBenefitTaxComparerRequest +public class CapitalBenefitTaxRequest { [MaxLength(50)] public string Name { get; set; } + [Range(2018, 2099, ErrorMessage = "Valid tax years start from 2018")] + public int CalculationYear { get; set; } + public CivilStatus CivilStatus { get; set; } public ReligiousGroupType ReligiousGroup { get; set; } public ReligiousGroupType? PartnerReligiousGroup { get; set; } - /// - /// List of BFS number defines for which municipalites a comparison is calculated. - /// - public int[] BfsNumberList { get; set; } + [Required] + [Range(typeof(int), "0", "10000", ErrorMessage = "BFS number not valid")] + public int BfsMunicipalityId { get; set; } [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] public decimal TaxableBenefits { get; set; } diff --git a/src/CommonTypes/Tax/CapitalBenefitTaxResponse.cs b/src/CommonTypes/Tax/CapitalBenefitTaxResponse.cs new file mode 100644 index 00000000..96646673 --- /dev/null +++ b/src/CommonTypes/Tax/CapitalBenefitTaxResponse.cs @@ -0,0 +1,14 @@ +using Domain.Models.Tax; + +namespace PensionCoach.Tools.CommonTypes.Tax; + +public class CapitalBenefitTaxResponse +{ + public string Name { get; set; } + + public int CalculationYear { get; set; } + + public decimal TotalTaxAmount { get; set; } + + public TaxAmountDetail TaxDetails { get; set; } +} diff --git a/src/CommonTypes/Tax/FullTaxRequest.cs b/src/CommonTypes/Tax/FullTaxRequest.cs index 00c2bda6..365c12e8 100644 --- a/src/CommonTypes/Tax/FullTaxRequest.cs +++ b/src/CommonTypes/Tax/FullTaxRequest.cs @@ -1,31 +1,31 @@ using System.ComponentModel.DataAnnotations; +using Domain.Enums; -namespace PensionCoach.Tools.CommonTypes.Tax +namespace PensionCoach.Tools.CommonTypes.Tax; + +public class FullTaxRequest { - public class FullTaxRequest - { - [MaxLength(50)] - public string Name { get; set; } + [MaxLength(50)] + public string Name { get; set; } - [Range(2018, 2099, ErrorMessage = "Valid tax years start from 2018")] - public int CalculationYear { get; set; } + [Range(2018, 2099, ErrorMessage = "Valid tax years start from 2018")] + public int CalculationYear { get; set; } - public CivilStatus CivilStatus { get; set; } + public CivilStatus CivilStatus { get; set; } - public ReligiousGroupType ReligiousGroup { get; set; } + public ReligiousGroupType ReligiousGroup { get; set; } - public ReligiousGroupType? PartnerReligiousGroup { get; set; } + public ReligiousGroupType? PartnerReligiousGroup { get; set; } - [Range(typeof(int), "0", "100000", ErrorMessage = "BFS Number not valid")] - public int BfsMunicipalityId { get; set; } + [Range(typeof(int), "0", "100000", ErrorMessage = "BFS Number not valid")] + public int BfsMunicipalityId { get; set; } - [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] - public decimal TaxableIncome { get; set; } + [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] + public decimal TaxableIncome { get; set; } - [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] - public decimal TaxableFederalIncome { get; set; } + [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] + public decimal TaxableFederalIncome { get; set; } - [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] - public decimal TaxableWealth { get; set; } - } + [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] + public decimal TaxableWealth { get; set; } } diff --git a/src/CommonTypes/Tax/FullTaxResponse.cs b/src/CommonTypes/Tax/FullTaxResponse.cs index 64125585..3647afab 100644 --- a/src/CommonTypes/Tax/FullTaxResponse.cs +++ b/src/CommonTypes/Tax/FullTaxResponse.cs @@ -1,27 +1,28 @@ -namespace PensionCoach.Tools.CommonTypes.Tax +using Domain.Models.Tax; + +namespace PensionCoach.Tools.CommonTypes.Tax; + +public class FullTaxResponse { - public class FullTaxResponse - { - public string Name { get; set; } + public string Name { get; set; } - public int CalculationYear { get; set; } + public int CalculationYear { get; set; } - public TaxRateDetails TaxRateDetails { get; set; } + public TaxRateDetails TaxRateDetails { get; set; } - public decimal TotalTaxAmount { get; set; } + public decimal TotalTaxAmount { get; set; } - public decimal CantonTaxAmount { get; set; } + public decimal CantonTaxAmount { get; set; } - public decimal MunicipalityTaxAmount { get; set; } + public decimal MunicipalityTaxAmount { get; set; } - public decimal FederalTaxAmount { get; set; } + public decimal FederalTaxAmount { get; set; } - public decimal IncomeTaxAmount { get; set; } + public decimal IncomeTaxAmount { get; set; } - public decimal WealthTaxAmount { get; set; } + public decimal WealthTaxAmount { get; set; } - public decimal ChurchTaxAmount { get; set; } + public decimal ChurchTaxAmount { get; set; } - public decimal PollTaxAmount { get; set; } - } + public decimal PollTaxAmount { get; set; } } diff --git a/src/CommonTypes/Tax/MarginalTaxInfo.cs b/src/CommonTypes/Tax/MarginalTaxInfo.cs deleted file mode 100644 index 1c90c731..00000000 --- a/src/CommonTypes/Tax/MarginalTaxInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace PensionCoach.Tools.CommonTypes.Tax; - -public record MarginalTaxInfo(decimal Salary, decimal Rate, decimal TotalTaxAmount); diff --git a/src/CommonTypes/Tax/MarginalTaxRequest.cs b/src/CommonTypes/Tax/MarginalTaxRequest.cs index 43af31ec..524490d1 100644 --- a/src/CommonTypes/Tax/MarginalTaxRequest.cs +++ b/src/CommonTypes/Tax/MarginalTaxRequest.cs @@ -1,35 +1,35 @@ using System.ComponentModel.DataAnnotations; +using Domain.Enums; -namespace PensionCoach.Tools.CommonTypes.Tax +namespace PensionCoach.Tools.CommonTypes.Tax; + +public class MarginalTaxRequest { - public class MarginalTaxRequest - { - [MaxLength(50)] - public string Name { get; set; } + [MaxLength(50)] + public string Name { get; set; } - [Range(2018, 2099, ErrorMessage = "Valid tax years start from 2018")] - public int CalculationYear { get; set; } + [Range(2018, 2099, ErrorMessage = "Valid tax years start from 2018")] + public int CalculationYear { get; set; } - public CivilStatus CivilStatus { get; set; } + public CivilStatus CivilStatus { get; set; } - public ReligiousGroupType ReligiousGroup { get; set; } + public ReligiousGroupType ReligiousGroup { get; set; } - public ReligiousGroupType? PartnerReligiousGroup { get; set; } + public ReligiousGroupType? PartnerReligiousGroup { get; set; } - [Range(typeof(int), "0", "100000", ErrorMessage = "BFS Number not valid")] - public int BfsMunicipalityId { get; set; } + [Range(typeof(int), "0", "100000", ErrorMessage = "BFS Number not valid")] + public int BfsMunicipalityId { get; set; } - [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] - public decimal TaxableAmount { get; set; } + [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] + public decimal TaxableAmount { get; set; } - [Range(typeof(int), "0", "1000000", ErrorMessage = "Value not possible")] - public int LowerSalaryLimit { get; set; } + [Range(typeof(int), "0", "1000000", ErrorMessage = "Value not possible")] + public int LowerSalaryLimit { get; set; } - [Range(typeof(int), "0", "10000000", ErrorMessage = "Value not possible")] - public int UpperSalaryLimit { get; set; } + [Range(typeof(int), "0", "10000000", ErrorMessage = "Value not possible")] + public int UpperSalaryLimit { get; set; } - [Range(typeof(int), "0", "1000", ErrorMessage = "Value not possible")] - public int NumberOfSamples { get; set; } + [Range(typeof(int), "0", "1000", ErrorMessage = "Value not possible")] + public int NumberOfSamples { get; set; } - } } diff --git a/src/CommonTypes/Tax/MarginalTaxResponse.cs b/src/CommonTypes/Tax/MarginalTaxResponse.cs index f563da33..73ccc81c 100644 --- a/src/CommonTypes/Tax/MarginalTaxResponse.cs +++ b/src/CommonTypes/Tax/MarginalTaxResponse.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Domain.Models.Tax; namespace PensionCoach.Tools.CommonTypes.Tax; diff --git a/src/CommonTypes/Tax/TaxPerson.cs b/src/CommonTypes/Tax/TaxPerson.cs deleted file mode 100644 index 5f898246..00000000 --- a/src/CommonTypes/Tax/TaxPerson.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace PensionCoach.Tools.CommonTypes.Tax -{ - public record TaxPerson : TaxPersonBasic - { - public decimal TaxableIncome { get; set; } - public decimal TaxableFederalIncome { get; set; } - public decimal TaxableWealth { get; set; } - } -} diff --git a/src/CommonUtils/CommonUtils.csproj b/src/CommonUtils/CommonUtils.csproj index 962c4cc3..fdcd2fd6 100644 --- a/src/CommonUtils/CommonUtils.csproj +++ b/src/CommonUtils/CommonUtils.csproj @@ -1,16 +1,12 @@  - net7.0 + net8.0 PensionCoach.Tools.CommonUtils PensionCoach.Tools.CommonUtils - - - - - + diff --git a/src/CommonUtils/ConfigurationExtensions.cs b/src/CommonUtils/ConfigurationExtensions.cs deleted file mode 100644 index ea54f9b4..00000000 --- a/src/CommonUtils/ConfigurationExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using Microsoft.Extensions.Configuration; -using PensionCoach.Tools.CommonTypes.Tax; - -namespace PensionCoach.Tools.CommonUtils; - -public static class ConfigurationExtensions -{ - public static ApplicationMode GetApplicationMode(this IConfiguration configuration) - { - const string key = "ApplicationMode"; - - return configuration[key] switch - { - null => ApplicationMode.Proprietary, - {} v => Enum.Parse(v), - }; - } -} diff --git a/src/CommonUtils/MeasureTime.cs b/src/CommonUtils/MeasureTime.cs index c06fbcac..a8bb9dff 100644 --- a/src/CommonUtils/MeasureTime.cs +++ b/src/CommonUtils/MeasureTime.cs @@ -1,23 +1,24 @@ using System; using System.Diagnostics; -namespace PensionCoach.Tools.CommonUtils; - -public sealed class MeasureTime : IDisposable +namespace PensionCoach.Tools.CommonUtils { - private readonly Action _action; - private readonly Stopwatch _sw; - - public MeasureTime(Action action) + public sealed class MeasureTime : IDisposable { - _action = action; - _sw = new Stopwatch(); - _sw.Start(); - } + private readonly Action _action; + private readonly Stopwatch _sw; - public void Dispose() - { - _sw.Stop(); - _action.Invoke(_sw.ElapsedMilliseconds); + public MeasureTime(Action action) + { + _action = action; + _sw = new Stopwatch(); + _sw.Start(); + } + + public void Dispose() + { + _sw.Stop(); + _action.Invoke(_sw.ElapsedMilliseconds); + } } } diff --git a/src/Domain.Tests/Domain.Tests.csproj b/src/Domain.Tests/Domain.Tests.csproj new file mode 100644 index 00000000..8516d719 --- /dev/null +++ b/src/Domain.Tests/Domain.Tests.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + enable + enable + + false + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/src/Domain.Tests/TechnicalAgeTest.cs b/src/Domain.Tests/TechnicalAgeTest.cs new file mode 100644 index 00000000..9d4d8da0 --- /dev/null +++ b/src/Domain.Tests/TechnicalAgeTest.cs @@ -0,0 +1,71 @@ +using Domain.Models.Bvg; +using FluentAssertions; + +namespace Domain.Tests; + +[Trait("Domain Tests", "Technical Age")] +public class TechnicalAgeTest +{ + [Theory(DisplayName = "operator +")] + [InlineData(33, 1, 33, 1, 66, 2)] + [InlineData(58, 0, 1, 0, 59, 0)] + [InlineData(58, 6, 1, 8, 60, 2)] + public void Add(int year1, int month1, int year2, int month2, int expectedYear, int expectedMonth) + { + TechnicalAge age1 = new(year1, month1); + TechnicalAge age2 = new(year2, month2); + + TechnicalAge expectedAge = new(expectedYear, expectedMonth); + + TechnicalAge result = age1 + age2; + + result.Should().BeEquivalentTo(expectedAge); + } + + [Theory(DisplayName = "operator -")] + [InlineData(33, 1, 33, 1, 0, 0)] + [InlineData(65, 0, 58, 6, 6, 6)] + [InlineData(64, 8, 58, 6, 6, 2)] + [InlineData(64, 8, 58, 9, 5, 11)] + public void Subtract(int year1, int month1, int year2, int month2, int expectedYear, int expectedMonth) + { + TechnicalAge age1 = new(year1, month1); + TechnicalAge age2 = new(year2, month2); + + TechnicalAge expectedAge = new(expectedYear, expectedMonth); + + TechnicalAge result = age1 - age2; + + result.Should().BeEquivalentTo(expectedAge); + } + + [Theory(DisplayName = "operator <=")] + [InlineData(33, 1, 33, 1, true)] + [InlineData(65, 0, 58, 6, false)] + [InlineData(64, 8, 58, 6, false)] + [InlineData(58, 8, 58, 9, true)] + public void LessOrEqual(int year1, int month1, int year2, int month2, bool expected) + { + TechnicalAge age1 = new(year1, month1); + TechnicalAge age2 = new(year2, month2); + + bool result = age1 <= age2; + + result.Should().Be(expected); + } + + [Theory(DisplayName = "operator >=")] + [InlineData(33, 1, 33, 1, true)] + [InlineData(65, 0, 58, 6, true)] + [InlineData(64, 8, 58, 6, true)] + [InlineData(58, 8, 58, 9, false)] + public void GreaterOrEqual(int year1, int month1, int year2, int month2, bool expected) + { + TechnicalAge age1 = new(year1, month1); + TechnicalAge age2 = new(year2, month2); + + bool result = age1 >= age2; + + result.Should().Be(expected); + } +} diff --git a/src/Domain/Contracts/ICashFlowDefinition.cs b/src/Domain/Contracts/ICashFlowDefinition.cs new file mode 100644 index 00000000..685d6e3f --- /dev/null +++ b/src/Domain/Contracts/ICashFlowDefinition.cs @@ -0,0 +1,20 @@ +using Domain.Models.MultiPeriod.Definitions; + +namespace Domain.Contracts +{ + public interface ICashFlowDefinition + { + /// + /// Gets or sets the header properties + /// + public CashFlowHeader Header { get; set; } + + /// + /// Gets or sets the date of process. + /// + /// + /// The date of process. + /// + public DateTime DateOfProcess { get; set; } + } +} diff --git a/src/Domain/Contracts/ICompositeCashFlowDefinition.cs b/src/Domain/Contracts/ICompositeCashFlowDefinition.cs new file mode 100644 index 00000000..9bf763fb --- /dev/null +++ b/src/Domain/Contracts/ICompositeCashFlowDefinition.cs @@ -0,0 +1,3 @@ +namespace Domain.Contracts; + +public interface ICompositeCashFlowDefinition { } diff --git a/src/Domain/Contracts/IDynamicCashFlowDefinition.cs b/src/Domain/Contracts/IDynamicCashFlowDefinition.cs new file mode 100644 index 00000000..e33cc32a --- /dev/null +++ b/src/Domain/Contracts/IDynamicCashFlowDefinition.cs @@ -0,0 +1,4 @@ +namespace Domain.Contracts +{ + public interface IDynamicCashFlowDefinition : ICashFlowDefinition { } +} diff --git a/src/Domain/Contracts/IStaticCashFlowDefinition.cs b/src/Domain/Contracts/IStaticCashFlowDefinition.cs new file mode 100644 index 00000000..220474ee --- /dev/null +++ b/src/Domain/Contracts/IStaticCashFlowDefinition.cs @@ -0,0 +1,4 @@ +namespace Domain.Contracts +{ + public interface IStaticCashFlowDefinition : ICashFlowDefinition { } +} diff --git a/src/Domain/Domain.csproj b/src/Domain/Domain.csproj new file mode 100644 index 00000000..8b95e245 --- /dev/null +++ b/src/Domain/Domain.csproj @@ -0,0 +1,8 @@ + + + + net8.0 + enable + + + diff --git a/src/Domain/Enums/AccountType.cs b/src/Domain/Enums/AccountType.cs new file mode 100644 index 00000000..7a385ed4 --- /dev/null +++ b/src/Domain/Enums/AccountType.cs @@ -0,0 +1,38 @@ +using System.Text.Json.Serialization; + +namespace Domain.Enums +{ + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum AccountType + { + Exogenous, + Wealth, + Income, + + /// + /// A investment account which is subject to wealth tax scheme. + /// However, if it pays dividends, they are subjects to the income tax. + /// + /// + /// Planned feature: its value may change stochastically. + /// + Investment, + + /// + /// Capital benefit assets in second pillar account (BVG Vorsorgegelder, FZP) which + /// are subject to capital benefits tax scheme. + /// + OccupationalPension, + + /// + /// Capital benefit assets in third pillar account (3a) which + /// are subject to capital benefits tax scheme. + /// + ThirdPillar, + + /// + /// Accumulated tax assets which are subject to all tax types like income, wealth and capital benefits tax scheme. + /// + Tax + } +} diff --git a/src/Domain/Enums/ApplicationMode.cs b/src/Domain/Enums/ApplicationMode.cs new file mode 100644 index 00000000..ba9f6685 --- /dev/null +++ b/src/Domain/Enums/ApplicationMode.cs @@ -0,0 +1,9 @@ +namespace Domain.Enums +{ + public enum ApplicationMode + { + Proprietary = 0, + Estv = 1, + Mock = 2 + } +} diff --git a/src/Domain/Enums/Canton.cs b/src/Domain/Enums/Canton.cs new file mode 100644 index 00000000..59e28c52 --- /dev/null +++ b/src/Domain/Enums/Canton.cs @@ -0,0 +1,77 @@ +using System.Text.Json.Serialization; + +namespace Domain.Enums +{ + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum Canton + { + /// + /// Undefined is not a valid and is treated as an error + /// + Undefined = 0, + + AG = 1, + + AI = 2, + + AR = 3, + + /// + /// Bern + /// + BE = 4, + + BL = 5, + + BS = 6, + + FR = 7, + + GE = 8, + + GL = 9, + + GR = 10, + + JU = 11, + + LU = 12, + + NE = 13, + + NW = 14, + + OW = 15, + + SH = 16, + + /// + /// Solothurn + /// + SO = 17, + + /// + /// St.Gallen + /// + SG = 18, + + SZ = 19, + + TI = 20, + + TG = 21, + + UR = 22, + + VD = 23, + + VS = 24, + + ZG = 25, + + /// + /// Zürich + /// + ZH = 26, + } +} diff --git a/src/CommonTypes/CivilStatus.cs b/src/Domain/Enums/CivilStatus.cs similarity index 89% rename from src/CommonTypes/CivilStatus.cs rename to src/Domain/Enums/CivilStatus.cs index eae879dd..5f5513a9 100644 --- a/src/CommonTypes/CivilStatus.cs +++ b/src/Domain/Enums/CivilStatus.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace PensionCoach.Tools.CommonTypes +namespace Domain.Enums { [JsonConverter(typeof(JsonStringEnumConverter))] public enum CivilStatus diff --git a/src/Domain/Enums/FrequencyType.cs b/src/Domain/Enums/FrequencyType.cs new file mode 100644 index 00000000..f9e306d3 --- /dev/null +++ b/src/Domain/Enums/FrequencyType.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace Domain.Enums +{ + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum FrequencyType + { + Daily, + Monthly, + Yearly + } +} diff --git a/src/CommonTypes/Gender.cs b/src/Domain/Enums/Gender.cs similarity index 88% rename from src/CommonTypes/Gender.cs rename to src/Domain/Enums/Gender.cs index a33184f4..63bd8fa7 100644 --- a/src/CommonTypes/Gender.cs +++ b/src/Domain/Enums/Gender.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace PensionCoach.Tools.CommonTypes +namespace Domain.Enums { /// /// Represents the gender of a human being. diff --git a/src/CommonTypes/ReligiousGroupType.cs b/src/Domain/Enums/ReligiousGroupType.cs similarity index 89% rename from src/CommonTypes/ReligiousGroupType.cs rename to src/Domain/Enums/ReligiousGroupType.cs index b2a79dc2..893647f0 100644 --- a/src/CommonTypes/ReligiousGroupType.cs +++ b/src/Domain/Enums/ReligiousGroupType.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace PensionCoach.Tools.CommonTypes +namespace Domain.Enums { [JsonConverter(typeof(JsonStringEnumConverter))] public enum ReligiousGroupType diff --git a/src/CommonTypes/Tax/TaxType.cs b/src/Domain/Enums/TaxType.cs similarity index 100% rename from src/CommonTypes/Tax/TaxType.cs rename to src/Domain/Enums/TaxType.cs diff --git a/src/BvgCalculator.Abstractions/Models/BvgCalculationResult.cs b/src/Domain/Models/Bvg/BvgCalculationResult.cs similarity index 88% rename from src/BvgCalculator.Abstractions/Models/BvgCalculationResult.cs rename to src/Domain/Models/Bvg/BvgCalculationResult.cs index b1470895..bf115b9f 100644 --- a/src/BvgCalculator.Abstractions/Models/BvgCalculationResult.cs +++ b/src/Domain/Models/Bvg/BvgCalculationResult.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; - -namespace PensionCoach.Tools.BvgCalculator.Models +namespace Domain.Models.Bvg { public class BvgCalculationResult { diff --git a/src/BvgCalculator.Abstractions/Models/BvgPerson.cs b/src/Domain/Models/Bvg/BvgPerson.cs similarity index 88% rename from src/BvgCalculator.Abstractions/Models/BvgPerson.cs rename to src/Domain/Models/Bvg/BvgPerson.cs index 7dc6dc08..d52650ce 100644 --- a/src/BvgCalculator.Abstractions/Models/BvgPerson.cs +++ b/src/Domain/Models/Bvg/BvgPerson.cs @@ -1,7 +1,6 @@ -using System; -using PensionCoach.Tools.CommonTypes; +using Domain.Enums; -namespace PensionCoach.Tools.BvgCalculator.Models +namespace Domain.Models.Bvg { public class BvgPerson { diff --git a/src/Domain/Models/Bvg/PredecessorRetirementCapital.cs b/src/Domain/Models/Bvg/PredecessorRetirementCapital.cs new file mode 100644 index 00000000..2c2d0e92 --- /dev/null +++ b/src/Domain/Models/Bvg/PredecessorRetirementCapital.cs @@ -0,0 +1,36 @@ +namespace Domain.Models.Bvg; + +public class PredecessorRetirementCapital +{ + /// + /// Gets or sets the process date. + /// + /// + /// The process date. + /// + public DateTime DateOfProcess { get; set; } + + /// + /// Gets or sets the begin of year amount. + /// + /// + /// The begin of year amount. + /// + public decimal BeginOfYearAmount { get; set; } + + /// + /// Gets or sets the end of year amount. + /// + /// + /// The end of year amount. + /// + public decimal EndOfYearAmount { get; set; } + + /// + /// Gets or sets the current amount. + /// + /// + /// The current amount. + /// + public decimal CurrentAmount { get; set; } +} diff --git a/src/Domain/Models/Bvg/RetirementCapital.cs b/src/Domain/Models/Bvg/RetirementCapital.cs new file mode 100644 index 00000000..63dd3254 --- /dev/null +++ b/src/Domain/Models/Bvg/RetirementCapital.cs @@ -0,0 +1,22 @@ +namespace Domain.Models.Bvg; + +public class RetirementCapital(DateTime date, decimal value, decimal valueWithoutInterest) +{ + /// + /// Date of validity + /// + public DateTime Date { get; } = date; + + /// + /// Mandatory retirement assets BVG portion + /// + public decimal Value { get; } = value; + + /// + /// Gets the value without interest BVG by raw retirement credits. + /// + /// + /// The value without interest BVG by raw retirement credits. + /// + public decimal ValueWithoutInterest { get; } = valueWithoutInterest; +} diff --git a/src/Domain/Models/Bvg/RetirementCredit.cs b/src/Domain/Models/Bvg/RetirementCredit.cs new file mode 100644 index 00000000..5c8b4283 --- /dev/null +++ b/src/Domain/Models/Bvg/RetirementCredit.cs @@ -0,0 +1,26 @@ +namespace Domain.Models.Bvg; + +/// +/// Holds retirement credit +/// +public class RetirementCredit +{ + /// + /// Gets the age. + /// + /// + /// The age. + /// + public int Age { get; set; } + + /// + /// BVG portion + /// + public decimal AmountRaw { get; set; } + + public RetirementCredit(decimal amountRaw, int age) + { + AmountRaw = amountRaw; + Age = age; + } +} diff --git a/src/Domain/Models/Bvg/TechnicalAge.cs b/src/Domain/Models/Bvg/TechnicalAge.cs new file mode 100644 index 00000000..d3d188cd --- /dev/null +++ b/src/Domain/Models/Bvg/TechnicalAge.cs @@ -0,0 +1,93 @@ +using System.Diagnostics; + +namespace Domain.Models.Bvg; + +/// +/// It is defined as technical age. This means years and months +/// derived from the technical date of birth and a given process date expressed as (years, months). +/// +/// +/// Example: Vorfalldatum: 22.1.2021, Geburtsdatum: 15.1.1958 +/// => techn. Geburtsdatum 1.2.1958 +/// => techn. Alter = 2021.1 - 1958.2 = 62.1 +/// +[DebuggerDisplay("{Years}/{Months}")] +public record TechnicalAge(int Years, int Months) +{ + private const int NumMonths = 12; + + public static TechnicalAge operator +(TechnicalAge a, TechnicalAge b) + { + int totalMonths = a.Months + b.Months; + int extraYears = totalMonths / NumMonths; + int remainingMonths = totalMonths % NumMonths; + + return new TechnicalAge(a.Years + b.Years + extraYears, remainingMonths); + } + + public static TechnicalAge operator -(TechnicalAge a, TechnicalAge b) + { + int diffYears = a.Years - b.Years; + int diffMonths = a.Months - b.Months; + + if (diffMonths >= 0) + { + return new TechnicalAge(diffYears, diffMonths); + } + + diffYears--; + diffMonths += NumMonths; + + return new TechnicalAge(diffYears, diffMonths); + } + + public static bool operator <(TechnicalAge a, TechnicalAge b) + { + if (a.Years < b.Years) + { + return true; + } + + if (a.Years == b.Years) + { + return a.Months < b.Months; + } + + return false; + } + + public static bool operator >(TechnicalAge a, TechnicalAge b) + { + return !(a <= b); + } + + public static bool operator <=(TechnicalAge a, TechnicalAge b) + { + if (a.Years < b.Years) + { + return true; + } + + if (a.Years == b.Years) + { + return a.Months <= b.Months; + } + + return false; + } + + public static bool operator >=(TechnicalAge a, TechnicalAge b) + { + return !(a < b); + } + + public static implicit operator TechnicalAge((int Years, int Months) age) + { + return new TechnicalAge(age.Years, age.Months); + } + + public static TechnicalAge From(int years, int months) + { + return new TechnicalAge(years, months); + } +} diff --git a/src/Domain/Models/Cashflows/AccountTransactionResult.cs b/src/Domain/Models/Cashflows/AccountTransactionResult.cs new file mode 100644 index 00000000..beb7570d --- /dev/null +++ b/src/Domain/Models/Cashflows/AccountTransactionResult.cs @@ -0,0 +1,13 @@ +using Domain.Models.Cashflows.Accounts; + +namespace Domain.Models.Cashflows +{ + public record AccountTransactionResult + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public List Transactions { get; set; } = new(); + } +} diff --git a/src/Domain/Models/Cashflows/AccountTransactionResultHolder.cs b/src/Domain/Models/Cashflows/AccountTransactionResultHolder.cs new file mode 100644 index 00000000..713d639e --- /dev/null +++ b/src/Domain/Models/Cashflows/AccountTransactionResultHolder.cs @@ -0,0 +1,19 @@ +namespace Domain.Models.Cashflows +{ + public record AccountTransactionResultHolder + { + public AccountTransactionResult ExogenousAccount { get; set; } + + public AccountTransactionResult IncomeAccount { get; set; } + + public AccountTransactionResult WealthAccount { get; set; } + + public AccountTransactionResult InvestmentAccount { get; set; } + + public AccountTransactionResult OccupationalPensionAccount { get; set; } + + public AccountTransactionResult ThirdPillarAccount { get; set; } + + public AccountTransactionResult TaxAccount { get; set; } + } +} diff --git a/src/Domain/Models/Cashflows/Accounts/AccountTransaction.cs b/src/Domain/Models/Cashflows/Accounts/AccountTransaction.cs new file mode 100644 index 00000000..4f0e6434 --- /dev/null +++ b/src/Domain/Models/Cashflows/Accounts/AccountTransaction.cs @@ -0,0 +1,4 @@ +namespace Domain.Models.Cashflows.Accounts +{ + public record AccountTransaction(string Description, DateTime ValutaDate, decimal Amount); +} diff --git a/src/Domain/Models/Cashflows/Accounts/ExogenousAccount.cs b/src/Domain/Models/Cashflows/Accounts/ExogenousAccount.cs new file mode 100644 index 00000000..b684f007 --- /dev/null +++ b/src/Domain/Models/Cashflows/Accounts/ExogenousAccount.cs @@ -0,0 +1,15 @@ +namespace Domain.Models.Cashflows.Accounts +{ + public class ExogenousAccount : ICashFlowAccount + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public decimal Balance { get; set; } + + public decimal NetGrowthRate { get; set; } + + public List Transactions { get; set; } = new(); + } +} diff --git a/src/Domain/Models/Cashflows/Accounts/ICashFlowAccount.cs b/src/Domain/Models/Cashflows/Accounts/ICashFlowAccount.cs new file mode 100644 index 00000000..2a745108 --- /dev/null +++ b/src/Domain/Models/Cashflows/Accounts/ICashFlowAccount.cs @@ -0,0 +1,15 @@ +namespace Domain.Models.Cashflows.Accounts +{ + public interface ICashFlowAccount + { + Guid Id { get; set; } + + string Name { get; set; } + + decimal Balance { get; set; } + + decimal NetGrowthRate { get; set; } + + List Transactions { get; set; } + } +} diff --git a/src/Domain/Models/Cashflows/Accounts/IncomeAccount.cs b/src/Domain/Models/Cashflows/Accounts/IncomeAccount.cs new file mode 100644 index 00000000..0f9c5f82 --- /dev/null +++ b/src/Domain/Models/Cashflows/Accounts/IncomeAccount.cs @@ -0,0 +1,15 @@ +namespace Domain.Models.Cashflows.Accounts +{ + public class IncomeAccount : ICashFlowAccount + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public decimal Balance { get; set; } + + public decimal NetGrowthRate { get; set; } + + public List Transactions { get; set; } = new(); + } +} diff --git a/src/Domain/Models/Cashflows/Accounts/InvestmentAccount.cs b/src/Domain/Models/Cashflows/Accounts/InvestmentAccount.cs new file mode 100644 index 00000000..1f48c0e5 --- /dev/null +++ b/src/Domain/Models/Cashflows/Accounts/InvestmentAccount.cs @@ -0,0 +1,31 @@ +namespace Domain.Models.Cashflows.Accounts +{ + /// + /// Represents an investment account. it models the cash flow of an investment account. + /// However, there are two different types of cash flows: + /// 1. the cash flow of the capital gains + /// 2. the cash flow of the dividends or interests + /// + public class InvestmentAccount : ICashFlowAccount + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public decimal Balance { get; set; } + + /// + /// Gets or sets the net income yield. Income types are dividends and interests which are subject to income tax. + /// This is the yield before income taxes. + /// + public decimal NetIncomeYield { get; set; } + + /// + /// Gets or sets the net rate of return. This is the yield before wealth taxes, + /// and does not include the dividends. + /// + public decimal NetGrowthRate { get; set; } + + public List Transactions { get; set; } = new(); + } +} diff --git a/src/Domain/Models/Cashflows/Accounts/OccupationalPensionAccount.cs b/src/Domain/Models/Cashflows/Accounts/OccupationalPensionAccount.cs new file mode 100644 index 00000000..ae386a3a --- /dev/null +++ b/src/Domain/Models/Cashflows/Accounts/OccupationalPensionAccount.cs @@ -0,0 +1,15 @@ +namespace Domain.Models.Cashflows.Accounts +{ + public class OccupationalPensionAccount : ICashFlowAccount + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public decimal Balance { get; set; } + + public decimal NetGrowthRate { get; set; } + + public List Transactions { get; set; } = new(); + } +} diff --git a/src/Domain/Models/Cashflows/Accounts/TaxAccount.cs b/src/Domain/Models/Cashflows/Accounts/TaxAccount.cs new file mode 100644 index 00000000..4ee3d49d --- /dev/null +++ b/src/Domain/Models/Cashflows/Accounts/TaxAccount.cs @@ -0,0 +1,15 @@ +namespace Domain.Models.Cashflows.Accounts +{ + public class TaxAccount : ICashFlowAccount + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public decimal Balance { get; set; } + + public decimal NetGrowthRate { get; set; } + + public List Transactions { get; set; } = new(); + } +} diff --git a/src/Domain/Models/Cashflows/Accounts/ThirdPillarAccount.cs b/src/Domain/Models/Cashflows/Accounts/ThirdPillarAccount.cs new file mode 100644 index 00000000..2de13efa --- /dev/null +++ b/src/Domain/Models/Cashflows/Accounts/ThirdPillarAccount.cs @@ -0,0 +1,15 @@ +namespace Domain.Models.Cashflows.Accounts +{ + public class ThirdPillarAccount : ICashFlowAccount + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public decimal Balance { get; set; } + + public decimal NetGrowthRate { get; set; } + + public List Transactions { get; set; } = new(); + } +} diff --git a/src/Domain/Models/Cashflows/Accounts/WealthAccount.cs b/src/Domain/Models/Cashflows/Accounts/WealthAccount.cs new file mode 100644 index 00000000..61d4aec9 --- /dev/null +++ b/src/Domain/Models/Cashflows/Accounts/WealthAccount.cs @@ -0,0 +1,15 @@ +namespace Domain.Models.Cashflows.Accounts +{ + public class WealthAccount : ICashFlowAccount + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public decimal Balance { get; set; } + + public decimal NetGrowthRate { get; set; } + + public List Transactions { get; set; } = new(); + } +} diff --git a/src/Domain/Models/Cashflows/CashFlowModel.cs b/src/Domain/Models/Cashflows/CashFlowModel.cs new file mode 100644 index 00000000..52ad319d --- /dev/null +++ b/src/Domain/Models/Cashflows/CashFlowModel.cs @@ -0,0 +1,12 @@ +using Domain.Enums; +using PensionCoach.Tools.CommonTypes.Tax; + +namespace Domain.Models.Cashflows; + +public record CashFlowModel( + DateOnly DateOfProcess, + decimal Amount, + AccountType Source, + AccountType Target, + bool IsTaxable, + TaxType TaxType); diff --git a/src/Domain/Models/MultiPeriod/Actions/ChangeResidenceAction.cs b/src/Domain/Models/MultiPeriod/Actions/ChangeResidenceAction.cs new file mode 100644 index 00000000..b3d3730d --- /dev/null +++ b/src/Domain/Models/MultiPeriod/Actions/ChangeResidenceAction.cs @@ -0,0 +1,49 @@ +using Domain.Contracts; +using Domain.Enums; +using Domain.Models.MultiPeriod.Definitions; + +namespace Domain.Models.MultiPeriod.Actions +{ + /// + /// Simulates the change of the residence. A change always happens at the end of a period. + /// + public record ChangeResidenceAction : ICompositeCashFlowDefinition + { + /// + /// Gets or sets the header properties + /// + public CashFlowHeader Header { get; set; } + + /// + /// Gets the date of change of residence. + /// + /// + /// The change of residence at this year. + /// + public DateTime DateOfProcess { get; set; } + + /// + /// Gets the destination municipality identifier. + /// + /// + /// The destination municipality identifier. + /// + public int DestinationMunicipalityId { get; set; } + + /// + /// Gets or sets the destination canton. + /// + /// + /// The destination canton. + /// + public Canton DestinationCanton { get; set; } + + /// + /// Gets the change cost. The cost to change of the residence is deducted from wealth. + /// + /// + /// The change cost. + /// + public decimal ChangeCost { get; set; } + } +} diff --git a/src/Domain/Models/MultiPeriod/Actions/DynamicTransferAccountAction.cs b/src/Domain/Models/MultiPeriod/Actions/DynamicTransferAccountAction.cs new file mode 100644 index 00000000..f1bb262c --- /dev/null +++ b/src/Domain/Models/MultiPeriod/Actions/DynamicTransferAccountAction.cs @@ -0,0 +1,33 @@ +using Domain.Contracts; +using Domain.Models.MultiPeriod.Definitions; +using PensionCoach.Tools.CommonTypes.Tax; + +namespace Domain.Models.MultiPeriod.Actions +{ + public record DynamicTransferAccountAction : IDynamicCashFlowDefinition + { + /// + /// Gets or sets the header properties + /// + public CashFlowHeader Header { get; set; } + + /// + /// Gets or sets the date of transfer. + /// + /// + /// The date of transfer. + /// + public DateTime DateOfProcess { get; set; } + + /// + /// How much in relative terms is transferred from the source to the target account. + /// + public decimal TransferRatio { get; set; } = decimal.One; + + public FlowPair Flow { get; set; } + + public bool IsTaxable { get; set; } + + public TaxType TaxType { get; set; } + } +} diff --git a/src/Domain/Models/MultiPeriod/Actions/OrdinaryRetirementAction.cs b/src/Domain/Models/MultiPeriod/Actions/OrdinaryRetirementAction.cs new file mode 100644 index 00000000..e6fa67d7 --- /dev/null +++ b/src/Domain/Models/MultiPeriod/Actions/OrdinaryRetirementAction.cs @@ -0,0 +1,25 @@ +using Domain.Contracts; +using Domain.Models.MultiPeriod.Definitions; + +namespace Domain.Models.MultiPeriod.Actions +{ + public record OrdinaryRetirementAction : ICompositeCashFlowDefinition + { + /// + /// Gets or sets the header properties + /// + public CashFlowHeader Header { get; set; } + + public DateTime DateOfProcess { get; set; } + + public int NumberOfPeriods { get; set; } + + public decimal CapitalOptionFactor { get; set; } = decimal.Zero; + + public decimal RetirementPension { get; set; } + + public decimal CapitalConsumptionAmountPerYear { get; set; } + + public decimal AhvPensionAmount { get; set; } + } +} diff --git a/src/Domain/Models/MultiPeriod/CashFlowDefinitionHolder.cs b/src/Domain/Models/MultiPeriod/CashFlowDefinitionHolder.cs new file mode 100644 index 00000000..91fdd64e --- /dev/null +++ b/src/Domain/Models/MultiPeriod/CashFlowDefinitionHolder.cs @@ -0,0 +1,43 @@ +using Domain.Contracts; +using Domain.Models.MultiPeriod.Actions; +using Domain.Models.MultiPeriod.Definitions; + +namespace Domain.Models.MultiPeriod +{ + public record CashFlowDefinitionHolder + { + public IReadOnlyCollection InvestmentDefinitions { get; set; } = + Array.Empty().ToList(); + + /// + /// List of composite cash flow definitions. + /// A composite cash flow definition is composed by one or several more basic definitions. + /// + public IReadOnlyCollection Composites { get; set; } = + Array.Empty().ToList(); + + /// + /// Gets the generic cash flow definitions. + /// + /// + /// The generic cash flow definitions. + /// + public IReadOnlyCollection StaticGenericCashFlowDefinitions { get; set; } = + Array.Empty().ToList(); + + /// + /// Collection of cash-flow actions. + /// + public IReadOnlyCollection CashFlowActions { get; set; } = + Array.Empty().ToList(); + + /// + /// Gets the change residence actions. + /// + /// + /// The change residence actions. + /// + public IReadOnlyCollection ChangeResidenceActions { get; set; } = + Array.Empty().ToList(); + } +} diff --git a/src/Domain/Models/MultiPeriod/Definitions/CashFlowHeader.cs b/src/Domain/Models/MultiPeriod/Definitions/CashFlowHeader.cs new file mode 100644 index 00000000..4cac4e65 --- /dev/null +++ b/src/Domain/Models/MultiPeriod/Definitions/CashFlowHeader.cs @@ -0,0 +1,21 @@ +namespace Domain.Models.MultiPeriod.Definitions +{ + public record CashFlowHeader + { + /// + /// Gets or sets the identifier. + /// + /// + /// The identifier. + /// + public string Id { get; set; } + + /// + /// Gets the name. + /// + /// + /// The name. + /// + public string Name { get; set; } + } +} diff --git a/src/Domain/Models/MultiPeriod/Definitions/DynamicGenericCashFlowDefinition.cs b/src/Domain/Models/MultiPeriod/Definitions/DynamicGenericCashFlowDefinition.cs new file mode 100644 index 00000000..08147e1e --- /dev/null +++ b/src/Domain/Models/MultiPeriod/Definitions/DynamicGenericCashFlowDefinition.cs @@ -0,0 +1,43 @@ +using Domain.Contracts; +using Domain.Enums; +using PensionCoach.Tools.CommonTypes.Tax; + +namespace Domain.Models.MultiPeriod.Definitions +{ + public record DynamicGenericCashFlowDefinition : IStaticCashFlowDefinition + { + /// + /// Gets or sets the header properties + /// + public CashFlowHeader Header { get; set; } + + /// + /// Gets or sets the date of process. + /// + /// + /// The date of process. + /// + public DateTime DateOfProcess { get; set; } + + public int NumberOfPeriods { get; set; } + + /// + /// Fraction of the available capital which is transferred from the source to the target account. + /// + public decimal TransferQuota { get; set; } + + public FrequencyType Frequency { get; set; } + + /// + /// Gets the flow. Source account is cleared and moved to target account. + /// + /// + /// The flow. + /// + public FlowPair Flow { get; set; } + + public bool IsTaxable { get; set; } + + public TaxType TaxType { get; set; } + } +} diff --git a/src/Domain/Models/MultiPeriod/Definitions/FixedTransferAmountDefinition.cs b/src/Domain/Models/MultiPeriod/Definitions/FixedTransferAmountDefinition.cs new file mode 100644 index 00000000..15d519fa --- /dev/null +++ b/src/Domain/Models/MultiPeriod/Definitions/FixedTransferAmountDefinition.cs @@ -0,0 +1,32 @@ +using Domain.Contracts; +using PensionCoach.Tools.CommonTypes.Tax; + +namespace Domain.Models.MultiPeriod.Definitions +{ + public record FixedTransferAmountDefinition : ICompositeCashFlowDefinition + { + /// + /// Gets or sets the header properties + /// + public CashFlowHeader Header { get; set; } + + /// + /// Gets or sets the date of transfer. + /// + /// + /// The date of transfer. + /// + public DateTime DateOfProcess { get; set; } + + /// + /// Amount which is transferred from the source to the target + /// + public decimal TransferAmount { get; set; } + + public FlowPair Flow { get; set; } + + public bool IsTaxable { get; set; } + + public TaxType TaxType { get; set; } + } +} diff --git a/src/Domain/Models/MultiPeriod/Definitions/InvestmentPortfolioDefinition.cs b/src/Domain/Models/MultiPeriod/Definitions/InvestmentPortfolioDefinition.cs new file mode 100644 index 00000000..7a13f05a --- /dev/null +++ b/src/Domain/Models/MultiPeriod/Definitions/InvestmentPortfolioDefinition.cs @@ -0,0 +1,59 @@ +using Domain.Contracts; + +namespace Domain.Models.MultiPeriod.Definitions +{ + /// + /// This class represents an investment portfolio definition. The portfolio has two cash flow streams: + /// 1. capital growth on the initial investment which is subject to wealth tax and + /// 2. interest payments on the balance which is subject to income tax. + /// + public record InvestmentPortfolioDefinition : ICompositeCashFlowDefinition + { + /// + /// Gets or sets the header properties + /// + public CashFlowHeader Header { get; set; } + + /// + /// Gets or sets the date of process. + /// + /// + /// The date of process. + /// + public DateTime DateOfProcess { get; set; } + + /// + /// Gets or sets the initial investment amount. The initial investment does not change over time. + /// + /// + /// The initial investment amount. + /// + public decimal InitialInvestment { get; set; } + + /// + /// Gets the recurring investment. + /// + /// + /// The recurring investment. + /// + public RecurringInvestment RecurringInvestment { get; set; } + + /// + /// Gets or sets the net income rate. + /// + public decimal NetIncomeRate { get; set; } + + /// + /// Gets or sets the net rate of return of the capital. + /// + public decimal NetCapitalGrowthRate { get; set; } + + /// + /// Get or sets the investment period beginning with the begin year and repeating count times. + /// + /// + /// The investment period. + /// + public InvestmentPeriod InvestmentPeriod { get; set; } + } +} diff --git a/src/Domain/Models/MultiPeriod/Definitions/PurchaseInsuranceYearsPaymentsDefinition.cs b/src/Domain/Models/MultiPeriod/Definitions/PurchaseInsuranceYearsPaymentsDefinition.cs new file mode 100644 index 00000000..1aa21886 --- /dev/null +++ b/src/Domain/Models/MultiPeriod/Definitions/PurchaseInsuranceYearsPaymentsDefinition.cs @@ -0,0 +1,33 @@ +using Domain.Contracts; + +namespace Domain.Models.MultiPeriod.Definitions +{ + public record PurchaseInsuranceYearsPaymentsDefinition : ICompositeCashFlowDefinition + { + /// + /// Gets or sets the date of start. + /// + /// + /// The date of start. + /// + public DateTime DateOfStart { get; set; } + + /// + /// Gets or sets the net growth rate. + /// + /// + /// The net growth rate. + /// + public decimal NetGrowthRate { get; set; } + + /// + /// Gets or sets the yearly amount. + /// + public decimal YearlyAmount { get; set; } + + /// + /// Gets or sets the number of investments (one per year). + /// + public int NumberOfInvestments { get; set; } + } +} diff --git a/src/Domain/Models/MultiPeriod/Definitions/RelativeTransferAmountDefinition.cs b/src/Domain/Models/MultiPeriod/Definitions/RelativeTransferAmountDefinition.cs new file mode 100644 index 00000000..f8721fc7 --- /dev/null +++ b/src/Domain/Models/MultiPeriod/Definitions/RelativeTransferAmountDefinition.cs @@ -0,0 +1,32 @@ +using Domain.Contracts; +using PensionCoach.Tools.CommonTypes.Tax; + +namespace Domain.Models.MultiPeriod.Definitions +{ + public record RelativeTransferAmountDefinition : ICompositeCashFlowDefinition + { + /// + /// Gets or sets the header properties + /// + public CashFlowHeader Header { get; set; } + + /// + /// Gets or sets the date of transfer. + /// + /// + /// The date of transfer. + /// + public DateTime DateOfProcess { get; set; } + + /// + /// Amount which is transferred from the source to the target + /// + public decimal TransferRatio { get; set; } + + public FlowPair Flow { get; set; } + + public bool IsTaxable { get; set; } + + public TaxType TaxType { get; set; } + } +} diff --git a/src/Domain/Models/MultiPeriod/Definitions/SalaryPaymentsDefinition.cs b/src/Domain/Models/MultiPeriod/Definitions/SalaryPaymentsDefinition.cs new file mode 100644 index 00000000..301a79f1 --- /dev/null +++ b/src/Domain/Models/MultiPeriod/Definitions/SalaryPaymentsDefinition.cs @@ -0,0 +1,26 @@ +using Domain.Contracts; + +namespace Domain.Models.MultiPeriod.Definitions +{ + public record SalaryPaymentsDefinition : ICompositeCashFlowDefinition + { + /// + /// Gets or sets the net growth rate. + /// + /// + /// The net growth rate. + /// + public decimal NetGrowthRate { get; set; } + + /// + /// Gets or sets the yearly investment amount. + /// + public decimal YearlyAmount { get; set; } + + /// + /// Gets or sets the end of period. It marks not the final salary payment + /// but is used the calculate the pro-rated yearly amount in the last payment period. + /// + public DateTime DateOfEndOfPeriod { get; set; } + } +} diff --git a/src/Domain/Models/MultiPeriod/Definitions/SetupAccountDefinition.cs b/src/Domain/Models/MultiPeriod/Definitions/SetupAccountDefinition.cs new file mode 100644 index 00000000..8d55d2cd --- /dev/null +++ b/src/Domain/Models/MultiPeriod/Definitions/SetupAccountDefinition.cs @@ -0,0 +1,33 @@ +using Domain.Contracts; + +namespace Domain.Models.MultiPeriod.Definitions +{ + public record SetupAccountDefinition : ICompositeCashFlowDefinition + { + /// + /// Gets or sets the initial wealth. + /// + /// + /// The initial wealth. + /// + public decimal InitialWealth { get; set; } + + /// + /// Gets or sets the initial occupational pension assets (Berufliche Vorsorge). + /// + public decimal InitialOccupationalPensionAssets { get; set; } + + /// + /// Gets or sets the initial third pillar assets (3A Konto). + /// + public decimal InitialThirdPillarAssets { get; set; } + + /// + /// Gets or sets the initial investment assets. Investment assets have two + /// cash flow streams: + /// 1. Capital growth + /// 2. Interest payments (eg. dividends) which are paid out (not accumulated) + /// + public decimal InitialInvestmentAssets { get; set; } + } +} diff --git a/src/Domain/Models/MultiPeriod/Definitions/StaticGenericCashFlowDefinition.cs b/src/Domain/Models/MultiPeriod/Definitions/StaticGenericCashFlowDefinition.cs new file mode 100644 index 00000000..f9eaa409 --- /dev/null +++ b/src/Domain/Models/MultiPeriod/Definitions/StaticGenericCashFlowDefinition.cs @@ -0,0 +1,65 @@ +using Domain.Contracts; +using PensionCoach.Tools.CommonTypes.Tax; + +namespace Domain.Models.MultiPeriod.Definitions +{ + public record StaticGenericCashFlowDefinition : IStaticCashFlowDefinition + { + /// + /// Gets or sets the header properties + /// + public CashFlowHeader Header { get; set; } + + /// + /// Gets or sets the date of process. + /// + /// + /// The date of process. + /// + public DateTime DateOfProcess { get; set; } + + /// + /// Gets or sets the initial amount. + /// + /// + /// The initial amount. + /// + public decimal InitialAmount { get; set; } + + /// + /// Gets the recurring investment. + /// + /// + /// The recurring investment. + /// + public RecurringInvestment RecurringInvestment { get; set; } + + /// + /// Gets or sets the net growth rate. + /// + /// + /// The net growth rate. + /// + public decimal NetGrowthRate { get; set; } + + /// + /// Get or sets the investment period beginning with the begin year and repeating count times. + /// + /// + /// The investment period. + /// + public InvestmentPeriod InvestmentPeriod { get; set; } + + /// + /// Gets the flow. Source account is cleared and moved to target account. + /// + /// + /// The flow. + /// + public FlowPair Flow { get; set; } + + public bool IsTaxable { get; set; } + + public TaxType TaxType { get; set; } + } +} diff --git a/src/Domain/Models/MultiPeriod/Definitions/ThirdPillarPaymentsDefinition.cs b/src/Domain/Models/MultiPeriod/Definitions/ThirdPillarPaymentsDefinition.cs new file mode 100644 index 00000000..c249ae4e --- /dev/null +++ b/src/Domain/Models/MultiPeriod/Definitions/ThirdPillarPaymentsDefinition.cs @@ -0,0 +1,33 @@ +using Domain.Contracts; + +namespace Domain.Models.MultiPeriod.Definitions +{ + public record ThirdPillarPaymentsDefinition : ICompositeCashFlowDefinition + { + /// + /// Gets or sets the date of start. + /// + /// + /// The date of start. + /// + public DateTime DateOfStart { get; set; } + + /// + /// Gets or sets the net growth rate. + /// + /// + /// The net growth rate. + /// + public decimal NetGrowthRate { get; set; } + + /// + /// Gets or sets the yearly investment amount. + /// + public decimal YearlyAmount { get; set; } + + /// + /// Gets or sets the number of investments (one per year). + /// + public int NumberOfInvestments { get; set; } + } +} diff --git a/src/CommonTypes/MultiPeriod/FlowPair.cs b/src/Domain/Models/MultiPeriod/FlowPair.cs similarity index 52% rename from src/CommonTypes/MultiPeriod/FlowPair.cs rename to src/Domain/Models/MultiPeriod/FlowPair.cs index 4ad4159c..698b3bdd 100644 --- a/src/CommonTypes/MultiPeriod/FlowPair.cs +++ b/src/Domain/Models/MultiPeriod/FlowPair.cs @@ -1,4 +1,6 @@ -namespace PensionCoach.Tools.CommonTypes.MultiPeriod +using Domain.Enums; + +namespace Domain.Models.MultiPeriod { public record FlowPair(AccountType Source, AccountType Target); } diff --git a/src/CommonTypes/MultiPeriod/InvestmentPeriod.cs b/src/Domain/Models/MultiPeriod/InvestmentPeriod.cs similarity index 68% rename from src/CommonTypes/MultiPeriod/InvestmentPeriod.cs rename to src/Domain/Models/MultiPeriod/InvestmentPeriod.cs index e9ab42d5..e67b82a5 100644 --- a/src/CommonTypes/MultiPeriod/InvestmentPeriod.cs +++ b/src/Domain/Models/MultiPeriod/InvestmentPeriod.cs @@ -1,4 +1,4 @@ -namespace PensionCoach.Tools.CommonTypes.MultiPeriod +namespace Domain.Models.MultiPeriod { public record InvestmentPeriod { diff --git a/src/Domain/Models/MultiPeriod/MultiPeriodCalculationResult.cs b/src/Domain/Models/MultiPeriod/MultiPeriodCalculationResult.cs new file mode 100644 index 00000000..259901e9 --- /dev/null +++ b/src/Domain/Models/MultiPeriod/MultiPeriodCalculationResult.cs @@ -0,0 +1,14 @@ +using Domain.Models.Cashflows; + +namespace Domain.Models.MultiPeriod; + +public record MultiPeriodCalculationResult +{ + public int StartingYear { get; set; } + + public int NumberOfPeriods { get; set; } + + public IEnumerable Accounts{ get; set; } + + public AccountTransactionResultHolder Transactions { get; set; } +} diff --git a/src/Domain/Models/MultiPeriod/MultiPeriodCalculatorPerson.cs b/src/Domain/Models/MultiPeriod/MultiPeriodCalculatorPerson.cs new file mode 100644 index 00000000..36d1fb3b --- /dev/null +++ b/src/Domain/Models/MultiPeriod/MultiPeriodCalculatorPerson.cs @@ -0,0 +1,32 @@ +using Domain.Enums; + +namespace Domain.Models.MultiPeriod; + +public record MultiPeriodCalculatorPerson +{ + public string Name { get; set; } + + public DateTime DateOfBirth { get; set; } + + public Gender Gender { get; set; } + + public CivilStatus CivilStatus { get; set; } + + public int NumberOfChildren { get; set; } + + public ReligiousGroupType ReligiousGroupType { get; set; } + + public ReligiousGroupType? PartnerReligiousGroupType { get; set; } + + public Canton Canton { get; set; } + + public int MunicipalityId { get; set; } + + public decimal Income { get; set; } + + public decimal Wealth { get; set; } + + public (decimal Pillar3a, decimal PensionPlan) CapitalBenefits { get; set; } + + +} diff --git a/src/CommonTypes/MultiPeriod/RecurringInvestment.cs b/src/Domain/Models/MultiPeriod/RecurringInvestment.cs similarity index 68% rename from src/CommonTypes/MultiPeriod/RecurringInvestment.cs rename to src/Domain/Models/MultiPeriod/RecurringInvestment.cs index 68317fa4..c600b9d3 100644 --- a/src/CommonTypes/MultiPeriod/RecurringInvestment.cs +++ b/src/Domain/Models/MultiPeriod/RecurringInvestment.cs @@ -1,4 +1,6 @@ -namespace PensionCoach.Tools.CommonTypes.MultiPeriod +using Domain.Enums; + +namespace Domain.Models.MultiPeriod { public record RecurringInvestment { diff --git a/src/CommonTypes/MultiPeriod/SinglePeriodCalculationResult.cs b/src/Domain/Models/MultiPeriod/SinglePeriodCalculationResult.cs similarity index 73% rename from src/CommonTypes/MultiPeriod/SinglePeriodCalculationResult.cs rename to src/Domain/Models/MultiPeriod/SinglePeriodCalculationResult.cs index c7677b54..b64c2eb4 100644 --- a/src/CommonTypes/MultiPeriod/SinglePeriodCalculationResult.cs +++ b/src/Domain/Models/MultiPeriod/SinglePeriodCalculationResult.cs @@ -1,4 +1,6 @@ -namespace PensionCoach.Tools.CommonTypes.MultiPeriod +using Domain.Enums; + +namespace Domain.Models.MultiPeriod { public class SinglePeriodCalculationResult { diff --git a/src/Tax.Data.Abstractions/Models/MunicipalityEntity.cs b/src/Domain/Models/Municipality/MunicipalityEntity.cs similarity index 92% rename from src/Tax.Data.Abstractions/Models/MunicipalityEntity.cs rename to src/Domain/Models/Municipality/MunicipalityEntity.cs index aebb9f2d..e8630bd0 100644 --- a/src/Tax.Data.Abstractions/Models/MunicipalityEntity.cs +++ b/src/Domain/Models/Municipality/MunicipalityEntity.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations.Schema; -namespace Tax.Data.Abstractions.Models +namespace Domain.Models.Municipality { public class MunicipalityEntity { diff --git a/src/CommonTypes/Municipality/MunicipalityModel.cs b/src/Domain/Models/Municipality/MunicipalityModel.cs similarity index 80% rename from src/CommonTypes/Municipality/MunicipalityModel.cs rename to src/Domain/Models/Municipality/MunicipalityModel.cs index c7b09864..2d8e002f 100644 --- a/src/CommonTypes/Municipality/MunicipalityModel.cs +++ b/src/Domain/Models/Municipality/MunicipalityModel.cs @@ -1,6 +1,6 @@ -using System; +using Domain.Enums; -namespace PensionCoach.Tools.CommonTypes.Municipality +namespace Domain.Models.Municipality { public class MunicipalityModel { diff --git a/src/CommonTypes/Municipality/MunicipalitySearchFilter.cs b/src/Domain/Models/Municipality/MunicipalitySearchFilter.cs similarity index 90% rename from src/CommonTypes/Municipality/MunicipalitySearchFilter.cs rename to src/Domain/Models/Municipality/MunicipalitySearchFilter.cs index 3da23aa2..b4f2e445 100644 --- a/src/CommonTypes/Municipality/MunicipalitySearchFilter.cs +++ b/src/Domain/Models/Municipality/MunicipalitySearchFilter.cs @@ -1,4 +1,6 @@ -namespace PensionCoach.Tools.CommonTypes.Municipality +using Domain.Enums; + +namespace Domain.Models.Municipality { public class MunicipalitySearchFilter { diff --git a/src/PostOpenApi.Client/Models/ZipModel.cs b/src/Domain/Models/Municipality/ZipModel.cs similarity index 84% rename from src/PostOpenApi.Client/Models/ZipModel.cs rename to src/Domain/Models/Municipality/ZipModel.cs index 5faf746c..865faa88 100644 --- a/src/PostOpenApi.Client/Models/ZipModel.cs +++ b/src/Domain/Models/Municipality/ZipModel.cs @@ -1,4 +1,4 @@ -namespace PensionCoach.Tools.PostOpenApi.Models +namespace Domain.Models.Municipality { public class ZipModel { diff --git a/src/Tax.Tools.Comparison.Domain/TransferInCapitalBenefitsScenarioModel.cs b/src/Domain/Models/Scenarios/CapitalBenefitTransferInsScenarioModel.cs similarity index 82% rename from src/Tax.Tools.Comparison.Domain/TransferInCapitalBenefitsScenarioModel.cs rename to src/Domain/Models/Scenarios/CapitalBenefitTransferInsScenarioModel.cs index df9847df..7ba2e02c 100644 --- a/src/Tax.Tools.Comparison.Domain/TransferInCapitalBenefitsScenarioModel.cs +++ b/src/Domain/Models/Scenarios/CapitalBenefitTransferInsScenarioModel.cs @@ -1,8 +1,8 @@ -using System.Collections.Generic; +using Domain.Models.Tax; -namespace PensionCoach.Tools.TaxComparison; +namespace Domain.Models.Scenarios; -public class TransferInCapitalBenefitsScenarioModel +public class CapitalBenefitTransferInsScenarioModel { public IReadOnlyCollection TransferIns { get; set; } diff --git a/src/Domain/Models/Scenarios/ScenarioCalculationResult.cs b/src/Domain/Models/Scenarios/ScenarioCalculationResult.cs new file mode 100644 index 00000000..81e866fd --- /dev/null +++ b/src/Domain/Models/Scenarios/ScenarioCalculationResult.cs @@ -0,0 +1,21 @@ +using Domain.Models.Cashflows; +using Domain.Models.MultiPeriod; + +namespace Domain.Models.Scenarios; + +public record ScenarioCalculationResult +{ + public int StartingYear { get; set; } + + public int NumberOfPeriods { get; set; } + + public IEnumerable DeltaSeries { get; set; } + + public IEnumerable BenchmarkSeries { get; set; } + + public IEnumerable ScenarioSeries { get; set; } + + public AccountTransactionResultHolder BenchmarkTransactions { get; set; } + + public AccountTransactionResultHolder ScenarioTransactions { get; set; } +} diff --git a/src/Domain/Models/Scenarios/ThirdPillarVersusSelfInvestmentScenarioModel.cs b/src/Domain/Models/Scenarios/ThirdPillarVersusSelfInvestmentScenarioModel.cs new file mode 100644 index 00000000..4f1863f7 --- /dev/null +++ b/src/Domain/Models/Scenarios/ThirdPillarVersusSelfInvestmentScenarioModel.cs @@ -0,0 +1,32 @@ +namespace Domain.Models.Scenarios +{ + public class ThirdPillarVersusSelfInvestmentScenarioModel + { + /// + /// Final transfer-in year. At this year the third-pillar account is closed and the money is transferred to the wealth. + /// The capital withdrawn from the third pillar account is taxed by the capital benefits tax. + /// + public int FinalYear { get; set; } + + /// + /// The amount invested in the third pillar or self-investment account. + /// First investment is done in the calculation year up to final year. + /// + public decimal InvestmentAmount { get; set; } + + /// + /// Gets or sets the net growth rate for the investment. The investment capital is fully taxed by the wealth tax. + /// + public decimal InvestmentNetGrowthRate { get; set; } + + /// + /// Gets or sets the net income yield for the investment. Income types are dividends and interests which are subject to income tax. + /// + public decimal InvestmentNetIncomeYield { get; set; } + + /// + /// Gets or sets the net growth rate for the third pillar account. + /// + public decimal ThirdPillarNetGrowthRate { get; set; } + } +} diff --git a/src/TaxCalculator.Abstractions/Models/BasisTaxResult.cs b/src/Domain/Models/Tax/BasisTaxResult.cs similarity index 68% rename from src/TaxCalculator.Abstractions/Models/BasisTaxResult.cs rename to src/Domain/Models/Tax/BasisTaxResult.cs index e7c86ddf..44f9e24d 100644 --- a/src/TaxCalculator.Abstractions/Models/BasisTaxResult.cs +++ b/src/Domain/Models/Tax/BasisTaxResult.cs @@ -1,8 +1,8 @@ -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models +namespace Application.Tax.Proprietary.Abstractions.Models { public class BasisTaxResult { public decimal DeterminingFactorTaxableAmount { get; set; } public decimal TaxAmount { get; set; } } -} \ No newline at end of file +} diff --git a/src/CommonTypes/Tax/CapitalBenefitTaxPerson.cs b/src/Domain/Models/Tax/CapitalBenefitTaxPerson.cs similarity index 71% rename from src/CommonTypes/Tax/CapitalBenefitTaxPerson.cs rename to src/Domain/Models/Tax/CapitalBenefitTaxPerson.cs index e309381f..47686fb5 100644 --- a/src/CommonTypes/Tax/CapitalBenefitTaxPerson.cs +++ b/src/Domain/Models/Tax/CapitalBenefitTaxPerson.cs @@ -1,4 +1,4 @@ -namespace PensionCoach.Tools.CommonTypes.Tax +namespace Domain.Models.Tax { public record CapitalBenefitTaxPerson : TaxPersonBasic { diff --git a/src/TaxCalculator.Abstractions/Models/CapitalBenefitTaxResult.cs b/src/Domain/Models/Tax/CapitalBenefitTaxResult.cs similarity index 71% rename from src/TaxCalculator.Abstractions/Models/CapitalBenefitTaxResult.cs rename to src/Domain/Models/Tax/CapitalBenefitTaxResult.cs index 46575c64..bc61ce85 100644 --- a/src/TaxCalculator.Abstractions/Models/CapitalBenefitTaxResult.cs +++ b/src/Domain/Models/Tax/CapitalBenefitTaxResult.cs @@ -1,4 +1,6 @@ -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models +using Application.Tax.Proprietary.Abstractions.Models; + +namespace Domain.Models.Tax { public class CapitalBenefitTaxResult { @@ -9,8 +11,8 @@ public class CapitalBenefitTaxResult public decimal MunicipalityTaxAmount => MunicipalityRate / 100M * BasisTax.TaxAmount; public decimal CantonTaxAmount => CantonRate / 100M * BasisTax.TaxAmount; - public decimal ChurchTaxAmount => ChurchTax.TaxAmount.IfNone(0) + - ChurchTax.TaxAmountPartner.IfNone(0); + public decimal ChurchTaxAmount => (ChurchTax.TaxAmount ?? 0) + + (ChurchTax.TaxAmountPartner ?? 0); public decimal TotalTaxAmount => MunicipalityTaxAmount + CantonTaxAmount + diff --git a/src/Domain/Models/Tax/ChurchTaxResult.cs b/src/Domain/Models/Tax/ChurchTaxResult.cs new file mode 100644 index 00000000..c95023a2 --- /dev/null +++ b/src/Domain/Models/Tax/ChurchTaxResult.cs @@ -0,0 +1,11 @@ +namespace Domain.Models.Tax +{ + public class ChurchTaxResult + { + public decimal? TaxAmount { get; set; } + + public decimal? TaxAmountPartner { get; set; } + + public decimal TaxRate { get; set; } + } +} diff --git a/src/Tax.Data.Abstractions/Models/FederalTaxTariffModel.cs b/src/Domain/Models/Tax/FederalTaxTariffModel.cs similarity index 89% rename from src/Tax.Data.Abstractions/Models/FederalTaxTariffModel.cs rename to src/Domain/Models/Tax/FederalTaxTariffModel.cs index 188239ac..9272b7ba 100644 --- a/src/Tax.Data.Abstractions/Models/FederalTaxTariffModel.cs +++ b/src/Domain/Models/Tax/FederalTaxTariffModel.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations.Schema; - -namespace Tax.Data.Abstractions.Models +namespace Domain.Models.Tax { public class FederalTaxTariffModel { @@ -23,4 +22,4 @@ public class FederalTaxTariffModel [Column("Tariftyp")] public int TariffType { get; set; } } -} \ No newline at end of file +} diff --git a/src/TaxCalculator.Abstractions/Models/FullCapitalBenefitTaxResult.cs b/src/Domain/Models/Tax/FullCapitalBenefitTaxResult.cs similarity index 73% rename from src/TaxCalculator.Abstractions/Models/FullCapitalBenefitTaxResult.cs rename to src/Domain/Models/Tax/FullCapitalBenefitTaxResult.cs index cbfa97f2..49c591c8 100644 --- a/src/TaxCalculator.Abstractions/Models/FullCapitalBenefitTaxResult.cs +++ b/src/Domain/Models/Tax/FullCapitalBenefitTaxResult.cs @@ -1,4 +1,6 @@ -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models +using Application.Tax.Proprietary.Abstractions.Models; + +namespace Domain.Models.Tax { public class FullCapitalBenefitTaxResult { @@ -6,4 +8,4 @@ public class FullCapitalBenefitTaxResult public BasisTaxResult FederalResult { get; set; } public decimal TotalTaxAmount => StateResult.TotalTaxAmount + FederalResult.TaxAmount; } -} \ No newline at end of file +} diff --git a/src/Domain/Models/Tax/FullTaxResult.cs b/src/Domain/Models/Tax/FullTaxResult.cs new file mode 100644 index 00000000..e6b98472 --- /dev/null +++ b/src/Domain/Models/Tax/FullTaxResult.cs @@ -0,0 +1,10 @@ +using Application.Tax.Proprietary.Abstractions.Models; + +namespace Domain.Models.Tax; + +public class FullTaxResult +{ + public StateTaxResult StateTaxResult { get; set; } + public BasisTaxResult FederalTaxResult { get; set; } + public decimal TotalTaxAmount => StateTaxResult.TotalTaxAmount + FederalTaxResult.TaxAmount; +} diff --git a/src/Domain/Models/Tax/MarginalTaxInfo.cs b/src/Domain/Models/Tax/MarginalTaxInfo.cs new file mode 100644 index 00000000..d80f1a62 --- /dev/null +++ b/src/Domain/Models/Tax/MarginalTaxInfo.cs @@ -0,0 +1,4 @@ +namespace Domain.Models.Tax +{ + public record MarginalTaxInfo(decimal Salary, decimal Rate, decimal TotalTaxAmount); +} diff --git a/src/Domain/Models/Tax/Person/BasisTaxPerson.cs b/src/Domain/Models/Tax/Person/BasisTaxPerson.cs new file mode 100644 index 00000000..ba25c0ab --- /dev/null +++ b/src/Domain/Models/Tax/Person/BasisTaxPerson.cs @@ -0,0 +1,6 @@ +namespace Domain.Models.Tax.Person; + +public record BasisTaxPerson : TaxPersonBasic +{ + public decimal TaxableAmount { get; set; } +} diff --git a/src/Domain/Models/Tax/Person/ChurchTaxPerson.cs b/src/Domain/Models/Tax/Person/ChurchTaxPerson.cs new file mode 100644 index 00000000..ea976383 --- /dev/null +++ b/src/Domain/Models/Tax/Person/ChurchTaxPerson.cs @@ -0,0 +1,4 @@ +namespace Domain.Models.Tax.Person +{ + public record ChurchTaxPerson : TaxPersonBasic; +} diff --git a/src/Domain/Models/Tax/Person/FederalTaxPerson.cs b/src/Domain/Models/Tax/Person/FederalTaxPerson.cs new file mode 100644 index 00000000..7e80b2d2 --- /dev/null +++ b/src/Domain/Models/Tax/Person/FederalTaxPerson.cs @@ -0,0 +1,7 @@ +namespace Domain.Models.Tax.Person +{ + public record FederalTaxPerson : TaxPersonBasic + { + public decimal TaxableAmount { get; set; } + } +} diff --git a/src/Domain/Models/Tax/Person/PollTaxPerson.cs b/src/Domain/Models/Tax/Person/PollTaxPerson.cs new file mode 100644 index 00000000..03bb691e --- /dev/null +++ b/src/Domain/Models/Tax/Person/PollTaxPerson.cs @@ -0,0 +1,4 @@ +namespace Domain.Models.Tax.Person +{ + public record PollTaxPerson : TaxPersonBasic; +} diff --git a/src/Domain/Models/Tax/SingleTransferInModel.cs b/src/Domain/Models/Tax/SingleTransferInModel.cs new file mode 100644 index 00000000..d409b728 --- /dev/null +++ b/src/Domain/Models/Tax/SingleTransferInModel.cs @@ -0,0 +1,4 @@ +namespace Domain.Models.Tax +{ + public record SingleTransferInModel(decimal Amount, DateTime DateOfTransferIn); +} diff --git a/src/TaxCalculator.Abstractions/Models/StateTaxResult.cs b/src/Domain/Models/Tax/StateTaxResult.cs similarity index 62% rename from src/TaxCalculator.Abstractions/Models/StateTaxResult.cs rename to src/Domain/Models/Tax/StateTaxResult.cs index 9122bec9..8ba98eb1 100644 --- a/src/TaxCalculator.Abstractions/Models/StateTaxResult.cs +++ b/src/Domain/Models/Tax/StateTaxResult.cs @@ -1,7 +1,6 @@ -using LanguageExt; +using Application.Tax.Proprietary.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models +namespace Domain.Models.Tax { public class StateTaxResult { @@ -18,17 +17,17 @@ public class StateTaxResult public decimal MunicipalityTaxAmount => MunicipalityRate / 100M * (BasisIncomeTax.TaxAmount + BasisWealthTax.TaxAmount); public decimal CantonTaxAmount => CantonRate / 100M * (BasisIncomeTax.TaxAmount + BasisWealthTax.TaxAmount); - public decimal ChurchTaxAmount => ChurchTax.TaxAmount.IfNone(0) + - ChurchTax.TaxAmountPartner.IfNone(0); + public decimal ChurchTaxAmount => (ChurchTax.TaxAmount ?? 0) + + (ChurchTax.TaxAmountPartner ?? 0); public decimal TotalWealthTax => BasisWealthTax.TaxAmount * (MunicipalityRate / 100M + 1); public decimal TotalIncomeTax => BasisIncomeTax.TaxAmount * (MunicipalityRate / 100M + 1) + ChurchTaxAmount; public decimal TotalTaxAmount => MunicipalityTaxAmount + - CantonTaxAmount + - ChurchTaxAmount + - PollTaxAmount.IfNone(0); - public Option PollTaxAmount { get; set; } + CantonTaxAmount + + ChurchTaxAmount + + PollTaxAmount ?? 0m; + public decimal? PollTaxAmount { get; set; } } } diff --git a/src/CommonTypes/Tax/TaxAmountDetail.cs b/src/Domain/Models/Tax/TaxAmountDetail.cs similarity index 81% rename from src/CommonTypes/Tax/TaxAmountDetail.cs rename to src/Domain/Models/Tax/TaxAmountDetail.cs index 6f42d274..11508d47 100644 --- a/src/CommonTypes/Tax/TaxAmountDetail.cs +++ b/src/Domain/Models/Tax/TaxAmountDetail.cs @@ -1,4 +1,4 @@ -namespace PensionCoach.Tools.CommonTypes.Tax +namespace Domain.Models.Tax { public class TaxAmountDetail { diff --git a/src/Tax.Data.Abstractions/TaxFilterModel.cs b/src/Domain/Models/Tax/TaxFilterModel.cs similarity index 74% rename from src/Tax.Data.Abstractions/TaxFilterModel.cs rename to src/Domain/Models/Tax/TaxFilterModel.cs index 2f37db0f..b5c66646 100644 --- a/src/Tax.Data.Abstractions/TaxFilterModel.cs +++ b/src/Domain/Models/Tax/TaxFilterModel.cs @@ -1,8 +1,8 @@ -namespace Tax.Data.Abstractions +namespace Domain.Models.Tax { public class TaxFilterModel { public int Year { get; set; } public string Canton { get; set; } } -} \ No newline at end of file +} diff --git a/src/Domain/Models/Tax/TaxPerson.cs b/src/Domain/Models/Tax/TaxPerson.cs new file mode 100644 index 00000000..dc420141 --- /dev/null +++ b/src/Domain/Models/Tax/TaxPerson.cs @@ -0,0 +1,8 @@ +namespace Domain.Models.Tax; + +public record TaxPerson : TaxPersonBasic +{ + public decimal TaxableIncome { get; set; } + public decimal TaxableFederalIncome { get; set; } + public decimal TaxableWealth { get; set; } +} diff --git a/src/CommonTypes/Tax/TaxPersonBasic.cs b/src/Domain/Models/Tax/TaxPersonBasic.cs similarity index 83% rename from src/CommonTypes/Tax/TaxPersonBasic.cs rename to src/Domain/Models/Tax/TaxPersonBasic.cs index 12ae9e94..55a9dbec 100644 --- a/src/CommonTypes/Tax/TaxPersonBasic.cs +++ b/src/Domain/Models/Tax/TaxPersonBasic.cs @@ -1,4 +1,6 @@ -namespace PensionCoach.Tools.CommonTypes.Tax +using Domain.Enums; + +namespace Domain.Models.Tax { public record TaxPersonBasic { diff --git a/src/CommonTypes/Tax/TaxRateDetails.cs b/src/Domain/Models/Tax/TaxRateDetails.cs similarity index 77% rename from src/CommonTypes/Tax/TaxRateDetails.cs rename to src/Domain/Models/Tax/TaxRateDetails.cs index 5f588788..71c31712 100644 --- a/src/CommonTypes/Tax/TaxRateDetails.cs +++ b/src/Domain/Models/Tax/TaxRateDetails.cs @@ -1,4 +1,4 @@ -namespace PensionCoach.Tools.CommonTypes.Tax +namespace Domain.Models.Tax { public class TaxRateDetails { diff --git a/src/Tax.Data.Abstractions/Models/TaxRateEntity.cs b/src/Domain/Models/Tax/TaxRateEntity.cs similarity index 92% rename from src/Tax.Data.Abstractions/Models/TaxRateEntity.cs rename to src/Domain/Models/Tax/TaxRateEntity.cs index 5a74d83f..26c740a2 100644 --- a/src/Tax.Data.Abstractions/Models/TaxRateEntity.cs +++ b/src/Domain/Models/Tax/TaxRateEntity.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations.Schema; - -namespace Tax.Data.Abstractions.Models +namespace Domain.Models.Tax { public class TaxRateEntity { @@ -38,4 +37,4 @@ public class TaxRateEntity [Column("Personensteuer")] public decimal? PollTaxAmount { get; set; } } -} \ No newline at end of file +} diff --git a/src/CommonTypes/Tax/TaxSupportedMunicipalityModel.cs b/src/Domain/Models/Tax/TaxSupportedMunicipalityModel.cs similarity index 89% rename from src/CommonTypes/Tax/TaxSupportedMunicipalityModel.cs rename to src/Domain/Models/Tax/TaxSupportedMunicipalityModel.cs index bc6fe9ae..d7e4b344 100644 --- a/src/CommonTypes/Tax/TaxSupportedMunicipalityModel.cs +++ b/src/Domain/Models/Tax/TaxSupportedMunicipalityModel.cs @@ -1,4 +1,6 @@ -namespace PensionCoach.Tools.CommonTypes.Tax +using Domain.Enums; + +namespace Domain.Models.Tax { public record TaxSupportedMunicipalityModel { diff --git a/src/Tax.Data.Abstractions/Models/TaxTariffModel.cs b/src/Domain/Models/Tax/TaxTariffModel.cs similarity index 91% rename from src/Tax.Data.Abstractions/Models/TaxTariffModel.cs rename to src/Domain/Models/Tax/TaxTariffModel.cs index 3df02cc6..f2a0ad2c 100644 --- a/src/Tax.Data.Abstractions/Models/TaxTariffModel.cs +++ b/src/Domain/Models/Tax/TaxTariffModel.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations.Schema; - -namespace Tax.Data.Abstractions.Models +namespace Domain.Models.Tax { public class TaxTariffModel { @@ -29,4 +28,4 @@ public class TaxTariffModel [Column("Steuertyp")] public int TaxType { get; set; } } -} \ No newline at end of file +} diff --git a/src/Tax.Data.Abstractions/Models/ZipEntity.cs b/src/Domain/Models/Tax/ZipEntity.cs similarity index 80% rename from src/Tax.Data.Abstractions/Models/ZipEntity.cs rename to src/Domain/Models/Tax/ZipEntity.cs index 688b59d4..d6327f0d 100644 --- a/src/Tax.Data.Abstractions/Models/ZipEntity.cs +++ b/src/Domain/Models/Tax/ZipEntity.cs @@ -1,7 +1,6 @@ -using System; -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations.Schema; -namespace Tax.Data.Abstractions.Models +namespace Domain.Models.Tax { public class ZipEntity { diff --git a/src/Tax.Tools.Comparison.Domain/CapitalBenefitTaxComparerResult.cs b/src/Domain/Models/TaxComparison/CapitalBenefitTaxComparerResult.cs similarity index 70% rename from src/Tax.Tools.Comparison.Domain/CapitalBenefitTaxComparerResult.cs rename to src/Domain/Models/TaxComparison/CapitalBenefitTaxComparerResult.cs index a6237a2b..1ef5f273 100644 --- a/src/Tax.Tools.Comparison.Domain/CapitalBenefitTaxComparerResult.cs +++ b/src/Domain/Models/TaxComparison/CapitalBenefitTaxComparerResult.cs @@ -1,7 +1,7 @@ -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; +using Domain.Enums; +using Domain.Models.Tax; -namespace PensionCoach.Tools.TaxComparison +namespace Domain.Models.TaxComparison { public class CapitalBenefitTaxComparerResult { diff --git a/src/Domain/Models/TaxComparison/IncomeAndWealthTaxComparerResult.cs b/src/Domain/Models/TaxComparison/IncomeAndWealthTaxComparerResult.cs new file mode 100644 index 00000000..307e8ac5 --- /dev/null +++ b/src/Domain/Models/TaxComparison/IncomeAndWealthTaxComparerResult.cs @@ -0,0 +1,20 @@ +using Domain.Enums; +using Domain.Models.Tax; + +namespace Domain.Models.TaxComparison +{ + public class IncomeAndWealthTaxComparerResult + { + public int MunicipalityId { get; set; } + + public string MunicipalityName { get; set; } + + public Canton Canton { get; set; } + + public int MaxSupportedTaxYear { get; set; } + + public FullTaxResult TaxResult { get; set; } + + public int TotalCount { get; set; } + } +} diff --git a/src/EstvTaxCalculator.Abstractions/EstvTaxCalculator.Abstractions.csproj b/src/EstvTaxCalculator.Abstractions/EstvTaxCalculator.Abstractions.csproj deleted file mode 100644 index f6fd026f..00000000 --- a/src/EstvTaxCalculator.Abstractions/EstvTaxCalculator.Abstractions.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net7.0 - PensionCoach.Tools.EstvTaxCalculators.Abstractions - PensionCoach.Tools.EstvTaxCalculators.Abstractions - enable - - - - - - - diff --git a/src/EstvTaxCalculator.Abstractions/IEstvCalculatorClient.cs b/src/EstvTaxCalculator.Abstractions/IEstvCalculatorClient.cs deleted file mode 100644 index b6ebf335..00000000 --- a/src/EstvTaxCalculator.Abstractions/IEstvCalculatorClient.cs +++ /dev/null @@ -1,13 +0,0 @@ -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; - -namespace PensionCoach.Tools.EstvTaxCalculators.Abstractions; - -public interface IEstvTaxCalculatorClient -{ - Task GetTaxLocationsAsync(string zip, string city); - - Task CalculateIncomeAndWealthTaxAsync(int taxLocationId, int taxYear, TaxPerson person); - - Task CalculateCapitalBenefitTaxAsync(int taxLocationId, int taxYear, CapitalBenefitTaxPerson person); -} diff --git a/src/EstvTaxCalculator.Abstractions/Models/ChildModel.cs b/src/EstvTaxCalculator.Abstractions/Models/ChildModel.cs deleted file mode 100644 index 5eb9751f..00000000 --- a/src/EstvTaxCalculator.Abstractions/Models/ChildModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; - -public class ChildModel -{ - public int Age { get; set; } -} diff --git a/src/EstvTaxCalculator.Abstractions/Models/SimpleCapitalTaxResult.cs b/src/EstvTaxCalculator.Abstractions/Models/SimpleCapitalTaxResult.cs deleted file mode 100644 index dcc468f4..00000000 --- a/src/EstvTaxCalculator.Abstractions/Models/SimpleCapitalTaxResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; - -public class SimpleCapitalTaxResult -{ - public int TaxCanton { get; set; } - public int TaxChurch { get; set; } - public int TaxCity { get; set; } - public int TaxFed { get; set; } - public TaxLocation Location { get; set; } -} diff --git a/src/EstvTaxCalculator.Abstractions/Models/SimpleTaxResult.cs b/src/EstvTaxCalculator.Abstractions/Models/SimpleTaxResult.cs deleted file mode 100644 index c45116e7..00000000 --- a/src/EstvTaxCalculator.Abstractions/Models/SimpleTaxResult.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; - -public class SimpleTaxResult -{ - public int IncomeSimpleTaxCanton { get; set; } - public int FortuneTaxCanton { get; set; } - public int IncomeSimpleTaxCity { get; set; } - public int IncomeTaxChurch { get; set; } - public int IncomeTaxCity { get; set; } - public int IncomeSimpleTaxFed { get; set; } - public int PersonalTax { get; set; } - public int FortuneTaxCity { get; set; } - public int FortuneSimpleTaxCanton { get; set; } - public int IncomeTaxFed { get; set; } - public int FortuneSimpleTaxCity { get; set; } - public int IncomeTaxCanton { get; set; } - public TaxLocation Location { get; set; } - public int FortuneTaxChurch { get; set; } -} diff --git a/src/EstvTaxCalculator.Abstractions/Models/TaxLocation.cs b/src/EstvTaxCalculator.Abstractions/Models/TaxLocation.cs deleted file mode 100644 index fad0f7a7..00000000 --- a/src/EstvTaxCalculator.Abstractions/Models/TaxLocation.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Text.Json.Serialization; - -namespace PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; - -public class TaxLocation -{ - [JsonPropertyName("TaxLocationID")] - public int Id { get; set; } - - public string ZipCode { get; set; } - - [JsonPropertyName("BfsID")] - public int BfsId { get; set; } - - [JsonPropertyName("CantonID")] - public int CantonId { get; set; } - - public string BfsName { get; set; } - - public string City { get; set; } - - public string Canton { get; set; } -} diff --git a/src/EstvTaxCalculator.Abstractions/Models/TaxLocationFilter.cs b/src/EstvTaxCalculator.Abstractions/Models/TaxLocationFilter.cs deleted file mode 100644 index 3c5ef9b2..00000000 --- a/src/EstvTaxCalculator.Abstractions/Models/TaxLocationFilter.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; - -public class TaxLocationRequest -{ - public string Search { get; set; } - - public int Language { get; set; } = 1; -} diff --git a/src/EstvTaxCalculatorFacade.Integration.Tests/EstvTaxCalculatorFacade.Integration.Tests.csproj b/src/EstvTaxCalculatorFacade.Integration.Tests/EstvTaxCalculatorFacade.Integration.Tests.csproj index d73fa248..793fb8f7 100644 --- a/src/EstvTaxCalculatorFacade.Integration.Tests/EstvTaxCalculatorFacade.Integration.Tests.csproj +++ b/src/EstvTaxCalculatorFacade.Integration.Tests/EstvTaxCalculatorFacade.Integration.Tests.csproj @@ -1,27 +1,26 @@  - net7.0 + net8.0 false - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - diff --git a/src/EstvTaxCalculatorFacade.Integration.Tests/EstvTaxCalculatorFacadeIntegrationTests.cs b/src/EstvTaxCalculatorFacade.Integration.Tests/EstvTaxCalculatorFacadeIntegrationTests.cs index 7db910d8..fee62dc9 100644 --- a/src/EstvTaxCalculatorFacade.Integration.Tests/EstvTaxCalculatorFacadeIntegrationTests.cs +++ b/src/EstvTaxCalculatorFacade.Integration.Tests/EstvTaxCalculatorFacadeIntegrationTests.cs @@ -1,144 +1,146 @@ using System; using System.Threading.Tasks; +using Application.Tax.Estv.Client; +using Domain.Enums; +using Domain.Models.Tax; +using Infrastructure.EstvTaxCalculator; using Microsoft.Extensions.DependencyInjection; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.EstvTaxCalculators; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions; using Snapshooter.Xunit; using Xunit; -namespace EstvTaxCalculatorFacade.Integration.Tests; - -public class EstvTaxCalculatorFacadeIntegrationTests +namespace EstvTaxCalculatorFacade.Integration.Tests { - const string EstvTaxCalculatorBaseUrl = "https://swisstaxcalculator.estv.admin.ch/delegate/ost-integration/v1/lg-proxy/operation/c3b67379_ESTV/"; - private readonly ServiceProvider provider; - - public EstvTaxCalculatorFacadeIntegrationTests() - { - IServiceCollection services = new ServiceCollection(); - - services.AddEstvTaxCalculatorClient(EstvTaxCalculatorBaseUrl); - - provider = services.BuildServiceProvider(); - } - - [Fact(DisplayName = "Tax Location")] - public async Task Should_Get_TaxLocation_By_Estv_Successfully() + public class EstvTaxCalculatorFacadeIntegrationTests { - // given - string zip = "3303"; - string city = "Zuzwil"; - - IEstvTaxCalculatorClient estvClient = provider.GetRequiredService(); + const string EstvTaxCalculatorBaseUrl = "https://swisstaxcalculator.estv.admin.ch/delegate/ost-integration/v1/lg-proxy/operation/c3b67379_ESTV/"; + private readonly ServiceProvider provider; - var result = await estvClient.GetTaxLocationsAsync(zip, city); - - Snapshot.Match(result); - } + public EstvTaxCalculatorFacadeIntegrationTests() + { + IServiceCollection services = new ServiceCollection(); + services.AddEstvTaxCalculatorClient(EstvTaxCalculatorBaseUrl); - [Theory(DisplayName = "Income and Wealth Tax")] - [InlineData(2023, 800000000, 500_000, "Married", "Other", "Other")] - [InlineData(2022, 800000000, 500_000, "Married", "Other", "Other")] - [InlineData(2022, 800000000, 500_000, "Married", "Protestant", "Other")] - [InlineData(2022, 800000000, 0, "Married", "Other", "Other")] - [InlineData(2022, 885300000, 500_000, "Married", "Protestant", "Other")] - [InlineData(2022, 885300000, 500_000, "Married", "Protestant", "Roman")] - public async Task Should_Calculate_Income_And_Wealth_Tax_Successfully( - int taxYear, int taxLocationId, decimal wealth, string civilStatusString, string religiousType, string religiousTypePartner) - { - // given - IEstvTaxCalculatorClient estvClient = provider.GetRequiredService(); + provider = services.BuildServiceProvider(); + } - var result = await estvClient.CalculateIncomeAndWealthTaxAsync( - taxLocationId, taxYear, GetPerson(wealth, civilStatusString, religiousType, religiousTypePartner)); + [Fact(DisplayName = "Tax Location")] + public async Task Should_Get_TaxLocation_By_Estv_Successfully() + { + // given + string zip = "3303"; + string city = "Zuzwil"; - Snapshot.Match(result, $"ESTV SimpleTax {taxYear}{taxLocationId}{wealth}{civilStatusString}{religiousType}{religiousTypePartner}"); - } + IEstvTaxCalculatorClient estvClient = provider.GetRequiredService(); - [Theory(DisplayName = "Capital Benefit Tax")] - [InlineData(2022, 800000000, 1_000_000, "Married", "Other", "Other")] - [InlineData(2022, 800000000, 1_000_000, "Married", "Protestant", "Other")] - [InlineData(2022, 800000000, 0, "Married", "Other", "Other")] - [InlineData(2022, 885300000, 1_000_000, "Married", "Protestant", "Other")] - [InlineData(2022, 885300000, 1_000_000, "Married", "Protestant", "Roman")] - [InlineData(2023, 800000000, 1_000_000, "Married", "Protestant", "Other")] - public async Task Calculate_Capital_Benefit_Tax_Successfully( - int taxYear, int taxLocationId, decimal capitalBenefits, string civilStatusString, string religiousType, string religiousTypePartner) - { - // given - IEstvTaxCalculatorClient estvClient = provider.GetRequiredService(); + var result = await estvClient.GetTaxLocationsAsync(zip, city); - var result = await estvClient.CalculateCapitalBenefitTaxAsync( - taxLocationId, taxYear, GetCapitalBenefitPerson(capitalBenefits, civilStatusString, religiousType, religiousTypePartner)); - - Snapshot.Match(result, $"ESTV CapitalBenefits {taxYear}{taxLocationId}{capitalBenefits}{civilStatusString}{religiousType}{religiousTypePartner}"); - } + Snapshot.Match(result); + } - private static TaxPerson GetPerson(decimal wealth, string civilStatusString, string religiousTypePerson1, string religiousTypePerson2) - { - TaxPerson person = new TaxPerson + [Theory(DisplayName = "Income and Wealth Tax")] + [InlineData(2023, 800000000, 500_000, "Married", "Other", "Other")] + [InlineData(2022, 800000000, 500_000, "Married", "Other", "Other")] + [InlineData(2022, 800000000, 500_000, "Married", "Protestant", "Other")] + [InlineData(2022, 800000000, 0, "Married", "Other", "Other")] + [InlineData(2022, 885300000, 500_000, "Married", "Protestant", "Other")] + [InlineData(2022, 885300000, 500_000, "Married", "Protestant", "Roman")] + public async Task Should_Calculate_Income_And_Wealth_Tax_Successfully( + int taxYear, int taxLocationId, decimal wealth, string civilStatusString, string religiousType, string religiousTypePartner) { - Name = "Tester", - CivilStatus = CivilStatus.Single, - NumberOfChildren = 0, - ReligiousGroupType = ReligiousGroupType.Other, - TaxableWealth = wealth, - TaxableFederalIncome = 100_000, - TaxableIncome = 100_000, - }; + // given + IEstvTaxCalculatorClient estvClient = provider.GetRequiredService(); + var result = await estvClient.CalculateIncomeAndWealthTaxAsync( + taxLocationId, taxYear, GetPerson(wealth, civilStatusString, religiousType, religiousTypePartner)); - if (Enum.TryParse(civilStatusString, out var civilStatus)) - { - person.CivilStatus = civilStatus; + Snapshot.Match(result, $"ESTV SimpleTax {taxYear}{taxLocationId}{wealth}{civilStatusString}{religiousType}{religiousTypePartner}"); } - if (Enum.TryParse(religiousTypePerson1, out var person1)) + [Theory(DisplayName = "Capital Benefit Tax")] + [InlineData(2022, 800000000, 1_000_000, "Married", "Other", "Other")] + [InlineData(2022, 800000000, 1_000_000, "Married", "Protestant", "Other")] + [InlineData(2022, 800000000, 0, "Married", "Other", "Other")] + [InlineData(2022, 800000000, 77616, "Married", "Other", "Other")] + [InlineData(2022, 885300000, 1_000_000, "Married", "Protestant", "Other")] + [InlineData(2022, 885300000, 1_000_000, "Married", "Protestant", "Roman")] + [InlineData(2023, 800000000, 1_000_000, "Married", "Protestant", "Other")] + public async Task Calculate_Capital_Benefit_Tax_Successfully( + int taxYear, int taxLocationId, decimal capitalBenefits, string civilStatusString, string religiousType, string religiousTypePartner) { - person.ReligiousGroupType = person1; - } + // given + IEstvTaxCalculatorClient estvClient = provider.GetRequiredService(); - if (person.CivilStatus == CivilStatus.Married && - Enum.TryParse(religiousTypePerson2, out var person2)) - { - person.PartnerReligiousGroupType = person2; - } + var result = await estvClient.CalculateCapitalBenefitTaxAsync( + taxLocationId, taxYear, GetCapitalBenefitPerson(capitalBenefits, civilStatusString, religiousType, religiousTypePartner)); - return person; - } - - private static CapitalBenefitTaxPerson GetCapitalBenefitPerson( - decimal capitalBenefits, string civilStatusString, string religiousTypePerson1, string religiousTypePerson2) - { - var person = new CapitalBenefitTaxPerson - { - Name = "Tester", - CivilStatus = CivilStatus.Single, - NumberOfChildren = 0, - ReligiousGroupType = ReligiousGroupType.Other, - TaxableCapitalBenefits = capitalBenefits, - }; - - if (Enum.TryParse(civilStatusString, out var civilStatus)) - { - person.CivilStatus = civilStatus; + Snapshot.Match(result, $"ESTV CapitalBenefits {taxYear}{taxLocationId}{capitalBenefits}{civilStatusString}{religiousType}{religiousTypePartner}"); } - if (Enum.TryParse(religiousTypePerson1, out var person1)) + private static TaxPerson GetPerson(decimal wealth, string civilStatusString, string religiousTypePerson1, string religiousTypePerson2) { - person.ReligiousGroupType = person1; + + TaxPerson person = new TaxPerson + { + Name = "Tester", + CivilStatus = CivilStatus.Single, + NumberOfChildren = 0, + ReligiousGroupType = ReligiousGroupType.Other, + TaxableWealth = wealth, + TaxableFederalIncome = 100_000, + TaxableIncome = 100_000, + }; + + + if (Enum.TryParse(civilStatusString, out var civilStatus)) + { + person.CivilStatus = civilStatus; + } + + if (Enum.TryParse(religiousTypePerson1, out var person1)) + { + person.ReligiousGroupType = person1; + } + + if (person.CivilStatus == CivilStatus.Married && + Enum.TryParse(religiousTypePerson2, out var person2)) + { + person.PartnerReligiousGroupType = person2; + } + + return person; } - if (person.CivilStatus == CivilStatus.Married && - Enum.TryParse(religiousTypePerson2, out var person2)) + private static CapitalBenefitTaxPerson GetCapitalBenefitPerson( + decimal capitalBenefits, string civilStatusString, string religiousTypePerson1, string religiousTypePerson2) { - person.PartnerReligiousGroupType = person2; + var person = new CapitalBenefitTaxPerson + { + Name = "Tester", + CivilStatus = CivilStatus.Single, + NumberOfChildren = 0, + ReligiousGroupType = ReligiousGroupType.Other, + TaxableCapitalBenefits = capitalBenefits, + }; + + if (Enum.TryParse(civilStatusString, out var civilStatus)) + { + person.CivilStatus = civilStatus; + } + + if (Enum.TryParse(religiousTypePerson1, out var person1)) + { + person.ReligiousGroupType = person1; + } + + if (person.CivilStatus == CivilStatus.Married && + Enum.TryParse(religiousTypePerson2, out var person2)) + { + person.PartnerReligiousGroupType = person2; + } + + return person; } - - return person; } } diff --git a/src/EstvTaxCalculatorFacade.Integration.Tests/__snapshots__/ESTV CapitalBenefits 202280000000077616MarriedOtherOther.snap b/src/EstvTaxCalculatorFacade.Integration.Tests/__snapshots__/ESTV CapitalBenefits 202280000000077616MarriedOtherOther.snap new file mode 100644 index 00000000..7708fccb --- /dev/null +++ b/src/EstvTaxCalculatorFacade.Integration.Tests/__snapshots__/ESTV CapitalBenefits 202280000000077616MarriedOtherOther.snap @@ -0,0 +1,15 @@ +{ + "TaxCanton": 1536, + "TaxChurch": 0, + "TaxCity": 1847, + "TaxFed": 195, + "Location": { + "Id": 800000000, + "ZipCode": "8000", + "BfsId": 261, + "CantonId": 26, + "BfsName": "Zürich", + "City": "Zürich", + "Canton": "ZH" + } +} diff --git a/src/EstvTaxCalculatorFacade/EstvCalculatorClient.cs b/src/EstvTaxCalculatorFacade/EstvCalculatorClient.cs deleted file mode 100644 index cd8ce449..00000000 --- a/src/EstvTaxCalculatorFacade/EstvCalculatorClient.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System.Net.Http.Headers; -using System.Text; -using System.Text.Json; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; -using PensionCoach.Tools.EstvTaxCalculators.Models; - -namespace PensionCoach.Tools.EstvTaxCalculators; - -public class EstvTaxCalculatorClient : IEstvTaxCalculatorClient -{ - internal static string EstvTaxCalculatorClientName = "EstvTaxCalculatorClient"; - - private readonly IHttpClientFactory httpClientFactory; - - public EstvTaxCalculatorClient( - IHttpClientFactory httpClientFactory) - { - this.httpClientFactory = httpClientFactory; - } - - public async Task GetTaxLocationsAsync(string zip, string city) - { - HttpClient client = httpClientFactory.CreateClient("EstvTaxCalculatorClient"); - client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*")); - - var request = new TaxLocationRequest { Search = string.IsNullOrEmpty(city) ? $"{zip}" : $"{zip} {city}" }; - - TaxLocationResponse response = await CallAsync(JsonSerializer.Serialize(request), "API_searchLocation"); - - return response.Response; - } - - public async Task CalculateIncomeAndWealthTaxAsync(int taxLocationId, int taxYear, TaxPerson person) - { - SimpleTaxRequest request = new SimpleTaxRequest - { - TaxYear = taxYear, - TaxLocationId = taxLocationId, - Children = Array.Empty(), - Confession1 = Map(person.ReligiousGroupType), - Confession2 = 0, - Relationship = MapCivilStatus(person.CivilStatus), - TaxableIncomeFed = (int)person.TaxableFederalIncome, - TaxableFortune = (int)person.TaxableWealth, - TaxableIncomeCanton = (int)person.TaxableIncome - }; - - if (person.CivilStatus == CivilStatus.Married) - { - request.Confession2 = Map(person.PartnerReligiousGroupType); - } - - SimpleTaxResponse response = await CallAsync(JsonSerializer.Serialize(request), "API_calculateSimpleTaxes"); - - return response.Response; - } - - public async Task CalculateCapitalBenefitTaxAsync(int taxLocationId, int taxYear, CapitalBenefitTaxPerson person) - { - SimpleCapitalTaxRequest request = new() - { - TaxYear = taxYear, - TaxGroupId = taxLocationId, - AgeAtRetirement = 65, - NumberOfChildren = 0, - Confession1 = Map(person.ReligiousGroupType), - Confession2 = 0, - Relationship = MapCivilStatus(person.CivilStatus), - Capital = (int)person.TaxableCapitalBenefits, - }; - - if (person.CivilStatus == CivilStatus.Married) - { - request.Confession2 = Map(person.PartnerReligiousGroupType); - } - - SimpleCapitalTaxResponse response = - await CallAsync(JsonSerializer.Serialize(request), "API_calculateManyCapitalTaxes"); - - return response.Response.FirstOrDefault(); - } - - private async Task CallAsync(string request, string path) - { - HttpClient client = httpClientFactory.CreateClient("EstvTaxCalculatorClient"); - client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*")); - var content = new StringContent(request, Encoding.UTF8, "application/json"); - HttpResponseMessage response = await client.PostAsync(path, content); - - response.EnsureSuccessStatusCode(); - - string json = await response.Content.ReadAsStringAsync(); - - return JsonSerializer.Deserialize(json); - } - - private static int MapCivilStatus(CivilStatus civilStatus) - { - return civilStatus switch - { - CivilStatus.Single => 1, - CivilStatus.Married => 2, - _ => 2 - }; - } - - private static int Map(ReligiousGroupType? religiousGroupType) - { - return religiousGroupType switch - { - null => 0, - ReligiousGroupType.Protestant => 1, - ReligiousGroupType.Roman => 2, - ReligiousGroupType.Catholic => 3, - ReligiousGroupType.Other => 5, - _ => 5 - }; - } -} diff --git a/src/EstvTaxCalculatorFacade/EstvTaxCalculatorClientMock.cs b/src/EstvTaxCalculatorFacade/EstvTaxCalculatorClientMock.cs deleted file mode 100644 index b1666b69..00000000 --- a/src/EstvTaxCalculatorFacade/EstvTaxCalculatorClientMock.cs +++ /dev/null @@ -1,76 +0,0 @@ -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; - -namespace PensionCoach.Tools.EstvTaxCalculators; - -public class EstvTaxCalculatorClientMock : IEstvTaxCalculatorClient -{ - - public Task GetTaxLocationsAsync(string zip, string city) - { - return Task.FromResult(new[] - { - new TaxLocation - { - BfsId = 261, - BfsName = "Zürich", - Canton = "ZH", - CantonId = 1, - City = "Zürich", - ZipCode = "8000" - } - }); - } - - public Task CalculateIncomeAndWealthTaxAsync(int taxLocationId, int taxYear, TaxPerson person) - { - return Task.FromResult(new SimpleTaxResult - { - IncomeSimpleTaxCanton = 6296, - FortuneTaxCanton = 308, - IncomeSimpleTaxCity = 6296, - IncomeTaxChurch = 630, - IncomeTaxCity = 7492, - IncomeSimpleTaxFed = 2874, - PersonalTax = 24, - FortuneTaxCity = 366, - FortuneSimpleTaxCanton = 308, - IncomeTaxFed = 2874, - FortuneSimpleTaxCity = 308, - IncomeTaxCanton = 6296, - FortuneTaxChurch = 31, - Location = - { - Id = 80000000, - ZipCode = "800", - BfsId = 261, - CantonId = 26, - BfsName = "Züich", - City = "Zürich", - Canton = "ZH" - } - }); - } - - public Task CalculateCapitalBenefitTaxAsync(int taxLocationId, int taxYear, CapitalBenefitTaxPerson person) - { - return Task.FromResult(new SimpleCapitalTaxResult - { - TaxCanton = 20870, - TaxChurch = 2087, - TaxCity = 24835, - TaxFed = 10632, - Location = - { - Id = 800000000, - ZipCode = "8000", - BfsId = 261, - CantonId = 26, - BfsName = "Zürich", - City = "Zürich", - Canton = "ZH" - } - }); - } -} diff --git a/src/EstvTaxCalculatorFacade/EstvTaxCalculatorFacade.csproj b/src/EstvTaxCalculatorFacade/EstvTaxCalculatorFacade.csproj deleted file mode 100644 index bcc47774..00000000 --- a/src/EstvTaxCalculatorFacade/EstvTaxCalculatorFacade.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - net7.0 - PensionCoach.Tools.EstvTaxCalculators - PensionCoach.Tools.EstvTaxCalculators - enable - - - - - - - - - - - - - diff --git a/src/EstvTaxCalculatorFacade/Models/SimpleCapitalTaxRequest.cs b/src/EstvTaxCalculatorFacade/Models/SimpleCapitalTaxRequest.cs deleted file mode 100644 index deba50bc..00000000 --- a/src/EstvTaxCalculatorFacade/Models/SimpleCapitalTaxRequest.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Text.Json.Serialization; - -namespace PensionCoach.Tools.EstvTaxCalculators.Models; - -public class SimpleCapitalTaxRequest -{ - public int TaxYear { get; set; } - - [JsonPropertyName("TaxGroupID")] - public int TaxGroupId { get; set; } - - public int AgeAtRetirement { get; set; } - - public int Relationship { get; set; } - - public int Confession1 { get; set; } - - public int Confession2 { get; set; } - - public int NumberOfChildren { get; set; } - - public int Capital { get; set; } -} diff --git a/src/EstvTaxCalculatorFacade/Models/SimpleCapitalTaxResponse.cs b/src/EstvTaxCalculatorFacade/Models/SimpleCapitalTaxResponse.cs deleted file mode 100644 index d8127ba4..00000000 --- a/src/EstvTaxCalculatorFacade/Models/SimpleCapitalTaxResponse.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Text.Json.Serialization; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; - -namespace PensionCoach.Tools.EstvTaxCalculators.Models; - -public class SimpleCapitalTaxResponse -{ - [JsonPropertyName("response")] - public SimpleCapitalTaxResult[] Response { get; set; } -} diff --git a/src/EstvTaxCalculatorFacade/Models/SimpleTaxRequest.cs b/src/EstvTaxCalculatorFacade/Models/SimpleTaxRequest.cs deleted file mode 100644 index 8af582a1..00000000 --- a/src/EstvTaxCalculatorFacade/Models/SimpleTaxRequest.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Text.Json.Serialization; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; - -namespace PensionCoach.Tools.EstvTaxCalculators.Models; - -public class SimpleTaxRequest -{ - public int TaxYear { get; set; } - - [JsonPropertyName("TaxLocationID")] - public int TaxLocationId { get; set; } - public int Relationship { get; set; } - public int Confession1 { get; set; } - public IEnumerable Children { get; set; } - public int Confession2 { get; set; } - public int TaxableIncomeCanton { get; set; } - public int TaxableIncomeFed { get; set; } - public int TaxableFortune { get; set; } -} diff --git a/src/EstvTaxCalculatorFacade/Models/SimpleTaxResponse.cs b/src/EstvTaxCalculatorFacade/Models/SimpleTaxResponse.cs deleted file mode 100644 index da828bb1..00000000 --- a/src/EstvTaxCalculatorFacade/Models/SimpleTaxResponse.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Text.Json.Serialization; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; - -namespace PensionCoach.Tools.EstvTaxCalculators.Models; - -public class SimpleTaxResponse -{ - [JsonPropertyName("response")] - public SimpleTaxResult Response { get; set; } -} diff --git a/src/EstvTaxCalculatorFacade/Models/TaxLocationResponse.cs b/src/EstvTaxCalculatorFacade/Models/TaxLocationResponse.cs deleted file mode 100644 index 96d88243..00000000 --- a/src/EstvTaxCalculatorFacade/Models/TaxLocationResponse.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Text.Json.Serialization; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; - -namespace PensionCoach.Tools.EstvTaxCalculators.Models; - -public class TaxLocationResponse -{ - [JsonPropertyName("response")] - public TaxLocation[] Response { get; set; } -} diff --git a/src/EstvTaxCalculatorFacade/ServiceCollectionExtensions.cs b/src/EstvTaxCalculatorFacade/ServiceCollectionExtensions.cs deleted file mode 100644 index 1786b1c5..00000000 --- a/src/EstvTaxCalculatorFacade/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions; - -namespace PensionCoach.Tools.EstvTaxCalculators; - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddEstvTaxCalculatorClient(this IServiceCollection services, IConfiguration configuration) - { - string baseUrl = configuration["TaxCalculatorClient:EstvTaxCalculatorBaseUrl"]; - - return services.AddEstvTaxCalculatorClient(baseUrl); - } - - public static IServiceCollection AddEstvTaxCalculatorClient(this IServiceCollection services, string baseUrl) - { - services.AddHttpClient(EstvTaxCalculatorClient.EstvTaxCalculatorClientName, c => - { - c.BaseAddress = new Uri(baseUrl); - }); - - services.AddTransient(); - - return services; - } -} diff --git a/src/Infrastructure/Configuration/CheckSettingsConnector.cs b/src/Infrastructure/Configuration/CheckSettingsConnector.cs new file mode 100644 index 00000000..25ad2829 --- /dev/null +++ b/src/Infrastructure/Configuration/CheckSettingsConnector.cs @@ -0,0 +1,24 @@ +using Application.Features.CheckSettings; +using Microsoft.Extensions.Configuration; + +namespace Infrastructure.Configuration +{ + public class CheckSettingsConnector : ICheckSettingsConnector + { + private readonly IConfiguration _configuration; + + public CheckSettingsConnector(IConfiguration configuration) + { + _configuration = configuration; + } + + public Task> GetAsync() + { + _configuration.GetApplicationMode(); + + var settings = new Dictionary { { "Steuerrechner", _configuration.GetApplicationMode().ToString() } }; + + return Task.FromResult(settings); + } + } +} diff --git a/src/Infrastructure/Configuration/ConfigurationExtensions.cs b/src/Infrastructure/Configuration/ConfigurationExtensions.cs new file mode 100644 index 00000000..6e217c95 --- /dev/null +++ b/src/Infrastructure/Configuration/ConfigurationExtensions.cs @@ -0,0 +1,19 @@ +using Domain.Enums; +using Microsoft.Extensions.Configuration; + +namespace Infrastructure.Configuration +{ + public static class ConfigurationExtensions + { + public static ApplicationMode GetApplicationMode(this IConfiguration configuration) + { + const string key = "ApplicationMode"; + + return configuration[key] switch + { + null => ApplicationMode.Proprietary, + { } v => Enum.Parse(v), + }; + } + } +} diff --git a/src/Infrastructure/DataStaging/DataStagingConnector.cs b/src/Infrastructure/DataStaging/DataStagingConnector.cs new file mode 100644 index 00000000..ebce9b2c --- /dev/null +++ b/src/Infrastructure/DataStaging/DataStagingConnector.cs @@ -0,0 +1,296 @@ +using System.Globalization; +using System.Threading.Channels; +using Application.Municipality; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using Infrastructure.PostOpenApi; +using Infrastructure.PostOpenApi.Models; +using Infrastructure.Tax.Data; +using LanguageExt; +using Microsoft.Extensions.Logging; +using PensionCoach.Tools.CommonUtils; + +namespace Infrastructure.DataStaging +{ + public class DataStagingConnector : IDataStagingConnector + { + private readonly IMunicipalityRepository municipalityRepository; + private readonly MunicipalityDbContext municipalityDbContext; + private readonly ITaxDataPopulateService populateTaxDataService; + private readonly IPostOpenApiClient postOpenApiClient; + private readonly ILogger logger; + + public DataStagingConnector( + IMunicipalityRepository municipalityRepository, + MunicipalityDbContext municipalityDbContext, + ITaxDataPopulateService populateTaxDataService, + IPostOpenApiClient postOpenApiClient, + ILogger logger) + { + this.municipalityRepository = municipalityRepository; + this.municipalityDbContext = municipalityDbContext; + this.populateTaxDataService = populateTaxDataService; + this.postOpenApiClient = postOpenApiClient; + this.logger = logger; + } + + private int TotalCount { get; set; } + + public async Task PopulateWithZipCodeAsync() + { + int count = 0; + foreach (var municipalityEntity in municipalityDbContext.MunicipalityEntities + .Where(item => item.SuccessorId == 0 && string.IsNullOrEmpty(item.ZipCode))) + { + ZipEntity[] zipEntities = municipalityDbContext.TaxMunicipalityEntities + .Where(item => item.BfsNumber == municipalityEntity.BfsNumber && item.Canton == municipalityEntity.Canton) + .ToArray(); + + if (zipEntities.Length == 1) // unique, easy case + { + if (municipalityDbContext.MunicipalityEntities.Local + .Any(item => + item.BfsNumber == municipalityEntity.BfsNumber && + item.MutationId == municipalityEntity.MutationId)) + { + continue; + } + + municipalityEntity.ZipCode = zipEntities[0].ZipCode; + municipalityDbContext.Update(municipalityEntity); + count++; + } + else if (zipEntities.Length > 1) + { + var zipsByNameEntities = zipEntities + .Where(item => item.Name.Contains(municipalityEntity.CleanName)) + .ToArray(); + + if (zipsByNameEntities.Length == 1) + { + if (municipalityDbContext.MunicipalityEntities.Local + .Any(item => + item.BfsNumber == municipalityEntity.BfsNumber && + item.MutationId == municipalityEntity.MutationId)) + { + continue; + } + + municipalityEntity.ZipCode = zipsByNameEntities[0].ZipCode; + municipalityDbContext.Update(municipalityEntity); + count++; + } + else if (zipsByNameEntities.Length > 1) + { + // take smallest zip add on + var zipsByAddOn = zipsByNameEntities + .Where(item => item.ZipCodeAddOn == "0") + .ToArray(); + + if (zipsByAddOn.Length == 1) + { + if (municipalityDbContext.MunicipalityEntities.Local + .Any(item => + item.BfsNumber == municipalityEntity.BfsNumber && + item.MutationId == municipalityEntity.MutationId)) + { + continue; + } + + municipalityEntity.ZipCode = zipsByAddOn[0].ZipCode; + municipalityDbContext.Update(municipalityEntity); + count++; + } + else if (zipsByAddOn.Length > 1) + { + // take smallest zip add on + var zipFinalEntity = zipsByAddOn + .OrderBy(item => item.Name.Length) + .First(); + + if (municipalityDbContext.MunicipalityEntities.Local + .Any(item => + item.BfsNumber == municipalityEntity.BfsNumber && + item.MutationId == municipalityEntity.MutationId)) + { + continue; + } + + municipalityEntity.ZipCode = zipFinalEntity.ZipCode; + municipalityDbContext.Update(municipalityEntity); + count++; + } + } + } + } + + await municipalityDbContext.SaveChangesAsync(); + + return count; + } + + public Task PopulateWithTaxLocationAsync(bool doClear) + { + return populateTaxDataService.PopulateWithTaxLocationAsync(doClear); + } + + public async Task StagePlzTableAsync() + { + municipalityDbContext.TruncateTaxMunicipalityTable(); + + int count = 0; + + await foreach (var model in GetAllZipCodesAsync(int.MaxValue)) + { + municipalityDbContext.Add(new ZipEntity + { + BfsNumber = model.BfsCode, + Canton = model.Canton, + ZipCode = model.ZipCode, + ZipCodeAddOn = model.ZipCodeAddOn, + Name = model.MunicipalityName, + LanguageCode = model.LanguageCode, + DateOfValidity = model.DateOfValidity, + }); + + count++; + } + + await municipalityDbContext.SaveChangesAsync(CancellationToken.None); + + return count; + } + + public async Task CleanMunicipalityName() + { + int count = 0; + foreach (var municipalityEntity in municipalityDbContext.MunicipalityEntities) + { + Prelude.Optional((municipalityEntity.Name, municipalityEntity.Canton)) + .Map(t => RemoveCantonDescription(t.Name, t.Canton)) + .Map(Abbreviate) + .Iter(cleanName => + { + municipalityEntity.CleanName = cleanName; + municipalityDbContext.Update(municipalityEntity); + + count++; + }); + } + + await municipalityDbContext.SaveChangesAsync(CancellationToken.None); + + return count; + } + + /// + /// + public async IAsyncEnumerable GetAllZipCodesAsync(int limit) + { + const int numberOfReaders = 5; + const int limitPerFetch = 100; + + using (new MeasureTime(t => logger.LogDebug($"Execution time to fetch all ZIP: {t}ms"))) + { + logger.LogDebug($"First fetch zip data ({limitPerFetch} items)"); + + OpenApiZipInfo openApiData = await postOpenApiClient.GetZipCodesAsync(limitPerFetch, 0); + + if (openApiData is not { TotalCount: > 0 }) + { + yield break; + } + + TotalCount = openApiData.TotalCount; + + Channel<(int, int)> fetchZipChannel = Channel.CreateBounded<(int, int)>(new BoundedChannelOptions(5)); + Channel resultZipChannel = Channel.CreateUnbounded(); + + Task[] consumers = Enumerable + .Range(1, numberOfReaders) + .Select(counter => ConsumeDataAsync(fetchZipChannel.Reader, resultZipChannel.Writer, counter)) + .ToArray(); + + await WriteOpenApiDataToChannel(resultZipChannel, openApiData); + + await Task.Run(async () => + { + int count = 1; + while (count * limitPerFetch < TotalCount) + { + await fetchZipChannel.Writer.WriteAsync((limitPerFetch, count * limitPerFetch)); + count++; + } + + fetchZipChannel.Writer.Complete(); + }); + + await Task.WhenAll(consumers); + + resultZipChannel.Writer.Complete(); + + await foreach (ZipModel record in resultZipChannel.Reader.ReadAllAsync(CancellationToken.None)) + { + yield return record; + } + } + } + + private async Task ConsumeDataAsync(ChannelReader<(int, int)> channelReader, ChannelWriter channelWriter, int readerId) + { + while (await channelReader.WaitToReadAsync()) + { + if (channelReader.TryRead(out (int limit, int offset) fetch)) + { + logger.LogDebug($"Fetch zip data by reader {readerId}: limit {fetch.limit}, offset {fetch.offset}"); + + OpenApiZipInfo openApiData = await postOpenApiClient.GetZipCodesAsync(fetch.limit, fetch.offset); + + if (openApiData is null || openApiData.Records.Length() == 0) + { + break; + } + + await WriteOpenApiDataToChannel(channelWriter, openApiData); + } + } + + logger.LogDebug($"Reader {readerId} completed"); + } + + private static async Task WriteOpenApiDataToChannel(ChannelWriter channelWriter, OpenApiZipInfo openApiData) + { + foreach (ZipModel model in openApiData.Records.Select(x => new ZipModel + { + BfsCode = x.Record.Fields.BfsCode, + MunicipalityName = x.Record.Fields.MunicipalityName, + Canton = x.Record.Fields.Canton, + ZipCode = x.Record.Fields.ZipCode, + ZipCodeAddOn = x.Record.Fields.ZipCodeAddOn, + LanguageCode = x.Record.Fields.LanguageCode, + DateOfValidity = DateTime.Parse(x.Record.Fields.DateOfValidity, CultureInfo.InvariantCulture), + })) + { + await channelWriter.WriteAsync(model); + } + } + + private string RemoveCantonDescription(string name, string canton) + { + return name + .Replace($" ({canton})", string.Empty) + .Replace($" {canton}", string.Empty) + .Trim(); + } + + private string Abbreviate(string name) + { + return name + .Replace(" bei ", " b. ") + .Replace("Saint-", "St-") + .Replace("Sainte-", "Ste-") + .Replace("ë", "e") + .Trim(); + } + } +} diff --git a/src/Infrastructure/DataStaging/DataStagingServiceCollectionExtensions.cs b/src/Infrastructure/DataStaging/DataStagingServiceCollectionExtensions.cs new file mode 100644 index 00000000..49df9f9d --- /dev/null +++ b/src/Infrastructure/DataStaging/DataStagingServiceCollectionExtensions.cs @@ -0,0 +1,17 @@ +using Application.Features.CheckSettings; +using Infrastructure.Configuration; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Infrastructure.DataStaging +{ + public static class DataStagingServiceCollectionExtensions + { + public static void AddDataStagingServices(this IServiceCollection collection) + { + collection.AddTransient(); + collection.AddTransient(); + + } + } +} diff --git a/src/Infrastructure/DataStaging/IDataStagingConnector.cs b/src/Infrastructure/DataStaging/IDataStagingConnector.cs new file mode 100644 index 00000000..ae9e4a0e --- /dev/null +++ b/src/Infrastructure/DataStaging/IDataStagingConnector.cs @@ -0,0 +1,39 @@ +using Domain.Models.Municipality; + +namespace Infrastructure.DataStaging +{ + public interface IDataStagingConnector + { + /// + /// Copy zip code data from stage table to data store. + /// + /// + Task PopulateWithZipCodeAsync(); + + /// + /// Populate data store containing tax data with ESTV tax location id. + /// This is one one time step which may repeated every time a municipality change is available. + /// + /// + /// + Task PopulateWithTaxLocationAsync(bool doClear); + + /// + /// Stage table with zip codes from Post OpenData API. + /// + /// Number of cases processed. + Task StagePlzTableAsync(); + + /// + /// Get all current zip (PLZ) information for Switzerland supplied by the Swiss Post. + /// + /// + IAsyncEnumerable GetAllZipCodesAsync(int limit); + + /// + /// Clean municipality name. E.g. removing canton postfix to avoid ambiguity. + /// + /// Number of cases processed. + Task CleanMunicipalityName(); + } +} diff --git a/src/Infrastructure/DataStaging/ITaxDataPopulateService.cs b/src/Infrastructure/DataStaging/ITaxDataPopulateService.cs new file mode 100644 index 00000000..da99baaa --- /dev/null +++ b/src/Infrastructure/DataStaging/ITaxDataPopulateService.cs @@ -0,0 +1,12 @@ +namespace Infrastructure.DataStaging +{ + public interface ITaxDataPopulateService + { + /// + /// Populate municipality data with ESTV's tax location id. + /// If doClear is true, tax location id is set to null first. + /// + /// Number of cases processed. + Task PopulateWithTaxLocationAsync(bool doClear); + } +} diff --git a/src/Infrastructure/EstvTaxCalculator/EstvCalculatorClient.cs b/src/Infrastructure/EstvTaxCalculator/EstvCalculatorClient.cs new file mode 100644 index 00000000..3301daf3 --- /dev/null +++ b/src/Infrastructure/EstvTaxCalculator/EstvCalculatorClient.cs @@ -0,0 +1,123 @@ +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using Application.Tax.Estv.Client; +using Application.Tax.Estv.Client.Models; +using Domain.Enums; +using Domain.Models.Tax; +using Infrastructure.EstvTaxCalculator.Models; + +namespace Infrastructure.EstvTaxCalculator +{ + public class EstvTaxCalculatorClient : IEstvTaxCalculatorClient + { + internal static string EstvTaxCalculatorClientName = "EstvTaxCalculatorClient"; + + private readonly IHttpClientFactory httpClientFactory; + + public EstvTaxCalculatorClient( + IHttpClientFactory httpClientFactory) + { + this.httpClientFactory = httpClientFactory; + } + + public async Task GetTaxLocationsAsync(string zip, string city) + { + HttpClient client = httpClientFactory.CreateClient("EstvTaxCalculatorClient"); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*")); + + var request = new TaxLocationRequest { Search = string.IsNullOrEmpty(city) ? $"{zip}" : $"{zip} {city}" }; + + TaxLocationResponse response = await CallAsync(JsonSerializer.Serialize(request), "API_searchLocation"); + + return response.Response; + } + + public async Task CalculateIncomeAndWealthTaxAsync(int taxLocationId, int taxYear, TaxPerson person) + { + SimpleTaxRequest request = new SimpleTaxRequest + { + TaxYear = taxYear, + TaxLocationId = taxLocationId, + Children = Array.Empty(), + Confession1 = Map(person.ReligiousGroupType), + Confession2 = 0, + Relationship = MapCivilStatus(person.CivilStatus), + TaxableIncomeFed = (int)person.TaxableFederalIncome, + TaxableFortune = (int)person.TaxableWealth, + TaxableIncomeCanton = (int)person.TaxableIncome + }; + + if (person.CivilStatus == CivilStatus.Married) + { + request.Confession2 = Map(person.PartnerReligiousGroupType); + } + + SimpleTaxResponse response = await CallAsync(JsonSerializer.Serialize(request), "API_calculateSimpleTaxes"); + + return response.Response; + } + + public async Task CalculateCapitalBenefitTaxAsync(int taxLocationId, int taxYear, CapitalBenefitTaxPerson person) + { + SimpleCapitalTaxRequest request = new() + { + TaxYear = taxYear, + TaxGroupId = taxLocationId, + AgeAtRetirement = 65, + NumberOfChildren = 0, + Confession1 = Map(person.ReligiousGroupType), + Confession2 = 0, + Relationship = MapCivilStatus(person.CivilStatus), + Capital = (int)person.TaxableCapitalBenefits, + }; + + if (person.CivilStatus == CivilStatus.Married) + { + request.Confession2 = Map(person.PartnerReligiousGroupType); + } + + SimpleCapitalTaxResponse response = + await CallAsync(JsonSerializer.Serialize(request), "API_calculateManyCapitalTaxes"); + + return response.Response.FirstOrDefault(); + } + + private async Task CallAsync(string request, string path) + { + HttpClient client = httpClientFactory.CreateClient("EstvTaxCalculatorClient"); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*")); + var content = new StringContent(request, Encoding.UTF8, "application/json"); + HttpResponseMessage response = await client.PostAsync(path, content); + + response.EnsureSuccessStatusCode(); + + string json = await response.Content.ReadAsStringAsync(); + + return JsonSerializer.Deserialize(json); + } + + private static int MapCivilStatus(CivilStatus civilStatus) + { + return civilStatus switch + { + CivilStatus.Single => 1, + CivilStatus.Married => 2, + _ => 2 + }; + } + + private static int Map(ReligiousGroupType? religiousGroupType) + { + return religiousGroupType switch + { + null => 0, + ReligiousGroupType.Protestant => 1, + ReligiousGroupType.Roman => 2, + ReligiousGroupType.Catholic => 3, + ReligiousGroupType.Other => 5, + _ => 5 + }; + } + } +} diff --git a/src/Infrastructure/EstvTaxCalculator/EstvTaxCalculatorClientMock.cs b/src/Infrastructure/EstvTaxCalculator/EstvTaxCalculatorClientMock.cs new file mode 100644 index 00000000..905bf472 --- /dev/null +++ b/src/Infrastructure/EstvTaxCalculator/EstvTaxCalculatorClientMock.cs @@ -0,0 +1,76 @@ +using Application.Tax.Estv.Client; +using Application.Tax.Estv.Client.Models; +using Domain.Models.Tax; + +namespace Infrastructure.EstvTaxCalculator +{ + public class EstvTaxCalculatorClientMock : IEstvTaxCalculatorClient + { + public Task GetTaxLocationsAsync(string zip, string city) + { + return Task.FromResult(new[] + { + new TaxLocation + { + BfsId = 261, + BfsName = "Zürich", + Canton = "ZH", + CantonId = 1, + City = "Zürich", + ZipCode = "8000" + } + }); + } + + public Task CalculateIncomeAndWealthTaxAsync(int taxLocationId, int taxYear, TaxPerson person) + { + return Task.FromResult(new SimpleTaxResult + { + IncomeSimpleTaxCanton = 6296, + FortuneTaxCanton = 308, + IncomeSimpleTaxCity = 6296, + IncomeTaxChurch = 630, + IncomeTaxCity = 7492, + IncomeSimpleTaxFed = 2874, + PersonalTax = 24, + FortuneTaxCity = 366, + FortuneSimpleTaxCanton = 308, + IncomeTaxFed = 2874, + FortuneSimpleTaxCity = 308, + IncomeTaxCanton = 6296, + FortuneTaxChurch = 31, + Location = + { + Id = 80000000, + ZipCode = "800", + BfsId = 261, + CantonId = 26, + BfsName = "Züich", + City = "Zürich", + Canton = "ZH" + } + }); + } + + public Task CalculateCapitalBenefitTaxAsync(int taxLocationId, int taxYear, CapitalBenefitTaxPerson person) + { + return Task.FromResult(new SimpleCapitalTaxResult + { + TaxCanton = 20870, + TaxChurch = 2087, + TaxCity = 24835, + TaxFed = 10632, + Location = + { + Id = 800000000, + ZipCode = "8000", + BfsId = 261, + CantonId = 26, + BfsName = "Zürich", + City = "Zürich", + Canton = "ZH" + } + }); + } + } +} diff --git a/src/Infrastructure/EstvTaxCalculator/Models/SimpleCapitalTaxRequest.cs b/src/Infrastructure/EstvTaxCalculator/Models/SimpleCapitalTaxRequest.cs new file mode 100644 index 00000000..66041439 --- /dev/null +++ b/src/Infrastructure/EstvTaxCalculator/Models/SimpleCapitalTaxRequest.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace Infrastructure.EstvTaxCalculator.Models +{ + public class SimpleCapitalTaxRequest + { + public int TaxYear { get; set; } + + [JsonPropertyName("TaxGroupID")] + public int TaxGroupId { get; set; } + + public int AgeAtRetirement { get; set; } + + public int Relationship { get; set; } + + public int Confession1 { get; set; } + + public int Confession2 { get; set; } + + public int NumberOfChildren { get; set; } + + public int Capital { get; set; } + } +} diff --git a/src/Infrastructure/EstvTaxCalculator/Models/SimpleCapitalTaxResponse.cs b/src/Infrastructure/EstvTaxCalculator/Models/SimpleCapitalTaxResponse.cs new file mode 100644 index 00000000..2db13f20 --- /dev/null +++ b/src/Infrastructure/EstvTaxCalculator/Models/SimpleCapitalTaxResponse.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +using Application.Tax.Estv.Client.Models; + +namespace Infrastructure.EstvTaxCalculator.Models +{ + public class SimpleCapitalTaxResponse + { + [JsonPropertyName("response")] + public SimpleCapitalTaxResult[] Response { get; set; } + } +} diff --git a/src/Infrastructure/EstvTaxCalculator/Models/SimpleTaxRequest.cs b/src/Infrastructure/EstvTaxCalculator/Models/SimpleTaxRequest.cs new file mode 100644 index 00000000..ccbac54a --- /dev/null +++ b/src/Infrastructure/EstvTaxCalculator/Models/SimpleTaxRequest.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; +using Application.Tax.Estv.Client.Models; + +namespace Infrastructure.EstvTaxCalculator.Models +{ + public class SimpleTaxRequest + { + public int TaxYear { get; set; } + + [JsonPropertyName("TaxLocationID")] + public int TaxLocationId { get; set; } + public int Relationship { get; set; } + public int Confession1 { get; set; } + public IEnumerable Children { get; set; } + public int Confession2 { get; set; } + public int TaxableIncomeCanton { get; set; } + public int TaxableIncomeFed { get; set; } + public int TaxableFortune { get; set; } + } +} diff --git a/src/Infrastructure/EstvTaxCalculator/Models/SimpleTaxResponse.cs b/src/Infrastructure/EstvTaxCalculator/Models/SimpleTaxResponse.cs new file mode 100644 index 00000000..8db06b16 --- /dev/null +++ b/src/Infrastructure/EstvTaxCalculator/Models/SimpleTaxResponse.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +using Application.Tax.Estv.Client.Models; + +namespace Infrastructure.EstvTaxCalculator.Models +{ + public class SimpleTaxResponse + { + [JsonPropertyName("response")] + public SimpleTaxResult Response { get; set; } + } +} diff --git a/src/Infrastructure/EstvTaxCalculator/Models/TaxLocationResponse.cs b/src/Infrastructure/EstvTaxCalculator/Models/TaxLocationResponse.cs new file mode 100644 index 00000000..e5b99255 --- /dev/null +++ b/src/Infrastructure/EstvTaxCalculator/Models/TaxLocationResponse.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +using Application.Tax.Estv.Client.Models; + +namespace Infrastructure.EstvTaxCalculator.Models +{ + public class TaxLocationResponse + { + [JsonPropertyName("response")] + public TaxLocation[] Response { get; set; } + } +} diff --git a/src/Infrastructure/EstvTaxCalculator/ServiceCollectionExtensions.cs b/src/Infrastructure/EstvTaxCalculator/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..ee08f122 --- /dev/null +++ b/src/Infrastructure/EstvTaxCalculator/ServiceCollectionExtensions.cs @@ -0,0 +1,28 @@ +using Application.Tax.Estv.Client; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Infrastructure.EstvTaxCalculator +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddEstvTaxCalculatorClient(this IServiceCollection services, IConfiguration configuration) + { + string baseUrl = configuration["TaxCalculatorClient:EstvTaxCalculatorBaseUrl"]; + + return services.AddEstvTaxCalculatorClient(baseUrl); + } + + public static IServiceCollection AddEstvTaxCalculatorClient(this IServiceCollection services, string baseUrl) + { + services.AddHttpClient(EstvTaxCalculatorClient.EstvTaxCalculatorClientName, c => + { + c.BaseAddress = new Uri(baseUrl); + }); + + services.AddTransient(); + + return services; + } + } +} diff --git a/src/Infrastructure/HealthChecks/TaxCalculatorHealthCheck.cs b/src/Infrastructure/HealthChecks/TaxCalculatorHealthCheck.cs new file mode 100644 index 00000000..b6354033 --- /dev/null +++ b/src/Infrastructure/HealthChecks/TaxCalculatorHealthCheck.cs @@ -0,0 +1,46 @@ +using Application.Features.FullTaxCalculation; +using Application.Tax; +using Application.Tax.Proprietary.Abstractions.Models; +using Domain.Enums; +using Domain.Models.Tax; +using LanguageExt; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace Infrastructure.HealthChecks +{ + public class TaxCalculatorHealthCheck : IHealthCheck + { + private readonly ITaxCalculatorConnector taxCalculatorConnector; + + public TaxCalculatorHealthCheck(ITaxCalculatorConnector taxCalculatorConnector) + { + this.taxCalculatorConnector = taxCalculatorConnector; + } + + public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken) + { + try + { + Either result = await taxCalculatorConnector.CalculateAsync(2023, 261, + new TaxPerson() + { + CivilStatus = CivilStatus.Single, + Name = "HealthCheck", + NumberOfChildren = 0, + TaxableIncome = 100_000, + TaxableWealth = 0, + TaxableFederalIncome = 100_000, + ReligiousGroupType = ReligiousGroupType.Other, + }); + + return result.Match( + Right: _ => HealthCheckResult.Healthy("Tax calculator ok"), + Left: error => HealthCheckResult.Unhealthy($"Tax calculator nok {error}")); + } + catch (Exception ex) + { + return HealthCheckResult.Unhealthy($"An unhealthy result: {ex.Message}"); + } + } + } +} diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj new file mode 100644 index 00000000..445c3a6f --- /dev/null +++ b/src/Infrastructure/Infrastructure.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + enable + + + + + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Infrastructure/Municipality/MunicipalityRepository.cs b/src/Infrastructure/Municipality/MunicipalityRepository.cs new file mode 100644 index 00000000..08e8d6ce --- /dev/null +++ b/src/Infrastructure/Municipality/MunicipalityRepository.cs @@ -0,0 +1,56 @@ +using Application.Municipality; +using Domain.Enums; +using Domain.Models.Municipality; +using Infrastructure.Tax.Data; +using Microsoft.EntityFrameworkCore; + +namespace Infrastructure.Municipality +{ + public class MunicipalityRepository : IMunicipalityRepository + { + private readonly MunicipalityDbContext municipalityDbContext; + + public MunicipalityRepository(MunicipalityDbContext municipalityDbContext) + { + this.municipalityDbContext = municipalityDbContext; + this.municipalityDbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + } + + public IEnumerable GetAll() + { + return municipalityDbContext.MunicipalityEntities.ToList(); + } + + public MunicipalityEntity Get(int bfsNumber, int year) + { + return municipalityDbContext.MunicipalityEntities + .FirstOrDefault(item => item.BfsNumber == bfsNumber && string.IsNullOrEmpty(item.DateOfMutation)); + } + + public IEnumerable GetAllSupportTaxCalculation() + { + return municipalityDbContext.MunicipalityEntities + .Where(item => item.TaxLocationId != null) + .OrderBy(item => item.Canton) + .ThenBy(item => item.Name); + } + + public IEnumerable Search(MunicipalitySearchFilter searchFilter) + { + IQueryable result = municipalityDbContext.MunicipalityEntities; + + if (searchFilter.Canton != Canton.Undefined) + { + result = result.Where(item => item.Canton == searchFilter.Canton.ToString()); + } + + if (!string.IsNullOrEmpty(searchFilter.Name)) + { + result = + result.Where(item => item.Name.Contains(searchFilter.Name)); + } + + return result; + } + } +} diff --git a/src/Infrastructure/Municipality/ProprietaryMunicipalityConnector.cs b/src/Infrastructure/Municipality/ProprietaryMunicipalityConnector.cs new file mode 100644 index 00000000..7fc02216 --- /dev/null +++ b/src/Infrastructure/Municipality/ProprietaryMunicipalityConnector.cs @@ -0,0 +1,103 @@ +using Application.Municipality; +using Application.Tax.Proprietary.Repositories; +using AutoMapper; +using Domain.Enums; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using Infrastructure.Tax.Data; +using LanguageExt; + +namespace Infrastructure.Municipality +{ + public class ProprietaryMunicipalityConnector : IMunicipalityConnector + { + private readonly IMapper mapper; + private readonly IMunicipalityRepository municipalityRepository; + private readonly IStateTaxRateRepository stateTaxRateRepository; + private readonly Func dbContext; + + public ProprietaryMunicipalityConnector( + IMapper mapper, + IMunicipalityRepository municipalityRepository, + IStateTaxRateRepository stateTaxRateRepository, + Func dbContext) + { + this.mapper = mapper; + this.municipalityRepository = municipalityRepository; + this.stateTaxRateRepository = stateTaxRateRepository; + this.dbContext = dbContext; + } + + public Task> GetAllAsync() + { + return Task.FromResult(mapper.Map>(municipalityRepository.GetAll())); + } + + /// + /// Searches the specified search filter. + /// + /// The search filter. + /// List of municipalities. + public IEnumerable Search(MunicipalitySearchFilter searchFilter) + { + foreach (var entity in municipalityRepository.Search(searchFilter)) + { + var model = mapper.Map(entity); + + if (searchFilter.YearOfValidity.HasValue) + { + if (!model.DateOfMutation.HasValue) + { + yield return model; + } + else if (model.DateOfMutation.Value.Year > searchFilter.YearOfValidity) + { + yield return model; + } + } + else + { + yield return model; + } + } + } + + public Task> GetAsync(int bfsNumber, int year) + { + Option entity = municipalityRepository.GetAll() + .FirstOrDefault(item => item.BfsNumber == bfsNumber + && string.IsNullOrEmpty(item.DateOfMutation)); + + return entity + .Match>( + Some: item => mapper.Map(item), + None: () => $"Municipality not found by BFS number {bfsNumber}") + .AsTask(); + } + + /// + public Task> GetAllSupportTaxCalculationAsync() + { + IReadOnlyCollection municipalities = + stateTaxRateRepository.TaxRates() + .AsEnumerable() + .GroupBy(keySelector => new + { + Id = keySelector.BfsId, + Name = keySelector.MunicipalityName, + keySelector.Canton, + }) + .Select(item => new TaxSupportedMunicipalityModel + { + BfsMunicipalityNumber = item.Key.Id, + Name = item.Key.Name, + Canton = Enum.Parse(item.Key.Canton), + MaxSupportedYear = item.Max(entity => entity.Year), + }) + .OrderBy(item => item.Name) + .ToList(); + + return Task.FromResult(municipalities); + } + } +} diff --git a/src/Infrastructure/PostOpenApi/IPostOpenApiClient.cs b/src/Infrastructure/PostOpenApi/IPostOpenApiClient.cs new file mode 100644 index 00000000..8b700e45 --- /dev/null +++ b/src/Infrastructure/PostOpenApi/IPostOpenApiClient.cs @@ -0,0 +1,9 @@ +using Infrastructure.PostOpenApi.Models; + +namespace Infrastructure.PostOpenApi +{ + public interface IPostOpenApiClient + { + Task GetZipCodesAsync(int limit, int offset); + } +} diff --git a/src/Infrastructure/PostOpenApi/Models/OpenApiZipDetail.cs b/src/Infrastructure/PostOpenApi/Models/OpenApiZipDetail.cs new file mode 100644 index 00000000..4f84b90b --- /dev/null +++ b/src/Infrastructure/PostOpenApi/Models/OpenApiZipDetail.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace Infrastructure.PostOpenApi.Models +{ + public class OpenApiZipDetail + { + [JsonPropertyName("timestamp")] + public DateTime TimeStamp { get; set; } + + [JsonPropertyName("fields")] + public OpenApiZipFields Fields { get; set; } + } +} diff --git a/src/Infrastructure/PostOpenApi/Models/OpenApiZipFields.cs b/src/Infrastructure/PostOpenApi/Models/OpenApiZipFields.cs new file mode 100644 index 00000000..42687e75 --- /dev/null +++ b/src/Infrastructure/PostOpenApi/Models/OpenApiZipFields.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; + +namespace Infrastructure.PostOpenApi.Models +{ + public class OpenApiZipFields + { + [JsonPropertyName("bfsnr")] + public int BfsCode { get; set; } + + [JsonPropertyName("postleitzahl")] + public string ZipCode { get; set; } + + [JsonPropertyName("plz_zz")] + public string ZipCodeAddOn { get; set; } + + [JsonPropertyName("gilt_ab_dat")] + public string DateOfValidity { get; set; } + + [JsonPropertyName("ortbez27")] + public string MunicipalityName { get; set; } + + [JsonPropertyName("kanton")] + public string Canton { get; set; } + + [JsonPropertyName("sprachcode")] + public int LanguageCode { get; set; } + } +} diff --git a/src/Infrastructure/PostOpenApi/Models/OpenApiZipInfo.cs b/src/Infrastructure/PostOpenApi/Models/OpenApiZipInfo.cs new file mode 100644 index 00000000..34950eab --- /dev/null +++ b/src/Infrastructure/PostOpenApi/Models/OpenApiZipInfo.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace Infrastructure.PostOpenApi.Models +{ + public class OpenApiZipInfo + { + [JsonPropertyName("total_count")] + public int TotalCount { get; set; } + + [JsonPropertyName("records")] + public IEnumerable Records { get; set; } + } +} diff --git a/src/Infrastructure/PostOpenApi/Models/OpenApiZipRecord.cs b/src/Infrastructure/PostOpenApi/Models/OpenApiZipRecord.cs new file mode 100644 index 00000000..1c8c3a6c --- /dev/null +++ b/src/Infrastructure/PostOpenApi/Models/OpenApiZipRecord.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace Infrastructure.PostOpenApi.Models +{ + public class OpenApiZipRecord + { + [JsonPropertyName("record")] + public OpenApiZipDetail Record { get; set; } + } +} diff --git a/src/Infrastructure/PostOpenApi/PostOpenApiClient.cs b/src/Infrastructure/PostOpenApi/PostOpenApiClient.cs new file mode 100644 index 00000000..9a08152c --- /dev/null +++ b/src/Infrastructure/PostOpenApi/PostOpenApiClient.cs @@ -0,0 +1,29 @@ +using System.Net.Http.Json; +using Infrastructure.PostOpenApi.Models; + +namespace Infrastructure.PostOpenApi +{ + public class PostOpenApiClient : IPostOpenApiClient + { + internal static string ClientName = "PostOpenApiClient"; + + private readonly IHttpClientFactory httpClientFactory; + + public PostOpenApiClient(IHttpClientFactory httpClientFactory) + { + this.httpClientFactory = httpClientFactory; + } + + public Task GetZipCodesAsync(int limit, int offset) + { + string[] fieldNames = { "postleitzahl","plz_zz", "gilt_ab_dat", "bfsnr", "kanton", "ortbez27" } ; + + HttpClient client = httpClientFactory.CreateClient(ClientName); + + string partFields = string.Join("%2C%20", fieldNames); + + return client.GetFromJsonAsync( + $"plz_verzeichnis_v2/records?select={partFields}&limit={limit}&offset={offset}&timezone=UTC"); + } + } +} diff --git a/src/Infrastructure/PostOpenApi/PostOpenApiClientMock.cs b/src/Infrastructure/PostOpenApi/PostOpenApiClientMock.cs new file mode 100644 index 00000000..bca223f9 --- /dev/null +++ b/src/Infrastructure/PostOpenApi/PostOpenApiClientMock.cs @@ -0,0 +1,198 @@ +using System.Globalization; +using Infrastructure.PostOpenApi.Models; +using LanguageExt; + +namespace Infrastructure.PostOpenApi +{ + /// + /// Not yet used as Post Open API is not part of any user calls. + /// + public class PostOpenApiClientMock : IPostOpenApiClient + { + public Task GetZipCodesAsync(int limit, int offset) + { + return GetMockData().AsTask(); + } + + private OpenApiZipInfo GetMockData() + { + return new OpenApiZipInfo + { + TotalCount = 10, + Records = new List + { + new() + { + Record = new OpenApiZipDetail + { + Fields = new OpenApiZipFields + { + BfsCode = 261, + Canton = "ZH", + LanguageCode = 0, + MunicipalityName = "Zürich", + ZipCode = "8047", + ZipCodeAddOn = "00", + DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) + }, + TimeStamp = new DateTime(2022, 3, 17) + } + }, + new() + { + Record = new OpenApiZipDetail + { + Fields = new OpenApiZipFields + { + BfsCode = 951, + Canton = "BE", + LanguageCode = 0, + MunicipalityName = "Affoltern im Emmental", + ZipCode = "3416", + ZipCodeAddOn = "00", + DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) + }, + TimeStamp = new DateTime(2022, 3, 17) + } + }, + new() + { + Record = new OpenApiZipDetail + { + Fields = new OpenApiZipFields + { + BfsCode = 2196, + Canton = "FR", + LanguageCode = 0, + MunicipalityName = "Fribourg", + ZipCode = "1700", + ZipCodeAddOn = "00", + DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) + }, + TimeStamp = new DateTime(2022, 3, 17) + } + }, + new() + { + Record = new OpenApiZipDetail + { + Fields = new OpenApiZipFields + { + BfsCode = 2196, + Canton = "AG", + LanguageCode = 0, + MunicipalityName = "Aarau", + ZipCode = "5000", + ZipCodeAddOn = "00", + DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) + }, + TimeStamp = new DateTime(2022, 3, 17) + } + }, + new() + { + Record = new OpenApiZipDetail + { + Fields = new OpenApiZipFields + { + BfsCode = 6266, + Canton = "VS", + LanguageCode = 0, + MunicipalityName = "Sion", + ZipCode = "1950", + ZipCodeAddOn = "00", + DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) + }, + TimeStamp = new DateTime(2022, 3, 17) + } + }, + new() + { + Record = new OpenApiZipDetail + { + Fields = new OpenApiZipFields + { + BfsCode = 1201, + Canton = "UR", + LanguageCode = 0, + MunicipalityName = "Altdorf UR", + ZipCode = "6460", + ZipCodeAddOn = "00", + DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) + }, + TimeStamp = new DateTime(2022, 3, 17) + } + }, + new() + { + Record = new OpenApiZipDetail + { + Fields = new OpenApiZipFields + { + BfsCode = 1061, + Canton = "LU", + LanguageCode = 0, + MunicipalityName = "Luzern", + ZipCode = "6000", + ZipCodeAddOn = "00", + DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) + }, + TimeStamp = new DateTime(2022, 3, 17) + } + }, + new() + { + Record = new OpenApiZipDetail + { + Fields = new OpenApiZipFields + { + BfsCode = 2762, + Canton = "FR", + LanguageCode = 0, + MunicipalityName = "Allschwil", + ZipCode = "4123", + ZipCodeAddOn = "00", + DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) + }, + TimeStamp = new DateTime(2022, 3, 17) + } + }, + new() + { + Record = new OpenApiZipDetail + { + Fields = new OpenApiZipFields + { + BfsCode = 6742, + Canton = "AG", + LanguageCode = 0, + MunicipalityName = "Les Bois", + ZipCode = "2336", + ZipCodeAddOn = "00", + DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) + }, + TimeStamp = new DateTime(2022, 3, 17) + } + }, + new() + { + Record = new OpenApiZipDetail + { + Fields = new OpenApiZipFields + { + BfsCode = 3851, + Canton = "VS", + LanguageCode = 0, + MunicipalityName = "Davos Dorf", + ZipCode = "7260", + ZipCodeAddOn = "00", + DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) + }, + TimeStamp = new DateTime(2022, 3, 17) + } + }, + } + }; + } + } +} diff --git a/src/Infrastructure/PostOpenApi/ServiceCollectionExtensions.cs b/src/Infrastructure/PostOpenApi/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..d5ef7fe4 --- /dev/null +++ b/src/Infrastructure/PostOpenApi/ServiceCollectionExtensions.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Infrastructure.PostOpenApi +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddPostOpenApiClient(this IServiceCollection services, IConfiguration configuration) + { + string baseUrl = configuration["TaxCalculatorClient:PostOpenApiBaseUrl"]; + + services.AddHttpClient(PostOpenApiClient.ClientName, c => + { + c.BaseAddress = new Uri(baseUrl); + }); + + services.AddTransient(); + + return services; + } + } +} diff --git a/src/Infrastructure/Tax.Data/FederalTaxRateRepository.cs b/src/Infrastructure/Tax.Data/FederalTaxRateRepository.cs new file mode 100644 index 00000000..96a11737 --- /dev/null +++ b/src/Infrastructure/Tax.Data/FederalTaxRateRepository.cs @@ -0,0 +1,25 @@ +using Application.Tax.Proprietary.Enums; +using Application.Tax.Proprietary.Repositories; +using Domain.Models.Tax; + +namespace Infrastructure.Tax.Data +{ + public class FederalTaxRateRepository : IFederalTaxRateRepository + { + private readonly Func federalDbContextFunc; + + public FederalTaxRateRepository(Func federalDbContextFunc) + { + this.federalDbContextFunc = federalDbContextFunc; + } + + public IEnumerable TaxRates(int calculationYear, TariffType typeId) + { + using var dbContext = federalDbContextFunc(); + return dbContext.Tariffs + .Where(item => item.Year == calculationYear) + .Where(item => item.TariffType == (int)typeId) + .ToList(); + } + } +} diff --git a/src/Tax.Data/FederalTaxTariffDbContext.cs b/src/Infrastructure/Tax.Data/FederalTaxTariffDbContext.cs similarity index 82% rename from src/Tax.Data/FederalTaxTariffDbContext.cs rename to src/Infrastructure/Tax.Data/FederalTaxTariffDbContext.cs index 1452c022..311edc7f 100644 --- a/src/Tax.Data/FederalTaxTariffDbContext.cs +++ b/src/Infrastructure/Tax.Data/FederalTaxTariffDbContext.cs @@ -1,7 +1,7 @@ -using Microsoft.EntityFrameworkCore; -using Tax.Data.Abstractions.Models; +using Domain.Models.Tax; +using Microsoft.EntityFrameworkCore; -namespace Tax.Data +namespace Infrastructure.Tax.Data { public class FederalTaxTariffDbContext : DbContext { diff --git a/src/Tax.Data/MunicipalityDbContext.cs b/src/Infrastructure/Tax.Data/MunicipalityDbContext.cs similarity index 87% rename from src/Tax.Data/MunicipalityDbContext.cs rename to src/Infrastructure/Tax.Data/MunicipalityDbContext.cs index 41979225..74d18be5 100644 --- a/src/Tax.Data/MunicipalityDbContext.cs +++ b/src/Infrastructure/Tax.Data/MunicipalityDbContext.cs @@ -1,7 +1,8 @@ -using Microsoft.EntityFrameworkCore; -using Tax.Data.Abstractions.Models; +using Domain.Models.Municipality; +using Domain.Models.Tax; +using Microsoft.EntityFrameworkCore; -namespace Tax.Data +namespace Infrastructure.Tax.Data { public class MunicipalityDbContext : DbContext { diff --git a/src/Infrastructure/Tax.Data/Populate/SearchMunicipalityRequest.cs b/src/Infrastructure/Tax.Data/Populate/SearchMunicipalityRequest.cs new file mode 100644 index 00000000..84a4af7f --- /dev/null +++ b/src/Infrastructure/Tax.Data/Populate/SearchMunicipalityRequest.cs @@ -0,0 +1,4 @@ +namespace Infrastructure.Tax.Data.Populate +{ + public record SearchMunicipalityRequest(string Zipcode, string Name); +} diff --git a/src/Infrastructure/Tax.Data/Populate/SearchResultType.cs b/src/Infrastructure/Tax.Data/Populate/SearchResultType.cs new file mode 100644 index 00000000..97c3822f --- /dev/null +++ b/src/Infrastructure/Tax.Data/Populate/SearchResultType.cs @@ -0,0 +1,10 @@ +namespace Infrastructure.Tax.Data.Populate +{ + public enum SearchResultType + { + None, + Match, + NotUnique, + NotSet + } +} diff --git a/src/Infrastructure/Tax.Data/Populate/StaticTaxDataPopulateService.cs b/src/Infrastructure/Tax.Data/Populate/StaticTaxDataPopulateService.cs new file mode 100644 index 00000000..475c42b8 --- /dev/null +++ b/src/Infrastructure/Tax.Data/Populate/StaticTaxDataPopulateService.cs @@ -0,0 +1,360 @@ +using System.Threading.Channels; +using Application.Tax.Estv.Client; +using Application.Tax.Estv.Client.Models; +using Domain.Models.Municipality; +using Infrastructure.DataStaging; +using Microsoft.EntityFrameworkCore; + +namespace Infrastructure.Tax.Data.Populate +{ + public class StaticTaxDataPopulateService : ITaxDataPopulateService + { + private readonly MunicipalityDbContext municipalityDbContext; + private readonly IEstvTaxCalculatorClient estvTaxCalculatorClient; + + public StaticTaxDataPopulateService( + MunicipalityDbContext municipalityDbContext, IEstvTaxCalculatorClient estvTaxCalculatorClient) + { + this.municipalityDbContext = municipalityDbContext; + this.estvTaxCalculatorClient = estvTaxCalculatorClient; + } + + public async Task PopulateWithTaxLocationAsync(bool doClear) + { + const int numberOfReaders = 5; + const int numberOfFetchLevels = 6; + + if (doClear) + { + foreach (var municipalityEntity in municipalityDbContext.MunicipalityEntities.AsNoTracking()) + { + municipalityEntity.TaxLocationId = null; + municipalityEntity.Remark = null; + municipalityDbContext.Update(municipalityEntity); + } + + await municipalityDbContext.SaveChangesAsync(CancellationToken.None); + + municipalityDbContext.ChangeTracker.Clear(); + } + + var fetchLevelChannels = new Channel[numberOfFetchLevels]; + + for (int i = 0; i < fetchLevelChannels.Length; i++) + { + fetchLevelChannels[i] = Channel.CreateUnbounded(); + } + + Channel dbStoreChannel = Channel.CreateUnbounded(); + + Dictionary>> clientApiFetcher = + new Dictionary>> + { + { 0, entity => SearchApi(new SearchMunicipalityRequest(entity.ZipCode, entity.CleanName)) }, + { 1, entity => SearchApi(new SearchMunicipalityRequest(string.Empty, entity.CleanName)) }, + { 2, entity => SearchApi(new SearchMunicipalityRequest(entity.ZipCode, GetSplitName(entity))) }, + { 3, entity => SearchApi(new SearchMunicipalityRequest("", GetSplitName(entity))) }, + { 4, entity => SearchApi(new SearchMunicipalityRequest(entity.ZipCode, string.Empty)) }, + { 5, entity => SearchApi(new SearchMunicipalityRequest("", entity.OverruledName)) }, + }; + + Task[] fetchConsumers0 = Enumerable + .Range(1, numberOfReaders) + .Select(_ => FetchConsumerAsync(fetchLevelChannels[0], fetchLevelChannels[1], clientApiFetcher[0])) + .ToArray(); + + Task[] fetchConsumers1 = Enumerable + .Range(1, numberOfReaders) + .Select(_ => FetchConsumerAsync(fetchLevelChannels[1], fetchLevelChannels[2], clientApiFetcher[1])) + .ToArray(); + + Task[] fetchConsumers2 = Enumerable + .Range(1, numberOfReaders) + .Select(_ => FetchConsumerAsync(fetchLevelChannels[2], fetchLevelChannels[3], clientApiFetcher[2])) + .ToArray(); + + Task[] fetchConsumers3 = Enumerable + .Range(1, numberOfReaders) + .Select(_ => FetchConsumerAsync(fetchLevelChannels[3], fetchLevelChannels[4], clientApiFetcher[3])) + .ToArray(); + + Task[] fetchConsumers4 = Enumerable + .Range(1, numberOfReaders) + .Select(_ => FetchConsumerAsync(fetchLevelChannels[4], fetchLevelChannels[5], clientApiFetcher[4])) + .ToArray(); + + Task[] fetchConsumers5 = Enumerable + .Range(1, numberOfReaders) + .Select(_ => FetchConsumerAsync(fetchLevelChannels[5], dbStoreChannel, clientApiFetcher[5])) + .ToArray(); + + await Task.Run(async () => await FillInitialStage(fetchLevelChannels[0])); + + fetchLevelChannels[0].Writer.Complete(); + await Task.WhenAll(fetchConsumers0); + + fetchLevelChannels[1].Writer.Complete(); + await Task.WhenAll(fetchConsumers1); + + fetchLevelChannels[2].Writer.Complete(); + await Task.WhenAll(fetchConsumers2); + + fetchLevelChannels[3].Writer.Complete(); + await Task.WhenAll(fetchConsumers3); + + fetchLevelChannels[4].Writer.Complete(); + await Task.WhenAll(fetchConsumers4); + + fetchLevelChannels[5].Writer.Complete(); + await Task.WhenAll(fetchConsumers5); + + dbStoreChannel.Writer.Complete(); + + return await StoreResults(dbStoreChannel); + + async Task FetchConsumerAsync( + Channel fetchChannel, + Channel successorChannel, + Func> searcher) + { + while (await fetchChannel.Reader.WaitToReadAsync()) + { + if (!fetchChannel.Reader.TryRead(out TaxLocationSearchHolder data)) + { + continue; + } + + if (data.SearchResultType == SearchResultType.Match) + { + await successorChannel.Writer.WriteAsync(data); + } + else + { + TaxLocation[] locations = await searcher(data.MunicipalityEntity); + + TaxLocationSearchHolder searchResult = Dispatch(data.SearchLevel, locations, data.MunicipalityEntity); + + await successorChannel.Writer.WriteAsync(searchResult); + } + } + } + } + + private TaxLocationSearchHolder Dispatch(int searchLevel, TaxLocation[] taxLocations, MunicipalityEntity municipalityEntity) + { + TaxLocationSearchHolder searchHolder = CreateSearchHolder(taxLocations, municipalityEntity); + searchHolder.SearchLevel = searchLevel; + searchHolder.SearchResultType = SearchResultType.NotSet; + + (bool match, TaxLocation[] locations) = ApplyCheckers(searchHolder); + + if (match && locations.Length == 1) + { + return searchHolder with + { + SearchResultType = SearchResultType.Match, + TaxLocations = locations + }; + } + + return searchHolder; + } + + private TaxLocationSearchHolder CreateSearchHolder(TaxLocation[] locations, MunicipalityEntity entity) + { + if (locations.Length > 1) + { + return new TaxLocationSearchHolder + { + MunicipalityEntity = entity, + TaxLocations = locations, + SearchResultType = SearchResultType.NotUnique, + SearchLevel = 1, + }; + } + + if (locations.Length == 0) + { + return new TaxLocationSearchHolder + { + MunicipalityEntity = entity, + SearchResultType = SearchResultType.None, + SearchLevel = 1, + }; + } + + return new TaxLocationSearchHolder + { + MunicipalityEntity = entity, + SearchResultType = SearchResultType.Match, + TaxLocations = locations, + }; + } + + private async Task FillInitialStage(Channel fetchChannel) + { + foreach (MunicipalityEntity model in municipalityDbContext.MunicipalityEntities + .Where(item => item.SuccessorId == 0 && + item.MutationType == 11 && + item.TaxLocationId == null)) + { + await fetchChannel.Writer.WriteAsync( + new TaxLocationSearchHolder + { + SearchLevel = 0, + MunicipalityEntity = model, + TaxLocations = Array.Empty() + }); + } + } + + private async Task SearchApi(SearchMunicipalityRequest searchRequest) + { + if (searchRequest is null || (searchRequest.Zipcode == "" && searchRequest.Name == "")) + { + return Array.Empty(); + } + + return await estvTaxCalculatorClient.GetTaxLocationsAsync(searchRequest.Zipcode, searchRequest.Name) switch + { + null => Array.Empty(), + { } a => a + }; + } + + private (bool, TaxLocation[]) ApplyCheckers(TaxLocationSearchHolder searchHolder) + { + Func[] checkers = + { + MatchZipAndName, + MatchNameOnly, + MatchCanton, + MatchContainsNameAndCanton, + MatchByZipAsTaxId, + MatchByNamePart + }; + + if (searchHolder.TaxLocations == null) + { + return (false, null); + } + + foreach (var checker in checkers) + { + TaxLocation[] filteredLocations = checker(searchHolder.TaxLocations, searchHolder.MunicipalityEntity); + + if (filteredLocations.Length == 1) + { + return (true, filteredLocations); + } + } + + return (false, searchHolder.TaxLocations); + } + + private async Task StoreResults(Channel dbStoreChannel) + { + int count = 0; + await foreach (TaxLocationSearchHolder searchHolder in dbStoreChannel.Reader.ReadAllAsync(CancellationToken.None)) + { + // not found + if (searchHolder.SearchResultType is SearchResultType.None or + SearchResultType.NotUnique or + SearchResultType.NotSet) + { + searchHolder.MunicipalityEntity.Remark = searchHolder.SearchResultType.ToString(); + municipalityDbContext.Update(searchHolder.MunicipalityEntity); + continue; + } + + searchHolder.MunicipalityEntity.TaxLocationId = searchHolder.TaxLocations[0].Id; + if (searchHolder.MunicipalityEntity.ZipCode is null && searchHolder.TaxLocations[0].ZipCode is not null) + { + searchHolder.MunicipalityEntity.ZipCode = searchHolder.TaxLocations[0].ZipCode; + } + + municipalityDbContext.Update(searchHolder.MunicipalityEntity); + + count++; + } + + await municipalityDbContext.SaveChangesAsync(CancellationToken.None); + + return count; + } + + private TaxLocation[] MatchZipAndName(TaxLocation[] sourceLocations, MunicipalityEntity entity) + { + if (entity is null) + { + return Array.Empty(); + } + + return (sourceLocations ?? Array.Empty()) + .Where(item => item.ZipCode == entity.ZipCode && item.City == entity.CleanName) + .ToArray(); + } + + private TaxLocation[] MatchNameOnly(TaxLocation[] sourceLocations, MunicipalityEntity entity) + { + if (entity is null) + { + return Array.Empty(); + } + + return (sourceLocations ?? Array.Empty()) + .Where(item => item.City == entity.CleanName) + .ToArray(); + } + + private TaxLocation[] MatchCanton(TaxLocation[] sourceLocations, MunicipalityEntity entity) + { + return (sourceLocations ?? Array.Empty()) + .Where(item => item.Canton == entity.Canton) + .ToArray(); + } + + private TaxLocation[] MatchContainsNameAndCanton(TaxLocation[] sourceLocations, MunicipalityEntity entity) + { + return (sourceLocations ?? Array.Empty()) + .Where(item => item.City.Contains(entity.CleanName) && item.Canton == entity.Canton) + .ToArray(); + } + + private TaxLocation[] MatchByZipAsTaxId(TaxLocation[] sourceLocations, MunicipalityEntity entity) + { + int taxIdFromZipCode = Convert.ToInt32(entity.ZipCode) * 100000; + + return (sourceLocations ?? Array.Empty()) + .Where(item => item.Id == taxIdFromZipCode) + .ToArray(); + } + + private TaxLocation[] MatchByNamePart(TaxLocation[] sourceLocations, MunicipalityEntity entity) + { + // just try part of name before a hypen (ie. Illnau-Effretikon) + return GetSplitName(entity) switch + { + { } p => Filter(p), + _ => Array.Empty() + }; + + TaxLocation[] Filter(string part) + { + return (sourceLocations ?? Array.Empty()) + .Where(item => item.City == part) + .ToArray(); + } + } + + // just try part of name before a hypen (ie. Illnau-Effretikon) + private string GetSplitName(MunicipalityEntity entity) + { + return entity.CleanName.Split('-', ' ', '/') switch + { + { Length: 2 } p => p[0], + _ => null + }; + } + } +} diff --git a/src/Infrastructure/Tax.Data/Populate/TaxLocationSearchHolder.cs b/src/Infrastructure/Tax.Data/Populate/TaxLocationSearchHolder.cs new file mode 100644 index 00000000..6bd26be6 --- /dev/null +++ b/src/Infrastructure/Tax.Data/Populate/TaxLocationSearchHolder.cs @@ -0,0 +1,16 @@ +using Application.Tax.Estv.Client.Models; +using Domain.Models.Municipality; + +namespace Infrastructure.Tax.Data.Populate +{ + public record TaxLocationSearchHolder + { + public MunicipalityEntity MunicipalityEntity { get; init; } + + public TaxLocation[] TaxLocations { get; init; } + + public int SearchLevel { get; set; } + + public SearchResultType SearchResultType { get; set; } + } +} diff --git a/src/Infrastructure/Tax.Data/StateTaxRateRepository.cs b/src/Infrastructure/Tax.Data/StateTaxRateRepository.cs new file mode 100644 index 00000000..926d427e --- /dev/null +++ b/src/Infrastructure/Tax.Data/StateTaxRateRepository.cs @@ -0,0 +1,29 @@ +using Application.Tax.Proprietary.Repositories; +using Domain.Models.Tax; +using Microsoft.EntityFrameworkCore; + +namespace Infrastructure.Tax.Data +{ + public class StateTaxRateRepository : IStateTaxRateRepository + { + private readonly Func dbContextFunc; + + public StateTaxRateRepository(Func dbContextFunc) + { + this.dbContextFunc = dbContextFunc; + } + + public IEnumerable TaxRates() + { + using var dbContext = dbContextFunc(); + return dbContext.Rates.AsNoTracking().ToList(); + } + + public TaxRateEntity TaxRates(int calculationYear, int municipalityId) + { + using var dbContext = dbContextFunc(); + return dbContext.Rates.AsNoTracking() + .FirstOrDefault(item => item.BfsId == municipalityId && item.Year == calculationYear); + } + } +} diff --git a/src/Tax.Data/TaxDataExtensions.cs b/src/Infrastructure/Tax.Data/TaxDataExtensions.cs similarity index 82% rename from src/Tax.Data/TaxDataExtensions.cs rename to src/Infrastructure/Tax.Data/TaxDataExtensions.cs index 11bf7b09..48a0a65b 100644 --- a/src/Tax.Data/TaxDataExtensions.cs +++ b/src/Infrastructure/Tax.Data/TaxDataExtensions.cs @@ -1,10 +1,13 @@ -using System; +using Application.Municipality; +using Application.Tax.Proprietary.Repositories; +using Infrastructure.DataStaging; +using Infrastructure.Municipality; +using Infrastructure.Tax.Data.Populate; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Tax.Data.Abstractions; -namespace Tax.Data +namespace Infrastructure.Tax.Data { public static class TaxDataExtensions { @@ -71,7 +74,10 @@ public static void AddTaxData( collection.AddSingleton>(provider => provider.GetRequiredService); collection.AddTransient(); - collection.AddSingleton(); + collection.AddSingleton(); + collection.AddTransient(); + collection.AddTransient(); + collection.AddTransient(); } } } diff --git a/src/Tax.Data/TaxRateDbContext.cs b/src/Infrastructure/Tax.Data/TaxRateDbContext.cs similarity index 81% rename from src/Tax.Data/TaxRateDbContext.cs rename to src/Infrastructure/Tax.Data/TaxRateDbContext.cs index 3e1b5a6b..1d16b85e 100644 --- a/src/Tax.Data/TaxRateDbContext.cs +++ b/src/Infrastructure/Tax.Data/TaxRateDbContext.cs @@ -1,7 +1,7 @@ -using Microsoft.EntityFrameworkCore; -using Tax.Data.Abstractions.Models; +using Domain.Models.Tax; +using Microsoft.EntityFrameworkCore; -namespace Tax.Data +namespace Infrastructure.Tax.Data { public class TaxRateDbContext : DbContext { diff --git a/src/Tax.Data/TaxTariffData.cs b/src/Infrastructure/Tax.Data/TaxTariffData.cs similarity index 75% rename from src/Tax.Data/TaxTariffData.cs rename to src/Infrastructure/Tax.Data/TaxTariffData.cs index b6deb8ce..20d41ecf 100644 --- a/src/Tax.Data/TaxTariffData.cs +++ b/src/Infrastructure/Tax.Data/TaxTariffData.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using Application.Tax.Proprietary.Repositories; +using Domain.Models.Tax; using Microsoft.EntityFrameworkCore; -using Tax.Data.Abstractions; -using Tax.Data.Abstractions.Models; -namespace Tax.Data +namespace Infrastructure.Tax.Data { - public class TaxTariffData : ITaxTariffData + public class TaxTariffData : ITaxTariffRepository { private readonly Func dbContext; diff --git a/src/Tax.Data/TaxTariffDbContext.cs b/src/Infrastructure/Tax.Data/TaxTariffDbContext.cs similarity index 81% rename from src/Tax.Data/TaxTariffDbContext.cs rename to src/Infrastructure/Tax.Data/TaxTariffDbContext.cs index e5c0f091..fa48b75f 100644 --- a/src/Tax.Data/TaxTariffDbContext.cs +++ b/src/Infrastructure/Tax.Data/TaxTariffDbContext.cs @@ -1,7 +1,7 @@ -using Microsoft.EntityFrameworkCore; -using Tax.Data.Abstractions.Models; +using Domain.Models.Tax; +using Microsoft.EntityFrameworkCore; -namespace Tax.Data +namespace Infrastructure.Tax.Data { public class TaxTariffDbContext : DbContext { diff --git a/src/Tax.Data/files/TaxDb.db b/src/Infrastructure/files/TaxDb.db similarity index 100% rename from src/Tax.Data/files/TaxDb.db rename to src/Infrastructure/files/TaxDb.db diff --git a/src/LICENSE b/src/LICENSE new file mode 100644 index 00000000..add071f3 --- /dev/null +++ b/src/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 PensionTools / Dieter Niggeler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/PensionTools.sln b/src/PensionTools.sln index e8c8855f..e0665640 100644 --- a/src/PensionTools.sln +++ b/src/PensionTools.sln @@ -3,91 +3,94 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31919.166 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tax.Data", "Tax.Data\Tax.Data.csproj", "{77C27B5A-B471-4439-A0F9-C334C0367C06}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tax.Data.Tests", "Tax.Data.Tests\Tax.Data.Tests.csproj", "{38D1A086-2A5D-4654-B465-04C662DDEA1A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TaxCalculator.Abstractions", "TaxCalculator.Abstractions\TaxCalculator.Abstractions.csproj", "{CF096018-03FB-42BD-858A-B8272CB6302B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TaxCalculator.Tests", "TaxCalculator.Tests\TaxCalculator.Tests.csproj", "{1A039171-DE86-4DCE-8D47-8626666004E3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TaxCalculator", "TaxCalculator\TaxCalculator.csproj", "{4290D0E0-FCE3-48AE-AD69-A96439870A12}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TaxCalculator.WebApi", "TaxCalculator.WebApi\TaxCalculator.WebApi.csproj", "{19811879-AEFC-4F75-BF52-10CB8471F5CB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tax.Data.Abstractions", "Tax.Data.Abstractions\Tax.Data.Abstractions.csproj", "{8B3ECC01-C4F4-4CF9-AEE2-34252C84894F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tax.Tools.Comparison.Tests", "Tax.Tools.Comparison.Tests\Tax.Tools.Comparison.Tests.csproj", "{71DD068D-41A9-4084-8808-5190BB4D1AC0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tax.Data.Tests", "Tax.Data.Tests\Tax.Data.Tests.csproj", "{38D1A086-2A5D-4654-B465-04C662DDEA1A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonTypes", "CommonTypes\CommonTypes.csproj", "{7432DA0D-73B2-45F8-B6D8-2FF7423DB6BA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TaxCalculator.Tests", "TaxCalculator.Tests\TaxCalculator.Tests.csproj", "{1A039171-DE86-4DCE-8D47-8626666004E3}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{0F6FD2BA-85B0-4ABA-A3F9-A4F6BE4808B1}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TaxCalculators", "TaxCalculators", "{3B665A6F-1CE9-48B9-9736-A5C05C49335D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonUtils", "CommonUtils\CommonUtils.csproj", "{6A4CA971-D78F-48A7-84A0-81B7ADC92AFE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TaxCalculator.WebApi", "TaxCalculator.WebApi\TaxCalculator.WebApi.csproj", "{19811879-AEFC-4F75-BF52-10CB8471F5CB}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C1ED00D3-E701-4E17-9117-1B95EBD423D1}" + ProjectSection(SolutionItems) = preProject + LICENSE = LICENSE + EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Higher Order Calculators", "Higher Order Calculators", "{BF9972ED-AFAC-4E39-9AAD-89F46338CED1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BvgCalculator.Tests", "BvgCalculator.Tests\BvgCalculator.Tests.csproj", "{43E1CB25-89F2-4257-B95D-D2CFDA7CA472}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tax.Tools.Comparison", "Tax.Tools.Comparison\Tax.Tools.Comparison\Tax.Tools.Comparison.csproj", "{7F3959D0-0998-41D0-B362-2A1366CA1270}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BvgCalculator.Integration.Tests", "BvgCalculator.Integration.Tests\BvgCalculator.Integration.Tests.csproj", "{F270AF92-0177-4596-9ECD-BE34A16F916F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tax.Tools.Comparison.Abstractions", "Tax.Tools.Comparison\Tax.Tools.Comparison.Abstractions\Tax.Tools.Comparison.Abstractions.csproj", "{9354F5AC-21E6-4FE9-AF2E-4D80972ACE29}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PensionVersusCapitalCalculator.Tests", "PensionVersusCapitalCalculator.Tests\PensionVersusCapitalCalculator.Tests.csproj", "{E2D9578A-4597-40A3-8E4F-DCAB03733114}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tax.Tools.Comparison.Tests", "Tax.Tools.Comparison\Tax.Tools.Comparison.Tests\Tax.Tools.Comparison.Tests.csproj", "{71DD068D-41A9-4084-8808-5190BB4D1AC0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calculators.CashFlow.Tests", "Calculators.CashFlow.Tests\Calculators.CashFlow.Tests.csproj", "{A0EF9592-1653-4A8F-BFDB-955BA806B590}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PensionCalculators", "PensionCalculators", "{4224614A-B5A0-4633-BF49-A485C0E61595}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorApp", "BlazorApp\BlazorApp.csproj", "{82A13E90-F0A6-4A37-96A9-902D87FDF31A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BvgCalculator", "BvgCalculator\BvgCalculator.csproj", "{BEA34185-AC53-406B-8A57-F3E6C8F12636}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calculators.CashFlow.Integration.Tests", "Calculators.CashFlow.Integration.Tests\Calculators.CashFlow.Integration.Tests.csproj", "{55F6BA03-6CBC-4A34-8F33-E83062798441}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BvgCalculator.Abstractions", "BvgCalculator.Abstractions\BvgCalculator.Abstractions.csproj", "{CE0A3BC7-0EAC-4004-BDD3-4008C685C8EE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tax.Data.Integration.Tests", "Tax.Data.Integration.Tests\Tax.Data.Integration.Tests.csproj", "{0517B781-DC60-44F8-9B98-4C9A4654BDD4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonTypes", "CommonTypes\CommonTypes.csproj", "{7432DA0D-73B2-45F8-B6D8-2FF7423DB6BA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TaxCalculator.Integration.Tests", "TaxCalculator.Integration.Tests\TaxCalculator.Integration.Tests.csproj", "{47C8A4FD-6756-4474-870B-BD6921163C0B}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{0F6FD2BA-85B0-4ABA-A3F9-A4F6BE4808B1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EstvTaxCalculatorFacade.Integration.Tests", "EstvTaxCalculatorFacade.Integration.Tests\EstvTaxCalculatorFacade.Integration.Tests.csproj", "{202EB507-C7AB-49F2-B048-73267E7BBEC2}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonUtils", "CommonUtils\CommonUtils.csproj", "{6A4CA971-D78F-48A7-84A0-81B7ADC92AFE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PostOpenApi.Integration.Tests", "PostOpenApi.Integration.Tests\PostOpenApi.Integration.Tests.csproj", "{B35C3E62-552B-470D-9471-5B823A1B7831}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C1ED00D3-E701-4E17-9117-1B95EBD423D1}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{06D69571-67BA-449E-997A-B098419523EC}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BvgCalculator.Tests", "BvgCalculator.Tests\BvgCalculator.Tests.csproj", "{43E1CB25-89F2-4257-B95D-D2CFDA7CA472}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorApp.Services.Tests", "BlazorApp.Services.Tests\BlazorApp.Services.Tests.csproj", "{D934E764-94AB-412B-93B5-3C2E3A40E898}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BvgCalculator.Integration.Tests", "BvgCalculator.Integration.Tests\BvgCalculator.Integration.Tests.csproj", "{F270AF92-0177-4596-9ECD-BE34A16F916F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorApp.Components.Tests", "BlazorApp.Components.Tests\BlazorApp.Components.Tests.csproj", "{D28B965D-73AB-4905-AB37-63AF2CB99567}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PensionVersusCapitalCalculator.Abstractions", "PensionVersusCapitalCalculator.Abstractions\PensionVersusCapitalCalculator.Abstractions.csproj", "{568269F6-0D9E-4ADA-AE8C-FDF70814AE00}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain", "Domain\Domain.csproj", "{4747A117-C17E-4642-9934-2FF3D7CCBEBA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PensionVersusCapitalCalculator", "PensionVersusCapitalCalculator\PensionVersusCapitalCalculator.csproj", "{B905A759-712E-4C0B-B027-8DB4D0A7961B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "Application\Application.csproj", "{E4D497A0-3925-43F7-9B5F-A67FA65BABC4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PensionVersusCapitalCalculator.Tests", "PensionVersusCapitalCalculator.Tests\PensionVersusCapitalCalculator.Tests.csproj", "{E2D9578A-4597-40A3-8E4F-DCAB03733114}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "Infrastructure\Infrastructure.csproj", "{8BD68C7A-F83A-4D40-A59F-13C1650A05B6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calculators.CashFlow.Abstractions", "Calculators.Cashflow.Abstractions\Calculators.CashFlow.Abstractions.csproj", "{FF5ECF20-7A50-4950-B911-0D0C9386A008}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{955F3700-AC87-456C-B645-3F9451EAFA57}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calculators.CashFlow", "Calculators.CashFlow\Calculators.CashFlow.csproj", "{6D23DE23-3FBA-4FEF-ABDD-C022F149C355}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IntegrationTests", "IntegrationTests", "{5B7A4E65-54D1-4503-A426-DDA771085FC1}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calculators.CashFlow.Tests", "Calculators.CashFlow.Tests\Calculators.CashFlow.Tests.csproj", "{A0EF9592-1653-4A8F-BFDB-955BA806B590}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{911DCB3B-CEFA-4894-B075-432342C05405}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorApp", "BlazorApp\BlazorApp.csproj", "{82A13E90-F0A6-4A37-96A9-902D87FDF31A}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Data", "Data", "{318B773B-F7D6-4003-A92A-96E5E97A5C32}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calculators.CashFlow.Integration.Tests", "Calculators.CashFlow.Integration.Tests\Calculators.CashFlow.Integration.Tests.csproj", "{55F6BA03-6CBC-4A34-8F33-E83062798441}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tax", "Tax", "{1F650DE4-C5CB-4208-AE86-12C18C0C00F7}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TaxData", "TaxData", "{1081A860-2D1C-4AF4-A901-10B00BF278CF}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{37254007-E34E-4AC6-B17D-F0FC060F196D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tax.Data.Integration.Tests", "Tax.Data.Integration.Tests\Tax.Data.Integration.Tests.csproj", "{0517B781-DC60-44F8-9B98-4C9A4654BDD4}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{73BEA53A-145A-4C04-A615-839C73C97755}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TaxCalculator.Integration.Tests", "TaxCalculator.Integration.Tests\TaxCalculator.Integration.Tests.csproj", "{47C8A4FD-6756-4474-870B-BD6921163C0B}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IntegrationTests", "IntegrationTests", "{48B33FC6-19DD-4237-8524-09D1ED77F692}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EstvTaxCalculators", "EstvTaxCalculators", "{925F3C21-3653-4DA2-99F9-0982736674BA}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI", "UI", "{F88DC1A0-9524-4A84-AE6A-34DC74B610FC}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EstvTaxCalculatorFacade", "EstvTaxCalculatorFacade\EstvTaxCalculatorFacade.csproj", "{A0859BE8-14DC-479C-A20F-561092BF59F6}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{89C541A7-00DE-42C9-A51D-D3E23A22FFB5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EstvTaxCalculatorFacade.Integration.Tests", "EstvTaxCalculatorFacade.Integration.Tests\EstvTaxCalculatorFacade.Integration.Tests.csproj", "{202EB507-C7AB-49F2-B048-73267E7BBEC2}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{6BC3D884-18CD-4328-9EEF-39B7FFBB7F3E}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Post OpenApi", "Post OpenApi", "{6878921D-9AA1-4159-8E29-B440B2E44C9B}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{AC2D3C39-76E0-463D-B235-1B7FCFE98B0B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PostOpenApi.Client", "PostOpenApi.Client\PostOpenApi.Client.csproj", "{8F5A7D0B-09EE-4952-885D-B1738DE243BB}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Aspire", "Aspire", "{C564FA3C-70E4-4C36-93FC-04088D491984}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PostOpenApi.Integration.Tests", "PostOpenApi.Integration.Tests\PostOpenApi.Integration.Tests.csproj", "{B35C3E62-552B-470D-9471-5B823A1B7831}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire", "Aspire\Aspire.csproj", "{74E21B07-87D1-4F7D-8804-EF1397FA48C9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EstvTaxCalculator.Abstractions", "EstvTaxCalculator.Abstractions\EstvTaxCalculator.Abstractions.csproj", "{1F58B6AE-89D4-414A-9397-F775130B3FBF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspireHost", "AspireHost\AspireHost.csproj", "{5099CEDA-9354-44A6-ABE6-DED73307FF02}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tax.Tools.Comparison.Domain", "Tax.Tools.Comparison.Domain\Tax.Tools.Comparison.Domain.csproj", "{9617FEB8-2160-4D3A-B157-2D97EF3B2F56}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Domain", "Domain", "{F1743BB6-65C2-44D3-940C-F0BF17C0557F}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Blazor", "Blazor", "{06D69571-67BA-449E-997A-B098419523EC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain.Tests", "Domain.Tests\Domain.Tests.csproj", "{C68CF325-C730-4111-9F11-E4CF453B620A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorApp.Services.Tests", "BlazorApp.Services.Tests\BlazorApp.Services.Tests.csproj", "{D934E764-94AB-412B-93B5-3C2E3A40E898}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorBvgRevisionApp", "BlazorBvgRevisionApp\BlazorBvgRevisionApp.csproj", "{435F6D3D-DFAA-4A87-A9B5-2B7B0AF2DD0F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorApp.Components.Tests", "BlazorApp.Components.Tests\BlazorApp.Components.Tests.csproj", "{D28B965D-73AB-4905-AB37-63AF2CB99567}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PensionToolsComponentLibrary", "PensionToolsComponentLibrary\PensionToolsComponentLibrary.csproj", "{187FDC48-1AFA-4FA4-AF62-A3FDECFE56DA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -95,22 +98,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {77C27B5A-B471-4439-A0F9-C334C0367C06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77C27B5A-B471-4439-A0F9-C334C0367C06}.Debug|Any CPU.Build.0 = Debug|Any CPU - {77C27B5A-B471-4439-A0F9-C334C0367C06}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77C27B5A-B471-4439-A0F9-C334C0367C06}.Release|Any CPU.Build.0 = Release|Any CPU - {CF096018-03FB-42BD-858A-B8272CB6302B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CF096018-03FB-42BD-858A-B8272CB6302B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CF096018-03FB-42BD-858A-B8272CB6302B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CF096018-03FB-42BD-858A-B8272CB6302B}.Release|Any CPU.Build.0 = Release|Any CPU - {4290D0E0-FCE3-48AE-AD69-A96439870A12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4290D0E0-FCE3-48AE-AD69-A96439870A12}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4290D0E0-FCE3-48AE-AD69-A96439870A12}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4290D0E0-FCE3-48AE-AD69-A96439870A12}.Release|Any CPU.Build.0 = Release|Any CPU - {8B3ECC01-C4F4-4CF9-AEE2-34252C84894F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8B3ECC01-C4F4-4CF9-AEE2-34252C84894F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B3ECC01-C4F4-4CF9-AEE2-34252C84894F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8B3ECC01-C4F4-4CF9-AEE2-34252C84894F}.Release|Any CPU.Build.0 = Release|Any CPU {38D1A086-2A5D-4654-B465-04C662DDEA1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {38D1A086-2A5D-4654-B465-04C662DDEA1A}.Debug|Any CPU.Build.0 = Debug|Any CPU {38D1A086-2A5D-4654-B465-04C662DDEA1A}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -123,26 +110,10 @@ Global {19811879-AEFC-4F75-BF52-10CB8471F5CB}.Debug|Any CPU.Build.0 = Debug|Any CPU {19811879-AEFC-4F75-BF52-10CB8471F5CB}.Release|Any CPU.ActiveCfg = Release|Any CPU {19811879-AEFC-4F75-BF52-10CB8471F5CB}.Release|Any CPU.Build.0 = Release|Any CPU - {7F3959D0-0998-41D0-B362-2A1366CA1270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7F3959D0-0998-41D0-B362-2A1366CA1270}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7F3959D0-0998-41D0-B362-2A1366CA1270}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7F3959D0-0998-41D0-B362-2A1366CA1270}.Release|Any CPU.Build.0 = Release|Any CPU - {9354F5AC-21E6-4FE9-AF2E-4D80972ACE29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9354F5AC-21E6-4FE9-AF2E-4D80972ACE29}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9354F5AC-21E6-4FE9-AF2E-4D80972ACE29}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9354F5AC-21E6-4FE9-AF2E-4D80972ACE29}.Release|Any CPU.Build.0 = Release|Any CPU {71DD068D-41A9-4084-8808-5190BB4D1AC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {71DD068D-41A9-4084-8808-5190BB4D1AC0}.Debug|Any CPU.Build.0 = Debug|Any CPU {71DD068D-41A9-4084-8808-5190BB4D1AC0}.Release|Any CPU.ActiveCfg = Release|Any CPU {71DD068D-41A9-4084-8808-5190BB4D1AC0}.Release|Any CPU.Build.0 = Release|Any CPU - {BEA34185-AC53-406B-8A57-F3E6C8F12636}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BEA34185-AC53-406B-8A57-F3E6C8F12636}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BEA34185-AC53-406B-8A57-F3E6C8F12636}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BEA34185-AC53-406B-8A57-F3E6C8F12636}.Release|Any CPU.Build.0 = Release|Any CPU - {CE0A3BC7-0EAC-4004-BDD3-4008C685C8EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CE0A3BC7-0EAC-4004-BDD3-4008C685C8EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CE0A3BC7-0EAC-4004-BDD3-4008C685C8EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CE0A3BC7-0EAC-4004-BDD3-4008C685C8EE}.Release|Any CPU.Build.0 = Release|Any CPU {7432DA0D-73B2-45F8-B6D8-2FF7423DB6BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7432DA0D-73B2-45F8-B6D8-2FF7423DB6BA}.Debug|Any CPU.Build.0 = Debug|Any CPU {7432DA0D-73B2-45F8-B6D8-2FF7423DB6BA}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -159,26 +130,10 @@ Global {F270AF92-0177-4596-9ECD-BE34A16F916F}.Debug|Any CPU.Build.0 = Debug|Any CPU {F270AF92-0177-4596-9ECD-BE34A16F916F}.Release|Any CPU.ActiveCfg = Release|Any CPU {F270AF92-0177-4596-9ECD-BE34A16F916F}.Release|Any CPU.Build.0 = Release|Any CPU - {568269F6-0D9E-4ADA-AE8C-FDF70814AE00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {568269F6-0D9E-4ADA-AE8C-FDF70814AE00}.Debug|Any CPU.Build.0 = Debug|Any CPU - {568269F6-0D9E-4ADA-AE8C-FDF70814AE00}.Release|Any CPU.ActiveCfg = Release|Any CPU - {568269F6-0D9E-4ADA-AE8C-FDF70814AE00}.Release|Any CPU.Build.0 = Release|Any CPU - {B905A759-712E-4C0B-B027-8DB4D0A7961B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B905A759-712E-4C0B-B027-8DB4D0A7961B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B905A759-712E-4C0B-B027-8DB4D0A7961B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B905A759-712E-4C0B-B027-8DB4D0A7961B}.Release|Any CPU.Build.0 = Release|Any CPU {E2D9578A-4597-40A3-8E4F-DCAB03733114}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E2D9578A-4597-40A3-8E4F-DCAB03733114}.Debug|Any CPU.Build.0 = Debug|Any CPU {E2D9578A-4597-40A3-8E4F-DCAB03733114}.Release|Any CPU.ActiveCfg = Release|Any CPU {E2D9578A-4597-40A3-8E4F-DCAB03733114}.Release|Any CPU.Build.0 = Release|Any CPU - {FF5ECF20-7A50-4950-B911-0D0C9386A008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FF5ECF20-7A50-4950-B911-0D0C9386A008}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FF5ECF20-7A50-4950-B911-0D0C9386A008}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FF5ECF20-7A50-4950-B911-0D0C9386A008}.Release|Any CPU.Build.0 = Release|Any CPU - {6D23DE23-3FBA-4FEF-ABDD-C022F149C355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D23DE23-3FBA-4FEF-ABDD-C022F149C355}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D23DE23-3FBA-4FEF-ABDD-C022F149C355}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D23DE23-3FBA-4FEF-ABDD-C022F149C355}.Release|Any CPU.Build.0 = Release|Any CPU {A0EF9592-1653-4A8F-BFDB-955BA806B590}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A0EF9592-1653-4A8F-BFDB-955BA806B590}.Debug|Any CPU.Build.0 = Debug|Any CPU {A0EF9592-1653-4A8F-BFDB-955BA806B590}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -199,30 +154,14 @@ Global {47C8A4FD-6756-4474-870B-BD6921163C0B}.Debug|Any CPU.Build.0 = Debug|Any CPU {47C8A4FD-6756-4474-870B-BD6921163C0B}.Release|Any CPU.ActiveCfg = Release|Any CPU {47C8A4FD-6756-4474-870B-BD6921163C0B}.Release|Any CPU.Build.0 = Release|Any CPU - {A0859BE8-14DC-479C-A20F-561092BF59F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A0859BE8-14DC-479C-A20F-561092BF59F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A0859BE8-14DC-479C-A20F-561092BF59F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A0859BE8-14DC-479C-A20F-561092BF59F6}.Release|Any CPU.Build.0 = Release|Any CPU {202EB507-C7AB-49F2-B048-73267E7BBEC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {202EB507-C7AB-49F2-B048-73267E7BBEC2}.Debug|Any CPU.Build.0 = Debug|Any CPU {202EB507-C7AB-49F2-B048-73267E7BBEC2}.Release|Any CPU.ActiveCfg = Release|Any CPU {202EB507-C7AB-49F2-B048-73267E7BBEC2}.Release|Any CPU.Build.0 = Release|Any CPU - {8F5A7D0B-09EE-4952-885D-B1738DE243BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8F5A7D0B-09EE-4952-885D-B1738DE243BB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8F5A7D0B-09EE-4952-885D-B1738DE243BB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8F5A7D0B-09EE-4952-885D-B1738DE243BB}.Release|Any CPU.Build.0 = Release|Any CPU {B35C3E62-552B-470D-9471-5B823A1B7831}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B35C3E62-552B-470D-9471-5B823A1B7831}.Debug|Any CPU.Build.0 = Debug|Any CPU {B35C3E62-552B-470D-9471-5B823A1B7831}.Release|Any CPU.ActiveCfg = Release|Any CPU {B35C3E62-552B-470D-9471-5B823A1B7831}.Release|Any CPU.Build.0 = Release|Any CPU - {1F58B6AE-89D4-414A-9397-F775130B3FBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1F58B6AE-89D4-414A-9397-F775130B3FBF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1F58B6AE-89D4-414A-9397-F775130B3FBF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1F58B6AE-89D4-414A-9397-F775130B3FBF}.Release|Any CPU.Build.0 = Release|Any CPU - {9617FEB8-2160-4D3A-B157-2D97EF3B2F56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9617FEB8-2160-4D3A-B157-2D97EF3B2F56}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9617FEB8-2160-4D3A-B157-2D97EF3B2F56}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9617FEB8-2160-4D3A-B157-2D97EF3B2F56}.Release|Any CPU.Build.0 = Release|Any CPU {D934E764-94AB-412B-93B5-3C2E3A40E898}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D934E764-94AB-412B-93B5-3C2E3A40E898}.Debug|Any CPU.Build.0 = Debug|Any CPU {D934E764-94AB-412B-93B5-3C2E3A40E898}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -231,44 +170,80 @@ Global {D28B965D-73AB-4905-AB37-63AF2CB99567}.Debug|Any CPU.Build.0 = Debug|Any CPU {D28B965D-73AB-4905-AB37-63AF2CB99567}.Release|Any CPU.ActiveCfg = Release|Any CPU {D28B965D-73AB-4905-AB37-63AF2CB99567}.Release|Any CPU.Build.0 = Release|Any CPU + {4747A117-C17E-4642-9934-2FF3D7CCBEBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4747A117-C17E-4642-9934-2FF3D7CCBEBA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4747A117-C17E-4642-9934-2FF3D7CCBEBA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4747A117-C17E-4642-9934-2FF3D7CCBEBA}.Release|Any CPU.Build.0 = Release|Any CPU + {E4D497A0-3925-43F7-9B5F-A67FA65BABC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4D497A0-3925-43F7-9B5F-A67FA65BABC4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4D497A0-3925-43F7-9B5F-A67FA65BABC4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4D497A0-3925-43F7-9B5F-A67FA65BABC4}.Release|Any CPU.Build.0 = Release|Any CPU + {8BD68C7A-F83A-4D40-A59F-13C1650A05B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BD68C7A-F83A-4D40-A59F-13C1650A05B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BD68C7A-F83A-4D40-A59F-13C1650A05B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BD68C7A-F83A-4D40-A59F-13C1650A05B6}.Release|Any CPU.Build.0 = Release|Any CPU + {74E21B07-87D1-4F7D-8804-EF1397FA48C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74E21B07-87D1-4F7D-8804-EF1397FA48C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74E21B07-87D1-4F7D-8804-EF1397FA48C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74E21B07-87D1-4F7D-8804-EF1397FA48C9}.Release|Any CPU.Build.0 = Release|Any CPU + {5099CEDA-9354-44A6-ABE6-DED73307FF02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5099CEDA-9354-44A6-ABE6-DED73307FF02}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5099CEDA-9354-44A6-ABE6-DED73307FF02}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5099CEDA-9354-44A6-ABE6-DED73307FF02}.Release|Any CPU.Build.0 = Release|Any CPU + {C68CF325-C730-4111-9F11-E4CF453B620A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C68CF325-C730-4111-9F11-E4CF453B620A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C68CF325-C730-4111-9F11-E4CF453B620A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C68CF325-C730-4111-9F11-E4CF453B620A}.Release|Any CPU.Build.0 = Release|Any CPU + {435F6D3D-DFAA-4A87-A9B5-2B7B0AF2DD0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {435F6D3D-DFAA-4A87-A9B5-2B7B0AF2DD0F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {435F6D3D-DFAA-4A87-A9B5-2B7B0AF2DD0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {435F6D3D-DFAA-4A87-A9B5-2B7B0AF2DD0F}.Release|Any CPU.Build.0 = Release|Any CPU + {187FDC48-1AFA-4FA4-AF62-A3FDECFE56DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {187FDC48-1AFA-4FA4-AF62-A3FDECFE56DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {187FDC48-1AFA-4FA4-AF62-A3FDECFE56DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {187FDC48-1AFA-4FA4-AF62-A3FDECFE56DA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {77C27B5A-B471-4439-A0F9-C334C0367C06} = {1081A860-2D1C-4AF4-A901-10B00BF278CF} - {CF096018-03FB-42BD-858A-B8272CB6302B} = {3B665A6F-1CE9-48B9-9736-A5C05C49335D} - {4290D0E0-FCE3-48AE-AD69-A96439870A12} = {3B665A6F-1CE9-48B9-9736-A5C05C49335D} - {8B3ECC01-C4F4-4CF9-AEE2-34252C84894F} = {1081A860-2D1C-4AF4-A901-10B00BF278CF} - {38D1A086-2A5D-4654-B465-04C662DDEA1A} = {1081A860-2D1C-4AF4-A901-10B00BF278CF} - {1A039171-DE86-4DCE-8D47-8626666004E3} = {3B665A6F-1CE9-48B9-9736-A5C05C49335D} - {7F3959D0-0998-41D0-B362-2A1366CA1270} = {BF9972ED-AFAC-4E39-9AAD-89F46338CED1} - {9354F5AC-21E6-4FE9-AF2E-4D80972ACE29} = {BF9972ED-AFAC-4E39-9AAD-89F46338CED1} - {71DD068D-41A9-4084-8808-5190BB4D1AC0} = {BF9972ED-AFAC-4E39-9AAD-89F46338CED1} - {BEA34185-AC53-406B-8A57-F3E6C8F12636} = {4224614A-B5A0-4633-BF49-A485C0E61595} - {CE0A3BC7-0EAC-4004-BDD3-4008C685C8EE} = {4224614A-B5A0-4633-BF49-A485C0E61595} + {38D1A086-2A5D-4654-B465-04C662DDEA1A} = {318B773B-F7D6-4003-A92A-96E5E97A5C32} + {1A039171-DE86-4DCE-8D47-8626666004E3} = {1F650DE4-C5CB-4208-AE86-12C18C0C00F7} + {19811879-AEFC-4F75-BF52-10CB8471F5CB} = {6BC3D884-18CD-4328-9EEF-39B7FFBB7F3E} + {71DD068D-41A9-4084-8808-5190BB4D1AC0} = {73BEA53A-145A-4C04-A615-839C73C97755} {7432DA0D-73B2-45F8-B6D8-2FF7423DB6BA} = {0F6FD2BA-85B0-4ABA-A3F9-A4F6BE4808B1} {6A4CA971-D78F-48A7-84A0-81B7ADC92AFE} = {0F6FD2BA-85B0-4ABA-A3F9-A4F6BE4808B1} - {43E1CB25-89F2-4257-B95D-D2CFDA7CA472} = {4224614A-B5A0-4633-BF49-A485C0E61595} - {F270AF92-0177-4596-9ECD-BE34A16F916F} = {4224614A-B5A0-4633-BF49-A485C0E61595} - {568269F6-0D9E-4ADA-AE8C-FDF70814AE00} = {4224614A-B5A0-4633-BF49-A485C0E61595} - {B905A759-712E-4C0B-B027-8DB4D0A7961B} = {4224614A-B5A0-4633-BF49-A485C0E61595} - {E2D9578A-4597-40A3-8E4F-DCAB03733114} = {4224614A-B5A0-4633-BF49-A485C0E61595} - {FF5ECF20-7A50-4950-B911-0D0C9386A008} = {BF9972ED-AFAC-4E39-9AAD-89F46338CED1} - {6D23DE23-3FBA-4FEF-ABDD-C022F149C355} = {BF9972ED-AFAC-4E39-9AAD-89F46338CED1} - {A0EF9592-1653-4A8F-BFDB-955BA806B590} = {BF9972ED-AFAC-4E39-9AAD-89F46338CED1} - {82A13E90-F0A6-4A37-96A9-902D87FDF31A} = {06D69571-67BA-449E-997A-B098419523EC} - {55F6BA03-6CBC-4A34-8F33-E83062798441} = {BF9972ED-AFAC-4E39-9AAD-89F46338CED1} - {0517B781-DC60-44F8-9B98-4C9A4654BDD4} = {1081A860-2D1C-4AF4-A901-10B00BF278CF} - {47C8A4FD-6756-4474-870B-BD6921163C0B} = {3B665A6F-1CE9-48B9-9736-A5C05C49335D} - {A0859BE8-14DC-479C-A20F-561092BF59F6} = {925F3C21-3653-4DA2-99F9-0982736674BA} - {202EB507-C7AB-49F2-B048-73267E7BBEC2} = {925F3C21-3653-4DA2-99F9-0982736674BA} - {8F5A7D0B-09EE-4952-885D-B1738DE243BB} = {6878921D-9AA1-4159-8E29-B440B2E44C9B} - {B35C3E62-552B-470D-9471-5B823A1B7831} = {6878921D-9AA1-4159-8E29-B440B2E44C9B} - {1F58B6AE-89D4-414A-9397-F775130B3FBF} = {925F3C21-3653-4DA2-99F9-0982736674BA} - {9617FEB8-2160-4D3A-B157-2D97EF3B2F56} = {BF9972ED-AFAC-4E39-9AAD-89F46338CED1} - {D934E764-94AB-412B-93B5-3C2E3A40E898} = {06D69571-67BA-449E-997A-B098419523EC} - {D28B965D-73AB-4905-AB37-63AF2CB99567} = {06D69571-67BA-449E-997A-B098419523EC} + {43E1CB25-89F2-4257-B95D-D2CFDA7CA472} = {73BEA53A-145A-4C04-A615-839C73C97755} + {F270AF92-0177-4596-9ECD-BE34A16F916F} = {48B33FC6-19DD-4237-8524-09D1ED77F692} + {E2D9578A-4597-40A3-8E4F-DCAB03733114} = {73BEA53A-145A-4C04-A615-839C73C97755} + {A0EF9592-1653-4A8F-BFDB-955BA806B590} = {73BEA53A-145A-4C04-A615-839C73C97755} + {82A13E90-F0A6-4A37-96A9-902D87FDF31A} = {89C541A7-00DE-42C9-A51D-D3E23A22FFB5} + {55F6BA03-6CBC-4A34-8F33-E83062798441} = {48B33FC6-19DD-4237-8524-09D1ED77F692} + {0517B781-DC60-44F8-9B98-4C9A4654BDD4} = {5B7A4E65-54D1-4503-A426-DDA771085FC1} + {47C8A4FD-6756-4474-870B-BD6921163C0B} = {5B7A4E65-54D1-4503-A426-DDA771085FC1} + {202EB507-C7AB-49F2-B048-73267E7BBEC2} = {5B7A4E65-54D1-4503-A426-DDA771085FC1} + {B35C3E62-552B-470D-9471-5B823A1B7831} = {5B7A4E65-54D1-4503-A426-DDA771085FC1} + {D934E764-94AB-412B-93B5-3C2E3A40E898} = {F88DC1A0-9524-4A84-AE6A-34DC74B610FC} + {D28B965D-73AB-4905-AB37-63AF2CB99567} = {F88DC1A0-9524-4A84-AE6A-34DC74B610FC} + {4747A117-C17E-4642-9934-2FF3D7CCBEBA} = {AC2D3C39-76E0-463D-B235-1B7FCFE98B0B} + {E4D497A0-3925-43F7-9B5F-A67FA65BABC4} = {AC2D3C39-76E0-463D-B235-1B7FCFE98B0B} + {8BD68C7A-F83A-4D40-A59F-13C1650A05B6} = {AC2D3C39-76E0-463D-B235-1B7FCFE98B0B} + {5B7A4E65-54D1-4503-A426-DDA771085FC1} = {911DCB3B-CEFA-4894-B075-432342C05405} + {911DCB3B-CEFA-4894-B075-432342C05405} = {955F3700-AC87-456C-B645-3F9451EAFA57} + {318B773B-F7D6-4003-A92A-96E5E97A5C32} = {911DCB3B-CEFA-4894-B075-432342C05405} + {1F650DE4-C5CB-4208-AE86-12C18C0C00F7} = {911DCB3B-CEFA-4894-B075-432342C05405} + {37254007-E34E-4AC6-B17D-F0FC060F196D} = {955F3700-AC87-456C-B645-3F9451EAFA57} + {73BEA53A-145A-4C04-A615-839C73C97755} = {37254007-E34E-4AC6-B17D-F0FC060F196D} + {48B33FC6-19DD-4237-8524-09D1ED77F692} = {37254007-E34E-4AC6-B17D-F0FC060F196D} + {F88DC1A0-9524-4A84-AE6A-34DC74B610FC} = {955F3700-AC87-456C-B645-3F9451EAFA57} + {89C541A7-00DE-42C9-A51D-D3E23A22FFB5} = {06D69571-67BA-449E-997A-B098419523EC} + {6BC3D884-18CD-4328-9EEF-39B7FFBB7F3E} = {06D69571-67BA-449E-997A-B098419523EC} + {74E21B07-87D1-4F7D-8804-EF1397FA48C9} = {C564FA3C-70E4-4C36-93FC-04088D491984} + {5099CEDA-9354-44A6-ABE6-DED73307FF02} = {C564FA3C-70E4-4C36-93FC-04088D491984} + {F1743BB6-65C2-44D3-940C-F0BF17C0557F} = {955F3700-AC87-456C-B645-3F9451EAFA57} + {C68CF325-C730-4111-9F11-E4CF453B620A} = {F1743BB6-65C2-44D3-940C-F0BF17C0557F} + {435F6D3D-DFAA-4A87-A9B5-2B7B0AF2DD0F} = {89C541A7-00DE-42C9-A51D-D3E23A22FFB5} + {187FDC48-1AFA-4FA4-AF62-A3FDECFE56DA} = {89C541A7-00DE-42C9-A51D-D3E23A22FFB5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AB937E37-4C08-4EB0-86F7-87F74A7F6C44} diff --git a/src/PensionToolsComponentLibrary/ExampleJsInterop.cs b/src/PensionToolsComponentLibrary/ExampleJsInterop.cs new file mode 100644 index 00000000..0600c754 --- /dev/null +++ b/src/PensionToolsComponentLibrary/ExampleJsInterop.cs @@ -0,0 +1,37 @@ +using Microsoft.JSInterop; + +namespace PensionToolsComponentLibrary +{ + // This class provides an example of how JavaScript functionality can be wrapped + // in a .NET class for easy consumption. The associated JavaScript module is + // loaded on demand when first needed. + // + // This class can be registered as scoped DI service and then injected into Blazor + // components for use. + + public class ExampleJsInterop : IAsyncDisposable + { + private readonly Lazy> moduleTask; + + public ExampleJsInterop(IJSRuntime jsRuntime) + { + moduleTask = new(() => jsRuntime.InvokeAsync( + "import", "./_content/PensionToolsComponentLibrary/exampleJsInterop.js").AsTask()); + } + + public async ValueTask Prompt(string message) + { + var module = await moduleTask.Value; + return await module.InvokeAsync("showPrompt", message); + } + + public async ValueTask DisposeAsync() + { + if (moduleTask.IsValueCreated) + { + var module = await moduleTask.Value; + await module.DisposeAsync(); + } + } + } +} diff --git a/src/PensionToolsComponentLibrary/GenderSelector.razor b/src/PensionToolsComponentLibrary/GenderSelector.razor new file mode 100644 index 00000000..9773415e --- /dev/null +++ b/src/PensionToolsComponentLibrary/GenderSelector.razor @@ -0,0 +1,62 @@ +@using Domain.Enums +@using PensionToolsComponentLibrary.Resources + +@inject IStringLocalizer Localizer + +@if (IsReadOnly) +{ + @if (DisplayInGrid) + { + @GetViewModel().Name + } + else + { + + } +} +else +{ + + + @foreach (var g in genderModels) + { + @g.Name + } + +} + +@code { + internal class GenderViewModel + { + public Gender Value { get; set; } + + public string Name { get; set;} + } + + [Parameter] public string Label { get; set; } + [Parameter] public bool IsReadOnly { get; set; } + [Parameter] public bool DisplayInGrid { get; set; } + [Parameter] public Gender SelectedGender { get; set; } = Gender.Undefined; + + [Parameter] + public EventCallback OnSelected { get; set; } + + private readonly List genderModels = new(); + + protected override void OnInitialized() + { + genderModels.Add(new GenderViewModel { Name = Localizer["gender.male"], Value = Gender.Male }); + genderModels.Add(new GenderViewModel { Name = Localizer["gender.female"], Value = Gender.Female }); + } + + private async Task HandleChange(Gender value) + { + SelectedGender = value; + await OnSelected.InvokeAsync(value); + } + + private GenderViewModel GetViewModel() + { + return genderModels.Single(item => item.Value == SelectedGender); + } +} \ No newline at end of file diff --git a/src/PensionToolsComponentLibrary/PensionToolsComponentLibrary.csproj b/src/PensionToolsComponentLibrary/PensionToolsComponentLibrary.csproj new file mode 100644 index 00000000..b09fc651 --- /dev/null +++ b/src/PensionToolsComponentLibrary/PensionToolsComponentLibrary.csproj @@ -0,0 +1,39 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + True + True + PensionComponents.resx + + + + + + ResXFileCodeGenerator + PensionComponents.Designer.cs + + + + diff --git a/src/PensionToolsComponentLibrary/Resources/PensionComponents.Designer.cs b/src/PensionToolsComponentLibrary/Resources/PensionComponents.Designer.cs new file mode 100644 index 00000000..1fcdcb53 --- /dev/null +++ b/src/PensionToolsComponentLibrary/Resources/PensionComponents.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PensionToolsComponentLibrary.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class PensionComponents { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal PensionComponents() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PensionToolsComponentLibrary.Resources.PensionComponents", typeof(PensionComponents).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Frau. + /// + internal static string gender_female { + get { + return ResourceManager.GetString("gender.female", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mann. + /// + internal static string gender_male { + get { + return ResourceManager.GetString("gender.male", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unbekannt. + /// + internal static string gender_undefined { + get { + return ResourceManager.GetString("gender.undefined", resourceCulture); + } + } + } +} diff --git a/src/PensionToolsComponentLibrary/Resources/PensionComponents.resx b/src/PensionToolsComponentLibrary/Resources/PensionComponents.resx new file mode 100644 index 00000000..98831245 --- /dev/null +++ b/src/PensionToolsComponentLibrary/Resources/PensionComponents.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Frau + + + Mann + + + Unbekannt + + \ No newline at end of file diff --git a/src/PensionToolsComponentLibrary/_Imports.razor b/src/PensionToolsComponentLibrary/_Imports.razor new file mode 100644 index 00000000..81b4e9e9 --- /dev/null +++ b/src/PensionToolsComponentLibrary/_Imports.razor @@ -0,0 +1,3 @@ +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.Extensions.Localization +@using MudBlazor diff --git a/src/PensionToolsComponentLibrary/wwwroot/background.png b/src/PensionToolsComponentLibrary/wwwroot/background.png new file mode 100644 index 00000000..e15a3bde Binary files /dev/null and b/src/PensionToolsComponentLibrary/wwwroot/background.png differ diff --git a/src/PensionToolsComponentLibrary/wwwroot/exampleJsInterop.js b/src/PensionToolsComponentLibrary/wwwroot/exampleJsInterop.js new file mode 100644 index 00000000..ea8d76ad --- /dev/null +++ b/src/PensionToolsComponentLibrary/wwwroot/exampleJsInterop.js @@ -0,0 +1,6 @@ +// This is a JavaScript module that is loaded on demand. It can export any number of +// functions, and may import other JavaScript modules if required. + +export function showPrompt(message) { + return prompt(message, 'Type anything here'); +} diff --git a/src/PensionVersusCapitalCalculator.Abstractions/IPensionVersusCapitalCalculator.cs b/src/PensionVersusCapitalCalculator.Abstractions/IPensionVersusCapitalCalculator.cs deleted file mode 100644 index 9e3676e8..00000000 --- a/src/PensionVersusCapitalCalculator.Abstractions/IPensionVersusCapitalCalculator.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - -namespace PensionVersusCapitalCalculator.Abstractions -{ - public interface IPensionVersusCapitalCalculator - { - Task> CalculateAsync( - int calculationYear, - int municipalityId, - Canton canton, - decimal retirementPension, - decimal retirementCapital, - TaxPerson taxPerson); - } -} diff --git a/src/PensionVersusCapitalCalculator.Abstractions/PensionVersusCapitalCalculator.Abstractions.csproj b/src/PensionVersusCapitalCalculator.Abstractions/PensionVersusCapitalCalculator.Abstractions.csproj deleted file mode 100644 index 3123bea1..00000000 --- a/src/PensionVersusCapitalCalculator.Abstractions/PensionVersusCapitalCalculator.Abstractions.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - net7.0 - - - - - - - - - - - - - - - diff --git a/src/PensionVersusCapitalCalculator.Tests/PensionVersusCapitalCalculator.Tests.csproj b/src/PensionVersusCapitalCalculator.Tests/PensionVersusCapitalCalculator.Tests.csproj index a46a8329..a7f4a460 100644 --- a/src/PensionVersusCapitalCalculator.Tests/PensionVersusCapitalCalculator.Tests.csproj +++ b/src/PensionVersusCapitalCalculator.Tests/PensionVersusCapitalCalculator.Tests.csproj @@ -1,30 +1,29 @@  - net7.0 + net8.0 false - - - - - - + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - + diff --git a/src/PensionVersusCapitalCalculator.Tests/PensionVersusCapitalCalculatorTests.cs b/src/PensionVersusCapitalCalculator.Tests/PensionVersusCapitalCalculatorTests.cs index db38e4ca..c1b5b477 100644 --- a/src/PensionVersusCapitalCalculator.Tests/PensionVersusCapitalCalculatorTests.cs +++ b/src/PensionVersusCapitalCalculator.Tests/PensionVersusCapitalCalculatorTests.cs @@ -1,50 +1,51 @@ -using System.Threading.Tasks; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionVersusCapitalCalculator.Abstractions; +using System; +using System.Threading.Tasks; +using Application.Features.PensionVersusCapital; +using Domain.Enums; +using Domain.Models.Tax; using Xunit; -namespace PensionVersusCapitalCalculator.Tests +namespace PensionVersusCapitalCalculator.Tests; + +[Trait("Tools", "Pension vs Capital")] +public class PensionVersusCapitalCalculatorTests : IClassFixture> { - [Trait("Tools", "Pension vs Capital")] - public class PensionVersusCapitalCalculatorTests : IClassFixture> + private readonly ToolsFixture fixture; + + public PensionVersusCapitalCalculatorTests(ToolsFixture fixture) { - private readonly ToolsFixture fixture; + this.fixture = fixture; + } - public PensionVersusCapitalCalculatorTests(ToolsFixture fixture) - { - this.fixture = fixture; - } - - [Theory] - [InlineData(50_000, 28680, 1_000_000, 261, 20.305)] // Zürich - [InlineData(50_000, 28680, 1_000_000, 141, 20.214)] // Zürich - [InlineData(50_000, 28680, 1_000_000, 3426, 20.075)] // SG - [InlineData(50_000, 28680, 1_000_000, 2526, 20.19)] // SO - public async Task Calculate_BreakEven_Factor(decimal retirementPension, decimal otherIncome, decimal retirementCapital, int municipalityId, decimal expectedBreakEvenFactor) + [Theory] + [InlineData(50_000, 28680, 1_000_000, 261, 20.596)] // Zürich + [InlineData(50_000, 28680, 1_000_000, 141, 20.51)] // Zürich + [InlineData(50_000, 28680, 1_000_000, 3426, 23.65)] // SG + [InlineData(50_000, 28680, 1_000_000, 2526, 24.245)] // SO + public async Task Calculate_BreakEven_Factor(decimal retirementPension, decimal otherIncome, decimal retirementCapital, int municipalityId, decimal expectedBreakEvenFactor) + { + const decimal epsilon = 0.001M; + const int calculationYear = 2019; + Canton canton = Canton.ZH; + CivilStatus civilStatus = CivilStatus.Single; + string name = "PensVsCap"; + ReligiousGroupType religiousGroupType = ReligiousGroupType.Protestant; + + TaxPerson taxPerson = new() { - const decimal epsilon = 0.001M; - const int calculationYear = 2019; - Canton canton = Canton.ZH; - CivilStatus civilStatus = CivilStatus.Single; - string name = "PensVsCap"; - ReligiousGroupType religiousGroupType = ReligiousGroupType.Protestant; - - TaxPerson taxPerson = new() - { - Name = name, - CivilStatus = civilStatus, - ReligiousGroupType = religiousGroupType, - TaxableFederalIncome = otherIncome, - TaxableIncome = otherIncome, - TaxableWealth = decimal.Zero - }; - - decimal result = - (await fixture.Service.CalculateAsync(calculationYear, municipalityId, canton, retirementPension, retirementCapital, taxPerson)) - .IfNone(decimal.Zero); - - Assert.InRange(result, expectedBreakEvenFactor - epsilon, expectedBreakEvenFactor + epsilon); - } + Name = name, + CivilStatus = civilStatus, + ReligiousGroupType = religiousGroupType, + TaxableFederalIncome = otherIncome, + TaxableIncome = otherIncome, + TaxableWealth = decimal.Zero + }; + + var response = await fixture.Service.CalculateAsync( + calculationYear, municipalityId, canton, retirementPension, retirementCapital, taxPerson); + + var result = response.IfLeft(error => throw new ApplicationException(error)); + + Assert.InRange(result, expectedBreakEvenFactor - epsilon, expectedBreakEvenFactor + epsilon); } } diff --git a/src/PensionVersusCapitalCalculator.Tests/ToolsFixture.cs b/src/PensionVersusCapitalCalculator.Tests/ToolsFixture.cs index d30f9232..3a92886c 100644 --- a/src/PensionVersusCapitalCalculator.Tests/ToolsFixture.cs +++ b/src/PensionVersusCapitalCalculator.Tests/ToolsFixture.cs @@ -3,49 +3,50 @@ using System.IO; using System.Linq; using System.Reflection; +using Application.Features.FullTaxCalculation; +using Application.Features.PensionVersusCapital; +using Infrastructure.Configuration; +using Infrastructure.Tax.Data; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using PensionCoach.Tools.TaxCalculator; -using Tax.Data; -namespace PensionVersusCapitalCalculator.Tests +namespace PensionVersusCapitalCalculator.Tests; + +public class ToolsFixture + where T : class { - public class ToolsFixture - where T : class - { - public ServiceProvider Provider { get; } + public ServiceProvider Provider { get; } - public T Calculator { get; } + public T Calculator { get; } - public T Service { get; } + public T Service { get; } - public ToolsFixture() - { - var projectPath = Assembly.GetExecutingAssembly() - .Location.Split("src", StringSplitOptions.RemoveEmptyEntries) - .First(); + public ToolsFixture() + { + var projectPath = Assembly.GetExecutingAssembly() + .Location.Split("src", StringSplitOptions.RemoveEmptyEntries) + .First(); - var dbFile = Path.Combine(projectPath, @"src\Tax.Data\files\TaxDb.db"); + var dbFile = Path.Combine(projectPath, @"src\Infrastructure\files\TaxDb.db"); - var configurationDict = new Dictionary - { - {"ConnectionStrings:TaxDb", dbFile} - }; + var configurationDict = new Dictionary + { + {"ConnectionStrings:TaxDb", dbFile} + }; - IConfiguration configuration = new ConfigurationBuilder() - .AddInMemoryCollection(configurationDict) - .Build(); + IConfiguration configuration = new ConfigurationBuilder() + .AddInMemoryCollection(configurationDict) + .Build(); - ServiceCollection coll = new ServiceCollection(); - coll.AddToolsCalculators(); - coll.AddTaxCalculators(configuration); - coll.AddTaxData(configuration); + ServiceCollection coll = new ServiceCollection(); + coll.AddToolsCalculators(); + coll.AddTaxCalculators(configuration.GetApplicationMode()); + coll.AddTaxData(configuration); - Provider = coll.BuildServiceProvider(); + Provider = coll.BuildServiceProvider(); - Calculator = Provider.GetRequiredService(); + Calculator = Provider.GetRequiredService(); - Service = Provider.GetRequiredService(); - } + Service = Provider.GetRequiredService(); } } diff --git a/src/PensionVersusCapitalCalculator/PensionVersusCapitalCalculator.cs b/src/PensionVersusCapitalCalculator/PensionVersusCapitalCalculator.cs deleted file mode 100644 index 2b92dad9..00000000 --- a/src/PensionVersusCapitalCalculator/PensionVersusCapitalCalculator.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionVersusCapitalCalculator.Abstractions; - -namespace PensionVersusCapitalCalculator -{ - public class PensionVersusCapitalCalculator : IPensionVersusCapitalCalculator - { - private readonly IIncomeTaxCalculator incomeTaxCalculator; - private readonly Func capitalBenefitCalculatorFunc; - - public PensionVersusCapitalCalculator( - IIncomeTaxCalculator incomeTaxCalculator, - Func capitalBenefitCalculatorFunc) - { - this.incomeTaxCalculator = incomeTaxCalculator; - this.capitalBenefitCalculatorFunc = capitalBenefitCalculatorFunc; - } - - public async Task> CalculateAsync( - int calculationYear, - int municipalityId, - Canton canton, - decimal retirementPension, - decimal retirementCapital, - TaxPerson taxPerson) - { - ICapitalBenefitTaxCalculator capitalBenefitCalculator = capitalBenefitCalculatorFunc(canton); - - CapitalBenefitTaxPerson capitalBenefitTaxPerson = new() - { - Name = "Benefit Person", - CivilStatus = taxPerson.CivilStatus, - TaxableCapitalBenefits = retirementCapital, - ReligiousGroupType = taxPerson.ReligiousGroupType, - NumberOfChildren = taxPerson.NumberOfChildren, - PartnerReligiousGroupType = taxPerson.PartnerReligiousGroupType - }; - - Either capitalBenefitTaxCalculationResult = - await capitalBenefitCalculator.CalculateAsync(calculationYear, municipalityId, canton, capitalBenefitTaxPerson); - - Either justOtherIncomeTaxCalculationResult = - await incomeTaxCalculator.CalculateAsync(calculationYear, municipalityId, canton, taxPerson); - - decimal otherIncome = taxPerson.TaxableIncome; - - taxPerson.TaxableIncome += retirementPension; - taxPerson.TaxableFederalIncome += retirementPension; - - Either withPensionIncomeTaxCalculationResult = - await incomeTaxCalculator.CalculateAsync(calculationYear, municipalityId, canton, taxPerson); - - var r = from benefitsTax in capitalBenefitTaxCalculationResult - from otherIncomeTax in justOtherIncomeTaxCalculationResult - from withPensionIncomeTax in withPensionIncomeTaxCalculationResult - select CalculateBreakEvenFactor(benefitsTax, otherIncomeTax, withPensionIncomeTax); - - return r.IfLeft(()=>Option.None); - - Option CalculateBreakEvenFactor( - CapitalBenefitTaxResult benefitTaxResult, - SingleTaxResult otherIncomeTaxResult, - SingleTaxResult withPensionIncomeTaxResult) - { - decimal capitalBenefitNet = capitalBenefitTaxPerson.TaxableCapitalBenefits - benefitTaxResult.TotalTaxAmount; - decimal incomeNet = taxPerson.TaxableIncome - otherIncome; - decimal totalTaxNet = withPensionIncomeTaxResult.TotalTaxAmount - otherIncomeTaxResult.TotalTaxAmount; - - if (incomeNet == decimal.Zero) - { - return Option.None; - } - - - - return capitalBenefitNet / (incomeNet - totalTaxNet); - } - } - } -} diff --git a/src/PensionVersusCapitalCalculator/PensionVersusCapitalCalculator.csproj b/src/PensionVersusCapitalCalculator/PensionVersusCapitalCalculator.csproj deleted file mode 100644 index 9c78709f..00000000 --- a/src/PensionVersusCapitalCalculator/PensionVersusCapitalCalculator.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - net7.0 - - - - - - - - - - - - diff --git a/src/PensionVersusCapitalCalculator/ToolsCollectionExtensions.cs b/src/PensionVersusCapitalCalculator/ToolsCollectionExtensions.cs deleted file mode 100644 index 832f37f0..00000000 --- a/src/PensionVersusCapitalCalculator/ToolsCollectionExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using PensionVersusCapitalCalculator.Abstractions; - -namespace PensionVersusCapitalCalculator -{ - public static class ToolsCollectionExtensions - { - public static void AddToolsCalculators(this IServiceCollection serviceCollection) - { - serviceCollection.AddSingleton(); - } - } -} diff --git a/src/PostOpenApi.Client/IPostOpenApiClient.cs b/src/PostOpenApi.Client/IPostOpenApiClient.cs deleted file mode 100644 index 75ed943b..00000000 --- a/src/PostOpenApi.Client/IPostOpenApiClient.cs +++ /dev/null @@ -1,8 +0,0 @@ -using PensionCoach.Tools.PostOpenApi.Models; - -namespace PensionCoach.Tools.PostOpenApi; - -public interface IPostOpenApiClient -{ - Task GetZipCodesAsync(int limit, int offset); -} diff --git a/src/PostOpenApi.Client/Models/OpenApiZipDetail.cs b/src/PostOpenApi.Client/Models/OpenApiZipDetail.cs deleted file mode 100644 index 556d2383..00000000 --- a/src/PostOpenApi.Client/Models/OpenApiZipDetail.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace PensionCoach.Tools.PostOpenApi.Models; - -public class OpenApiZipDetail -{ - [JsonPropertyName("timestamp")] - public DateTime TimeStamp { get; set; } - - [JsonPropertyName("fields")] - public OpenApiZipFields Fields { get; set; } -} diff --git a/src/PostOpenApi.Client/Models/OpenApiZipFields.cs b/src/PostOpenApi.Client/Models/OpenApiZipFields.cs deleted file mode 100644 index 8445dbc4..00000000 --- a/src/PostOpenApi.Client/Models/OpenApiZipFields.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Text.Json.Serialization; - -namespace PensionCoach.Tools.PostOpenApi.Models; - -public class OpenApiZipFields -{ - [JsonPropertyName("bfsnr")] - public int BfsCode { get; set; } - - [JsonPropertyName("postleitzahl")] - public string ZipCode { get; set; } - - [JsonPropertyName("plz_zz")] - public string ZipCodeAddOn { get; set; } - - [JsonPropertyName("gilt_ab_dat")] - public string DateOfValidity { get; set; } - - [JsonPropertyName("ortbez27")] - public string MunicipalityName { get; set; } - - [JsonPropertyName("kanton")] - public string Canton { get; set; } - - [JsonPropertyName("sprachcode")] - public int LanguageCode { get; set; } -} diff --git a/src/PostOpenApi.Client/Models/OpenApiZipInfo.cs b/src/PostOpenApi.Client/Models/OpenApiZipInfo.cs deleted file mode 100644 index ca4d2cde..00000000 --- a/src/PostOpenApi.Client/Models/OpenApiZipInfo.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace PensionCoach.Tools.PostOpenApi.Models; - -public class OpenApiZipInfo -{ - [JsonPropertyName("total_count")] - public int TotalCount { get; set; } - - [JsonPropertyName("records")] - public IEnumerable Records { get; set; } -} diff --git a/src/PostOpenApi.Client/Models/OpenApiZipRecord.cs b/src/PostOpenApi.Client/Models/OpenApiZipRecord.cs deleted file mode 100644 index c00f0756..00000000 --- a/src/PostOpenApi.Client/Models/OpenApiZipRecord.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace PensionCoach.Tools.PostOpenApi.Models; - -public class OpenApiZipRecord -{ - [JsonPropertyName("record")] - public OpenApiZipDetail Record { get; set; } -} diff --git a/src/PostOpenApi.Client/PostOpenApi.Client.csproj b/src/PostOpenApi.Client/PostOpenApi.Client.csproj deleted file mode 100644 index 974a6efd..00000000 --- a/src/PostOpenApi.Client/PostOpenApi.Client.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - net7.0 - enable - PensionCoach.Tools.PostOpenApi - PensionCoach.Tools.PostOpenApi - - - - - - - - - diff --git a/src/PostOpenApi.Client/PostOpenApiClient.cs b/src/PostOpenApi.Client/PostOpenApiClient.cs deleted file mode 100644 index f748e6ee..00000000 --- a/src/PostOpenApi.Client/PostOpenApiClient.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Net.Http.Json; -using PensionCoach.Tools.PostOpenApi.Models; - -namespace PensionCoach.Tools.PostOpenApi; - -public class PostOpenApiClient : IPostOpenApiClient -{ - internal static string ClientName = "PostOpenApiClient"; - - private readonly IHttpClientFactory httpClientFactory; - - public PostOpenApiClient(IHttpClientFactory httpClientFactory) - { - this.httpClientFactory = httpClientFactory; - } - - public Task GetZipCodesAsync(int limit, int offset) - { - string[] fieldNames = { "postleitzahl","plz_zz", "gilt_ab_dat", "bfsnr", "kanton", "ortbez27" } ; - - HttpClient client = httpClientFactory.CreateClient(ClientName); - - string partFields = string.Join("%2C%20", fieldNames); - - return client.GetFromJsonAsync( - $"plz_verzeichnis_v2/records?select={partFields}&limit={limit}&offset={offset}&timezone=UTC"); - } -} diff --git a/src/PostOpenApi.Client/PostOpenApiClientMock.cs b/src/PostOpenApi.Client/PostOpenApiClientMock.cs deleted file mode 100644 index cb50eaa6..00000000 --- a/src/PostOpenApi.Client/PostOpenApiClientMock.cs +++ /dev/null @@ -1,197 +0,0 @@ -using System.Globalization; -using LanguageExt; -using PensionCoach.Tools.PostOpenApi.Models; - -namespace PensionCoach.Tools.PostOpenApi; - -/// -/// Not yet used as Post Open API is not part of any user calls. -/// -public class PostOpenApiClientMock : IPostOpenApiClient -{ - public Task GetZipCodesAsync(int limit, int offset) - { - return GetMockData().AsTask(); - } - - private OpenApiZipInfo GetMockData() - { - return new OpenApiZipInfo - { - TotalCount = 10, - Records = new List - { - new() - { - Record = new OpenApiZipDetail - { - Fields = new OpenApiZipFields - { - BfsCode = 261, - Canton = "ZH", - LanguageCode = 0, - MunicipalityName = "Zürich", - ZipCode = "8047", - ZipCodeAddOn = "00", - DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) - }, - TimeStamp = new DateTime(2022, 3, 17) - } - }, - new() - { - Record = new OpenApiZipDetail - { - Fields = new OpenApiZipFields - { - BfsCode = 951, - Canton = "BE", - LanguageCode = 0, - MunicipalityName = "Affoltern im Emmental", - ZipCode = "3416", - ZipCodeAddOn = "00", - DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) - }, - TimeStamp = new DateTime(2022, 3, 17) - } - }, - new() - { - Record = new OpenApiZipDetail - { - Fields = new OpenApiZipFields - { - BfsCode = 2196, - Canton = "FR", - LanguageCode = 0, - MunicipalityName = "Fribourg", - ZipCode = "1700", - ZipCodeAddOn = "00", - DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) - }, - TimeStamp = new DateTime(2022, 3, 17) - } - }, - new() - { - Record = new OpenApiZipDetail - { - Fields = new OpenApiZipFields - { - BfsCode = 2196, - Canton = "AG", - LanguageCode = 0, - MunicipalityName = "Aarau", - ZipCode = "5000", - ZipCodeAddOn = "00", - DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) - }, - TimeStamp = new DateTime(2022, 3, 17) - } - }, - new() - { - Record = new OpenApiZipDetail - { - Fields = new OpenApiZipFields - { - BfsCode = 6266, - Canton = "VS", - LanguageCode = 0, - MunicipalityName = "Sion", - ZipCode = "1950", - ZipCodeAddOn = "00", - DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) - }, - TimeStamp = new DateTime(2022, 3, 17) - } - }, - new() - { - Record = new OpenApiZipDetail - { - Fields = new OpenApiZipFields - { - BfsCode = 1201, - Canton = "UR", - LanguageCode = 0, - MunicipalityName = "Altdorf UR", - ZipCode = "6460", - ZipCodeAddOn = "00", - DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) - }, - TimeStamp = new DateTime(2022, 3, 17) - } - }, - new() - { - Record = new OpenApiZipDetail - { - Fields = new OpenApiZipFields - { - BfsCode = 1061, - Canton = "LU", - LanguageCode = 0, - MunicipalityName = "Luzern", - ZipCode = "6000", - ZipCodeAddOn = "00", - DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) - }, - TimeStamp = new DateTime(2022, 3, 17) - } - }, - new() - { - Record = new OpenApiZipDetail - { - Fields = new OpenApiZipFields - { - BfsCode = 2762, - Canton = "FR", - LanguageCode = 0, - MunicipalityName = "Allschwil", - ZipCode = "4123", - ZipCodeAddOn = "00", - DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) - }, - TimeStamp = new DateTime(2022, 3, 17) - } - }, - new() - { - Record = new OpenApiZipDetail - { - Fields = new OpenApiZipFields - { - BfsCode = 6742, - Canton = "AG", - LanguageCode = 0, - MunicipalityName = "Les Bois", - ZipCode = "2336", - ZipCodeAddOn = "00", - DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) - }, - TimeStamp = new DateTime(2022, 3, 17) - } - }, - new() - { - Record = new OpenApiZipDetail - { - Fields = new OpenApiZipFields - { - BfsCode = 3851, - Canton = "VS", - LanguageCode = 0, - MunicipalityName = "Davos Dorf", - ZipCode = "7260", - ZipCodeAddOn = "00", - DateOfValidity = new DateTime(1990, 3, 17).ToString(CultureInfo.InvariantCulture) - }, - TimeStamp = new DateTime(2022, 3, 17) - } - }, - } - }; - } -} diff --git a/src/PostOpenApi.Client/ServiceCollectionExtensions.cs b/src/PostOpenApi.Client/ServiceCollectionExtensions.cs deleted file mode 100644 index d7c9b94d..00000000 --- a/src/PostOpenApi.Client/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace PensionCoach.Tools.PostOpenApi; - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddPostOpenApiClient(this IServiceCollection services, IConfiguration configuration) - { - string baseUrl = configuration["TaxCalculatorClient:PostOpenApiBaseUrl"]; - - services.AddHttpClient(PostOpenApiClient.ClientName, c => - { - c.BaseAddress = new Uri(baseUrl); - }); - - services.AddTransient(); - - return services; - } -} diff --git a/src/PostOpenApi.Integration.Tests/PostOpenApi.Integration.Tests.csproj b/src/PostOpenApi.Integration.Tests/PostOpenApi.Integration.Tests.csproj index 266dde6c..2a429e26 100644 --- a/src/PostOpenApi.Integration.Tests/PostOpenApi.Integration.Tests.csproj +++ b/src/PostOpenApi.Integration.Tests/PostOpenApi.Integration.Tests.csproj @@ -1,22 +1,22 @@  - net7.0 + net8.0 enable false - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/PostOpenApi.Integration.Tests/PostOpenApiClientIntegrationTests.cs b/src/PostOpenApi.Integration.Tests/PostOpenApiClientIntegrationTests.cs index 343fd578..08003348 100644 --- a/src/PostOpenApi.Integration.Tests/PostOpenApiClientIntegrationTests.cs +++ b/src/PostOpenApi.Integration.Tests/PostOpenApiClientIntegrationTests.cs @@ -4,35 +4,36 @@ using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; +using Domain.Models.Municipality; using Microsoft.AspNetCore.Mvc.Testing; -using PensionCoach.Tools.PostOpenApi.Models; using Snapshooter.Xunit; using TaxCalculator.WebApi; using Xunit; -namespace PostOpenApi.Integration.Tests; - -[Trait("Post OpenApi Tests", "Integration")] -public class PostOpenApiClientIntegrationTests : IClassFixture> +namespace PostOpenApi.Integration.Tests { - private readonly HttpClient client; - - public PostOpenApiClientIntegrationTests(WebApplicationFactory factory) + [Trait("Post OpenApi Tests", "Integration")] + public class PostOpenApiClientIntegrationTests : IClassFixture> { - client = factory.CreateDefaultClient(new Uri("http://localhost/api/admin/")); - } + private readonly HttpClient client; - [Fact(DisplayName = "All Zip Codes")] - public async Task Should_Get_All_Zip_Codes_Successfully() - { - IEnumerable result = await client.GetFromJsonAsync>("zip") switch + public PostOpenApiClientIntegrationTests(WebApplicationFactory factory) + { + client = factory.CreateDefaultClient(new Uri("http://localhost/api/admin/")); + } + + [Fact(DisplayName = "All Zip Codes")] + public async Task Should_Get_All_Zip_Codes_Successfully() { - {} a => a.ToList().OrderBy(item => item.BfsCode) - .ThenBy(item => item.ZipCode) - .ThenBy(item => item.MunicipalityName), - null => Array.Empty() - }; + IEnumerable result = await client.GetFromJsonAsync>("zip") switch + { + {} a => a.ToList().OrderBy(item => item.BfsCode) + .ThenBy(item => item.ZipCode) + .ThenBy(item => item.MunicipalityName), + null => Array.Empty() + }; - Snapshot.Match(result); + Snapshot.Match(result); + } } } diff --git a/src/PostOpenApi.Integration.Tests/__snapshots__/PostOpenApiClientIntegrationTests.Should_Get_All_Zip_Codes_Successfully.snap b/src/PostOpenApi.Integration.Tests/__snapshots__/PostOpenApiClientIntegrationTests.Should_Get_All_Zip_Codes_Successfully.snap index 66d0f689..02fbfd2d 100644 --- a/src/PostOpenApi.Integration.Tests/__snapshots__/PostOpenApiClientIntegrationTests.Should_Get_All_Zip_Codes_Successfully.snap +++ b/src/PostOpenApi.Integration.Tests/__snapshots__/PostOpenApiClientIntegrationTests.Should_Get_All_Zip_Codes_Successfully.snap @@ -2645,15 +2645,6 @@ "DateOfValidity": "1993-09-09T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 243, - "ZipCode": "8953", - "ZipCodeAddOn": "02", - "MunicipalityName": "Dietikon Glanzenberg", - "Canton": "ZH", - "DateOfValidity": "1995-12-13T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 243, "ZipCode": "8953", @@ -3623,7 +3614,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Zürich", "Canton": "ZH", - "DateOfValidity": "2000-03-29T00:00:00", + "DateOfValidity": "1998-12-04T00:00:00", "LanguageCode": 0 }, { @@ -3686,7 +3677,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Zürich", "Canton": "ZH", - "DateOfValidity": "2000-03-29T00:00:00", + "DateOfValidity": "1998-10-30T00:00:00", "LanguageCode": 0 }, { @@ -4661,6 +4652,15 @@ "DateOfValidity": "1993-09-09T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 351, + "ZipCode": "3000", + "ZipCodeAddOn": "75", + "MunicipalityName": "Bern 22 Kaserne", + "Canton": "BE", + "DateOfValidity": "2003-01-01T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 351, "ZipCode": "3000", @@ -4706,15 +4706,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 351, - "ZipCode": "3000", - "ZipCodeAddOn": "04", - "MunicipalityName": "Bern Bogenschützenstrasse", - "Canton": "BE", - "DateOfValidity": "2022-07-18T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 351, "ZipCode": "3000", @@ -6047,15 +6038,6 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 412, - "ZipCode": "3422", - "ZipCodeAddOn": "60", - "MunicipalityName": "Kirchberg BE Zustellung", - "Canton": "BE", - "DateOfValidity": "2003-09-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 413, "ZipCode": "3425", @@ -15249,7 +15231,7 @@ "BfsCode": 2125, "ZipCode": "1630", "ZipCodeAddOn": "70", - "MunicipalityName": "Bulle Dist Fil", + "MunicipalityName": "Bulle Distribution colis", "Canton": "FR", "DateOfValidity": "1999-04-06T00:00:00", "LanguageCode": 0 @@ -19772,6 +19754,15 @@ "DateOfValidity": "2021-09-01T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 2831, + "ZipCode": "4135", + "ZipCodeAddOn": "00", + "MunicipalityName": "Pratteln INT", + "Canton": "BL", + "DateOfValidity": "2023-06-19T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 2831, "ZipCode": "4140", @@ -29519,6 +29510,15 @@ "DateOfValidity": "1998-02-16T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 4401, + "ZipCode": "9321", + "ZipCodeAddOn": "00", + "MunicipalityName": "Arbon MS", + "Canton": "TG", + "DateOfValidity": "2023-09-04T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 4406, "ZipCode": "8582", @@ -29669,7 +29669,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Amriswil", "Canton": "TG", - "DateOfValidity": "1996-08-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29678,7 +29678,7 @@ "ZipCodeAddOn": "60", "MunicipalityName": "Amriswil Zustellung", "Canton": "TG", - "DateOfValidity": "2003-09-01T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29696,7 +29696,7 @@ "ZipCodeAddOn": "05", "MunicipalityName": "Hagenwil b. Amriswil", "Canton": "TG", - "DateOfValidity": "1998-09-08T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29705,7 +29705,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Schocherswil", "Canton": "TG", - "DateOfValidity": "2000-10-01T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29714,7 +29714,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Oberaach", "Canton": "TG", - "DateOfValidity": "1996-08-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29723,7 +29723,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Bischofszell", "Canton": "TG", - "DateOfValidity": "1997-05-07T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29732,7 +29732,7 @@ "ZipCodeAddOn": "60", "MunicipalityName": "Bischofszell Zustellung", "Canton": "TG", - "DateOfValidity": "2003-05-01T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29741,7 +29741,7 @@ "ZipCodeAddOn": "01", "MunicipalityName": "Halden", "Canton": "TG", - "DateOfValidity": "1997-10-23T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29750,7 +29750,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Schweizersholz", "Canton": "TG", - "DateOfValidity": "1996-07-12T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29759,7 +29759,7 @@ "ZipCodeAddOn": "07", "MunicipalityName": "Buchackern", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29768,7 +29768,7 @@ "ZipCodeAddOn": "08", "MunicipalityName": "Engishofen", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29777,7 +29777,7 @@ "ZipCodeAddOn": "09", "MunicipalityName": "Ennetaach", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29786,7 +29786,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Erlen", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29795,7 +29795,7 @@ "ZipCodeAddOn": "03", "MunicipalityName": "Kümmertshausen", "Canton": "TG", - "DateOfValidity": "1998-09-10T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29804,7 +29804,7 @@ "ZipCodeAddOn": "05", "MunicipalityName": "Riedt b. Erlen", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29813,7 +29813,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Hauptwil", "Canton": "TG", - "DateOfValidity": "1996-07-12T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29822,7 +29822,7 @@ "ZipCodeAddOn": "01", "MunicipalityName": "St. Pelagiberg", "Canton": "TG", - "DateOfValidity": "1996-07-12T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29831,7 +29831,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Wilen (Gottshaus)", "Canton": "TG", - "DateOfValidity": "1996-07-12T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29840,7 +29840,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Heldswil", "Canton": "TG", - "DateOfValidity": "1999-06-14T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29849,7 +29849,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Hohentannen", "Canton": "TG", - "DateOfValidity": "1999-06-14T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29858,7 +29858,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Kradolf", "Canton": "TG", - "DateOfValidity": "1998-08-18T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29867,7 +29867,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Buhwil", "Canton": "TG", - "DateOfValidity": "1996-11-22T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29876,7 +29876,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Schönenberg an der Thur", "Canton": "TG", - "DateOfValidity": "1998-08-18T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29885,7 +29885,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Neukirch an der Thur", "Canton": "TG", - "DateOfValidity": "1997-03-10T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29894,7 +29894,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Donzhausen", "Canton": "TG", - "DateOfValidity": "1996-08-27T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29903,7 +29903,7 @@ "ZipCodeAddOn": "01", "MunicipalityName": "Götighofen", "Canton": "TG", - "DateOfValidity": "1996-08-27T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29912,7 +29912,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Sulgen", "Canton": "TG", - "DateOfValidity": "1996-09-09T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29921,7 +29921,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Zihlschlacht", "Canton": "TG", - "DateOfValidity": "1997-04-11T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29930,7 +29930,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Sitterdorf", "Canton": "TG", - "DateOfValidity": "1997-04-11T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29939,7 +29939,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Basadingen", "Canton": "TG", - "DateOfValidity": "1999-06-14T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29948,7 +29948,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Schlattingen", "Canton": "TG", - "DateOfValidity": "1999-06-14T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29957,16 +29957,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Diessenhofen", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 4545, - "ZipCode": "8253", - "ZipCodeAddOn": "60", - "MunicipalityName": "Diessenhofen Zustellung", - "Canton": "TG", - "DateOfValidity": "2003-07-01T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29975,7 +29966,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Willisdorf", "Canton": "TG", - "DateOfValidity": "1996-08-13T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -29984,7 +29975,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Schlatt TG", "Canton": "TG", - "DateOfValidity": "1999-06-14T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31028,7 +31019,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Berlingen", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31037,7 +31028,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Eschenz", "Canton": "TG", - "DateOfValidity": "1996-08-05T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31046,7 +31037,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Lanzenneunforn", "Canton": "TG", - "DateOfValidity": "1998-02-16T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31055,7 +31046,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Herdern", "Canton": "TG", - "DateOfValidity": "1999-12-09T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31064,7 +31055,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Hörhausen", "Canton": "TG", - "DateOfValidity": "1999-06-14T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31073,7 +31064,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Homburg", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31082,7 +31073,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Hüttwilen", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31091,7 +31082,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Nussbaumen TG", "Canton": "TG", - "DateOfValidity": "1997-04-11T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31100,7 +31091,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Uerschhausen", "Canton": "TG", - "DateOfValidity": "1998-04-14T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31109,7 +31100,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Mammern", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31118,7 +31109,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Müllheim Dorf", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31127,7 +31118,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Dettighofen", "Canton": "TG", - "DateOfValidity": "1999-11-16T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31136,7 +31127,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Pfyn", "Canton": "TG", - "DateOfValidity": "1998-02-16T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31145,7 +31136,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Raperswilen", "Canton": "TG", - "DateOfValidity": "1997-04-11T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31154,7 +31145,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Mannenbach-Salenstein", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31163,7 +31154,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Salenstein", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31172,7 +31163,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Fruthwilen", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31181,7 +31172,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Steckborn", "Canton": "TG", - "DateOfValidity": "1997-04-11T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31190,7 +31181,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Etzwilen", "Canton": "TG", - "DateOfValidity": "1996-06-11T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31199,7 +31190,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Kaltenbach", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31208,7 +31199,7 @@ "ZipCodeAddOn": "03", "MunicipalityName": "Rheinklingen", "Canton": "TG", - "DateOfValidity": "1996-06-11T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31217,7 +31208,7 @@ "ZipCodeAddOn": "04", "MunicipalityName": "Wagenhausen", "Canton": "TG", - "DateOfValidity": "1996-09-20T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31325,7 +31316,7 @@ "ZipCodeAddOn": "10", "MunicipalityName": "Buch b. Kümmertshausen", "Canton": "TG", - "DateOfValidity": "1999-11-22T00:00:00", + "DateOfValidity": "2005-12-01T00:00:00", "LanguageCode": 0 }, { @@ -32138,15 +32129,6 @@ "DateOfValidity": "2003-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5061, - "ZipCode": "6780", - "ZipCodeAddOn": "02", - "MunicipalityName": "Madrano", - "Canton": "TI", - "DateOfValidity": "1995-12-06T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5063, "ZipCode": "6781", @@ -41552,6 +41534,15 @@ "DateOfValidity": "1999-04-08T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 6136, + "ZipCode": "1920", + "ZipCodeAddOn": "02", + "MunicipalityName": "Martigny Bourg", + "Canton": "VS", + "DateOfValidity": "1996-09-02T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 6137, "ZipCode": "1921", @@ -44990,6 +44981,15 @@ "DateOfValidity": "1996-06-10T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 6630, + "ZipCode": "1217", + "ZipCodeAddOn": "02", + "MunicipalityName": "Meyrin 2", + "Canton": "GE", + "DateOfValidity": "1996-06-10T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 6630, "ZipCode": "1217", @@ -46227,7 +46227,7 @@ "BfsCode": 7005, "ZipCode": "9494", "ZipCodeAddOn": "68", - "MunicipalityName": "Schaan BZ GKS", + "MunicipalityName": "Schaan BZ Spezialkunden", "Canton": "FL", "DateOfValidity": "2008-03-01T00:00:00", "LanguageCode": 0 diff --git a/src/Tax.Data.Abstractions/ITaxDataPopulateService.cs b/src/Tax.Data.Abstractions/ITaxDataPopulateService.cs deleted file mode 100644 index 1d93f0d1..00000000 --- a/src/Tax.Data.Abstractions/ITaxDataPopulateService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Threading.Tasks; - -namespace Tax.Data.Abstractions; - -public interface ITaxDataPopulateService -{ - /// - /// Populate municipality data with ESTV's tax location id. - /// If doClear is true, tax location id is set to null first. - /// - /// Number of cases processed. - Task PopulateWithTaxLocationAsync(bool doClear); -} diff --git a/src/Tax.Data.Abstractions/ITaxTariffData.cs b/src/Tax.Data.Abstractions/ITaxTariffData.cs deleted file mode 100644 index 79d3c146..00000000 --- a/src/Tax.Data.Abstractions/ITaxTariffData.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using Tax.Data.Abstractions.Models; - -namespace Tax.Data.Abstractions -{ - public interface ITaxTariffData - { - IReadOnlyCollection Get(TaxFilterModel filter); - } -} \ No newline at end of file diff --git a/src/Tax.Data.Abstractions/Models/DbSettings.cs b/src/Tax.Data.Abstractions/Models/DbSettings.cs deleted file mode 100644 index 73b2bdb9..00000000 --- a/src/Tax.Data.Abstractions/Models/DbSettings.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Tax.Data.Abstractions.Models -{ - public class DbSettings - { - public string FilePath { get; set; } - } -} diff --git a/src/Tax.Data.Abstractions/Models/Populate/SearchMunicipalityRequest.cs b/src/Tax.Data.Abstractions/Models/Populate/SearchMunicipalityRequest.cs deleted file mode 100644 index 0b7a5bdc..00000000 --- a/src/Tax.Data.Abstractions/Models/Populate/SearchMunicipalityRequest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Tax.Data.Abstractions.Models.Populate; - -public record SearchMunicipalityRequest(string Zipcode, string Name); diff --git a/src/Tax.Data.Abstractions/Models/Populate/SearchResultType.cs b/src/Tax.Data.Abstractions/Models/Populate/SearchResultType.cs deleted file mode 100644 index 53f709b9..00000000 --- a/src/Tax.Data.Abstractions/Models/Populate/SearchResultType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Tax.Data.Abstractions.Models.Populate; - -public enum SearchResultType -{ - None, - Match, - NotUnique, - NotSet -} diff --git a/src/Tax.Data.Abstractions/Models/Populate/TaxLocationSearchHolder.cs b/src/Tax.Data.Abstractions/Models/Populate/TaxLocationSearchHolder.cs deleted file mode 100644 index cd6fcd64..00000000 --- a/src/Tax.Data.Abstractions/Models/Populate/TaxLocationSearchHolder.cs +++ /dev/null @@ -1,14 +0,0 @@ -using PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; - -namespace Tax.Data.Abstractions.Models.Populate; - -public record TaxLocationSearchHolder -{ - public MunicipalityEntity MunicipalityEntity { get; init; } - - public TaxLocation[] TaxLocations { get; init; } - - public int SearchLevel { get; set; } - - public SearchResultType SearchResultType { get; set; } -} diff --git a/src/Tax.Data.Abstractions/Tax.Data.Abstractions.csproj b/src/Tax.Data.Abstractions/Tax.Data.Abstractions.csproj deleted file mode 100644 index 4c77db2c..00000000 --- a/src/Tax.Data.Abstractions/Tax.Data.Abstractions.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - net7.0 - - - - - - - - - - - diff --git a/src/Tax.Data.Integration.Tests/AdminIntegrationTests.cs b/src/Tax.Data.Integration.Tests/AdminIntegrationTests.cs index c6e26b6e..023d6ec7 100644 --- a/src/Tax.Data.Integration.Tests/AdminIntegrationTests.cs +++ b/src/Tax.Data.Integration.Tests/AdminIntegrationTests.cs @@ -4,101 +4,102 @@ using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; +using Domain.Models.Municipality; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; -using PensionCoach.Tools.PostOpenApi.Models; using Snapshooter.Xunit; using TaxCalculator.WebApi; using Xunit; using Xunit.Abstractions; -namespace Tax.Data.Integration.Tests; - -[Trait("Admin", "Integration")] -public class AdminIntegrationTests : IClassFixture> +namespace Tax.Data.Integration.Tests { - private readonly ITestOutputHelper outputHelper; - private readonly HttpClient client; - - public AdminIntegrationTests(ITestOutputHelper outputHelper) + [Trait("Admin", "Integration")] + public class AdminIntegrationTests : IClassFixture> { - this.outputHelper = outputHelper; - var testServer = new TestServer( - new WebHostBuilder() - .ConfigureAppConfiguration((_, builder) => - { - builder.AddJsonFile("appsettings.integration.json"); - }) - .UseStartup()); - - client = testServer.CreateClient(); - client.BaseAddress = new Uri("http://localhost/api/admin/"); - } + private readonly ITestOutputHelper outputHelper; + private readonly HttpClient client; - [Fact(DisplayName = "Populate With Tax Location", Skip = "Affects real data")] - public async Task Populate_With_Tax_Location() - { - bool doClear = true; + public AdminIntegrationTests(ITestOutputHelper outputHelper) + { + this.outputHelper = outputHelper; + var testServer = new TestServer( + new WebHostBuilder() + .ConfigureAppConfiguration((_, builder) => + { + builder.AddJsonFile("appsettings.integration.json"); + }) + .UseStartup()); + + client = testServer.CreateClient(); + client.BaseAddress = new Uri("http://localhost/api/admin/"); + } + + [Fact(DisplayName = "Populate With Tax Location", Skip = "Affects real data")] + public async Task Populate_With_Tax_Location() + { + bool doClear = true; - HttpResponseMessage response = await client.PostAsync($"tax/populate/{doClear}", null); + HttpResponseMessage response = await client.PostAsync($"tax/populate/{doClear}", null); - response.EnsureSuccessStatusCode(); + response.EnsureSuccessStatusCode(); - int result = await response.Content.ReadFromJsonAsync(); + int result = await response.Content.ReadFromJsonAsync(); - outputHelper.WriteLine($"Number of updates: {result}"); - } + outputHelper.WriteLine($"Number of updates: {result}"); + } - [Fact(DisplayName = "Populate With Zip Codes", Skip = "Affects real data")] - public async Task Populate_With_All_ZipCodes() - { - HttpResponseMessage response = await client.PostAsync("zip/populate", null); + [Fact(DisplayName = "Populate With Zip Codes", Skip = "Affects real data")] + public async Task Populate_With_All_ZipCodes() + { + HttpResponseMessage response = await client.PostAsync("zip/populate", null); - response.EnsureSuccessStatusCode(); + response.EnsureSuccessStatusCode(); - var result = await response.Content.ReadFromJsonAsync(); + var result = await response.Content.ReadFromJsonAsync(); - outputHelper.WriteLine($"Number of updates: {result}"); - } + outputHelper.WriteLine($"Number of updates: {result}"); + } - [Fact(DisplayName = "Stage Zip Codes", Skip = "Affects real data")] - public async Task Stage_With_ZipCodes() - { - HttpResponseMessage response = await client.PostAsync("zip/stage", null); + [Fact(DisplayName = "Stage Zip Codes", Skip = "Affects real data")] + public async Task Stage_With_ZipCodes() + { + HttpResponseMessage response = await client.PostAsync("zip/stage", null); - response.EnsureSuccessStatusCode(); + response.EnsureSuccessStatusCode(); - int result = await response.Content.ReadFromJsonAsync(); + int result = await response.Content.ReadFromJsonAsync(); - Assert.True(result > 0); - } + Assert.True(result > 0); + } - [Fact(DisplayName = "All Zip Codes")] - public async Task Get_All_Zip_Codes_Successfully() - { - IEnumerable result = await client.GetFromJsonAsync>("zip") switch + [Fact(DisplayName = "All Zip Codes")] + public async Task Get_All_Zip_Codes_Successfully() { - { } a => a.ToList() - .OrderBy(item => item.BfsCode) - .ThenBy(item => item.MunicipalityName) - .ThenBy(item => item.ZipCode), - null => Array.Empty() - }; - - Snapshot.Match(result); - } - - [Fact(DisplayName = "Clean Municipality Names", Skip = "Affects real data")] - public async Task Clean_Municipality_Names() - { - HttpResponseMessage response = await client.PostAsync("tax/clean", null); + IEnumerable result = await client.GetFromJsonAsync>("zip") switch + { + { } a => a.ToList() + .OrderBy(item => item.BfsCode) + .ThenBy(item => item.MunicipalityName) + .ThenBy(item => item.ZipCode), + null => Array.Empty() + }; + + Snapshot.Match(result); + } + + [Fact(DisplayName = "Clean Municipality Names", Skip = "Affects real data")] + public async Task Clean_Municipality_Names() + { + HttpResponseMessage response = await client.PostAsync("tax/clean", null); - response.EnsureSuccessStatusCode(); + response.EnsureSuccessStatusCode(); - int result = await response.Content.ReadFromJsonAsync(); + int result = await response.Content.ReadFromJsonAsync(); - Assert.True(result > 0); + Assert.True(result > 0); + } } } diff --git a/src/Tax.Data.Integration.Tests/MunicipalityDataIntegrationTests.cs b/src/Tax.Data.Integration.Tests/MunicipalityDataIntegrationTests.cs index fadd27a3..736f9468 100644 --- a/src/Tax.Data.Integration.Tests/MunicipalityDataIntegrationTests.cs +++ b/src/Tax.Data.Integration.Tests/MunicipalityDataIntegrationTests.cs @@ -3,12 +3,13 @@ using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; +using Domain.Enums; +using Domain.Models.Municipality; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Municipality; using Snapshooter.Xunit; using TaxCalculator.WebApi; using Xunit; diff --git a/src/Tax.Data.Integration.Tests/Tax.Data.Integration.Tests.csproj b/src/Tax.Data.Integration.Tests/Tax.Data.Integration.Tests.csproj index df8b48a1..2b021454 100644 --- a/src/Tax.Data.Integration.Tests/Tax.Data.Integration.Tests.csproj +++ b/src/Tax.Data.Integration.Tests/Tax.Data.Integration.Tests.csproj @@ -1,22 +1,22 @@  - net7.0 + net8.0 false - - - - - - - + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Tax.Data.Integration.Tests/__snapshots__/AdminIntegrationTests.Get_All_Zip_Codes_Successfully.snap b/src/Tax.Data.Integration.Tests/__snapshots__/AdminIntegrationTests.Get_All_Zip_Codes_Successfully.snap index e5fb8dcd..7a0f6aba 100644 --- a/src/Tax.Data.Integration.Tests/__snapshots__/AdminIntegrationTests.Get_All_Zip_Codes_Successfully.snap +++ b/src/Tax.Data.Integration.Tests/__snapshots__/AdminIntegrationTests.Get_All_Zip_Codes_Successfully.snap @@ -35,15 +35,6 @@ "DateOfValidity": "1996-08-02T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2, - "ZipCode": "8910", - "ZipCodeAddOn": "02", - "MunicipalityName": "Affoltern am Albis Obfelder", - "Canton": "ZH", - "DateOfValidity": "2021-08-09T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2, "ZipCode": "8909", @@ -197,15 +188,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 21, - "ZipCode": "8452", - "ZipCodeAddOn": "00", - "MunicipalityName": "Adlikon b. Andelfingen", - "Canton": "ZH", - "DateOfValidity": "1994-03-18T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 22, "ZipCode": "8463", @@ -296,24 +278,6 @@ "DateOfValidity": "1996-03-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 30, - "ZipCode": "8450", - "ZipCodeAddOn": "00", - "MunicipalityName": "Andelfingen", - "Canton": "ZH", - "DateOfValidity": "1988-06-01T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 30, - "ZipCode": "8450", - "ZipCodeAddOn": "60", - "MunicipalityName": "Andelfingen Zustellung", - "Canton": "ZH", - "DateOfValidity": "2004-12-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 31, "ZipCode": "8444", @@ -323,15 +287,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 32, - "ZipCode": "8457", - "ZipCodeAddOn": "00", - "MunicipalityName": "Humlikon", - "Canton": "ZH", - "DateOfValidity": "1988-05-29T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 33, "ZipCode": "8453", @@ -395,15 +350,6 @@ "DateOfValidity": "1988-06-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 35, - "ZipCode": "8460", - "ZipCodeAddOn": "02", - "MunicipalityName": "Marthalen Güeterstrass", - "Canton": "ZH", - "DateOfValidity": "2019-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 37, "ZipCode": "8475", @@ -557,15 +503,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 56, - "ZipCode": "8424", - "ZipCodeAddOn": "01", - "MunicipalityName": "Embrach Dorfstrasse", - "Canton": "ZH", - "DateOfValidity": "2018-06-25T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 56, "ZipCode": "8424", @@ -647,15 +584,6 @@ "DateOfValidity": "1996-08-05T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 62, - "ZipCode": "8302", - "ZipCodeAddOn": "01", - "MunicipalityName": "Kloten Bahnhofstrasse", - "Canton": "ZH", - "DateOfValidity": "2020-09-09T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 62, "ZipCode": "8302", @@ -728,15 +656,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 66, - "ZipCode": "8152", - "ZipCodeAddOn": "03", - "MunicipalityName": "Glattbrugg Schaffhauserstr", - "Canton": "ZH", - "DateOfValidity": "2020-10-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 66, "ZipCode": "8152", @@ -791,15 +710,6 @@ "DateOfValidity": "1994-09-20T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 69, - "ZipCode": "8301", - "ZipCodeAddOn": "01", - "MunicipalityName": "Glattzentrum N. Wint.str.", - "Canton": "ZH", - "DateOfValidity": "2017-04-24T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 69, "ZipCode": "8304", @@ -1324,20 +1234,20 @@ }, { "BfsCode": 117, - "ZipCode": "8343", - "ZipCodeAddOn": "00", - "MunicipalityName": "Hinwil Dist Ba", + "ZipCode": "8340", + "ZipCodeAddOn": "60", + "MunicipalityName": "Hinwil Zustellung", "Canton": "ZH", - "DateOfValidity": "1998-11-23T00:00:00", + "DateOfValidity": "2003-03-01T00:00:00", "LanguageCode": 0 }, { "BfsCode": 117, - "ZipCode": "8340", - "ZipCodeAddOn": "60", - "MunicipalityName": "Hinwil Zustellung", + "ZipCode": "8343", + "ZipCodeAddOn": "00", + "MunicipalityName": "Hinwil Zustellung Pakete", "Canton": "ZH", - "DateOfValidity": "2003-03-01T00:00:00", + "DateOfValidity": "1998-11-23T00:00:00", "LanguageCode": 0 }, { @@ -1394,15 +1304,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 120, - "ZipCode": "8636", - "ZipCodeAddOn": "01", - "MunicipalityName": "Wald ZH Poststrasse", - "Canton": "ZH", - "DateOfValidity": "2020-02-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 120, "ZipCode": "8636", @@ -1412,24 +1313,6 @@ "DateOfValidity": "2003-09-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 121, - "ZipCode": "8620", - "ZipCodeAddOn": "04", - "MunicipalityName": "Wetzikon Kastellstrasse", - "Canton": "ZH", - "DateOfValidity": "2015-04-01T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 121, - "ZipCode": "8620", - "ZipCodeAddOn": "05", - "MunicipalityName": "Wetzikon Poststrasse", - "Canton": "ZH", - "DateOfValidity": "2016-09-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 121, "ZipCode": "8620", @@ -1502,15 +1385,6 @@ "DateOfValidity": "2003-04-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 131, - "ZipCode": "8134", - "ZipCodeAddOn": "03", - "MunicipalityName": "Adliswil Poststrasse", - "Canton": "ZH", - "DateOfValidity": "2020-02-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 131, "ZipCode": "8134", @@ -1673,15 +1547,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 155, - "ZipCode": "8708", - "ZipCodeAddOn": "60", - "MunicipalityName": "Männedorf Zustellung", - "Canton": "ZH", - "DateOfValidity": "2003-10-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 156, "ZipCode": "8706", @@ -1691,15 +1556,6 @@ "DateOfValidity": "1996-03-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 156, - "ZipCode": "8706", - "ZipCodeAddOn": "03", - "MunicipalityName": "Meilen Dorfstrasse", - "Canton": "ZH", - "DateOfValidity": "2022-06-27T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 156, "ZipCode": "8706", @@ -1727,6 +1583,15 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 158, + "ZipCode": "8712", + "ZipCodeAddOn": "02", + "MunicipalityName": "Stäfa Bahnhofstrasse", + "Canton": "ZH", + "DateOfValidity": "2022-11-07T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 158, "ZipCode": "8713", @@ -1826,15 +1691,6 @@ "DateOfValidity": "2001-06-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 176, - "ZipCode": "8310", - "ZipCodeAddOn": "03", - "MunicipalityName": "Kemptthal Kemptpark", - "Canton": "ZH", - "DateOfValidity": "2019-09-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 176, "ZipCode": "8315", @@ -1880,15 +1736,6 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 177, - "ZipCode": "8330", - "ZipCodeAddOn": "01", - "MunicipalityName": "Pfäffikon ZH Bahnhofstrasse", - "Canton": "ZH", - "DateOfValidity": "2016-09-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 177, "ZipCode": "8330", @@ -2024,15 +1871,6 @@ "DateOfValidity": "2003-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 191, - "ZipCode": "8600", - "ZipCodeAddOn": "04", - "MunicipalityName": "Dübendorf Wilstrasse", - "Canton": "ZH", - "DateOfValidity": "2017-11-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 191, "ZipCode": "8044", @@ -2087,6 +1925,15 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 193, + "ZipCode": "8117", + "ZipCodeAddOn": "02", + "MunicipalityName": "Fällanden Wigartenstrasse", + "Canton": "ZH", + "DateOfValidity": "2023-03-13T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 193, "ZipCode": "8118", @@ -2159,15 +2006,6 @@ "DateOfValidity": "1999-10-25T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 197, - "ZipCode": "8603", - "ZipCodeAddOn": "01", - "MunicipalityName": "Schwerzenbach Bahnstrasse", - "Canton": "ZH", - "DateOfValidity": "2018-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 198, "ZipCode": "8615", @@ -2276,15 +2114,6 @@ "DateOfValidity": "1988-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 199, - "ZipCode": "8604", - "ZipCodeAddOn": "01", - "MunicipalityName": "Volketswil Zentrum", - "Canton": "ZH", - "DateOfValidity": "2018-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 200, "ZipCode": "8306", @@ -2402,15 +2231,6 @@ "DateOfValidity": "1997-04-11T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 219, - "ZipCode": "8352", - "ZipCodeAddOn": "01", - "MunicipalityName": "Elsau St. Gallerstrasse", - "Canton": "ZH", - "DateOfValidity": "2017-08-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 220, "ZipCode": "8523", @@ -2726,15 +2546,6 @@ "DateOfValidity": "2003-02-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 230, - "ZipCode": "8411", - "ZipCodeAddOn": "03", - "MunicipalityName": "Winterthur Pflanzschulstr", - "Canton": "ZH", - "DateOfValidity": "2015-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 230, "ZipCode": "8400", @@ -3086,15 +2897,6 @@ "DateOfValidity": "2020-03-27T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 250, - "ZipCode": "8902", - "ZipCodeAddOn": "70", - "MunicipalityName": "Urdorf Dist Ba", - "Canton": "ZH", - "DateOfValidity": "2004-07-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 250, "ZipCode": "8970", @@ -3140,6 +2942,15 @@ "DateOfValidity": "2004-12-01T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 250, + "ZipCode": "8902", + "ZipCodeAddOn": "70", + "MunicipalityName": "Urdorf Zustellung", + "Canton": "ZH", + "DateOfValidity": "2004-07-01T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 251, "ZipCode": "8951", @@ -3551,7 +3362,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Zürich", "Canton": "ZH", - "DateOfValidity": "2000-03-29T00:00:00", + "DateOfValidity": "1998-12-04T00:00:00", "LanguageCode": 0 }, { @@ -3587,7 +3398,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Zürich", "Canton": "ZH", - "DateOfValidity": "2000-03-29T00:00:00", + "DateOfValidity": "1998-10-30T00:00:00", "LanguageCode": 0 }, { @@ -3662,15 +3473,6 @@ "DateOfValidity": "1999-05-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 261, - "ZipCode": "8048", - "ZipCodeAddOn": "02", - "MunicipalityName": "Zürich Altstetterstrasse", - "Canton": "ZH", - "DateOfValidity": "2021-05-24T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 261, "ZipCode": "8006", @@ -3707,15 +3509,6 @@ "DateOfValidity": "1996-12-13T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 261, - "ZipCode": "8050", - "ZipCodeAddOn": "70", - "MunicipalityName": "Zürich Dist Ba", - "Canton": "ZH", - "DateOfValidity": "2004-07-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 261, "ZipCode": "8093", @@ -3743,15 +3536,6 @@ "DateOfValidity": "2017-11-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 261, - "ZipCode": "8027", - "ZipCodeAddOn": "02", - "MunicipalityName": "Zürich Gutenbergstrasse", - "Canton": "ZH", - "DateOfValidity": "2021-09-09T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 261, "ZipCode": "8081", @@ -3788,15 +3572,6 @@ "DateOfValidity": "2007-02-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 261, - "ZipCode": "8031", - "ZipCodeAddOn": "01", - "MunicipalityName": "Zürich Limmatstrasse", - "Canton": "ZH", - "DateOfValidity": "2018-02-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 261, "ZipCode": "8032", @@ -3815,6 +3590,15 @@ "DateOfValidity": "2019-03-01T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 261, + "ZipCode": "8050", + "ZipCodeAddOn": "70", + "MunicipalityName": "Zürich Oerlikon Zust Pakete", + "Canton": "ZH", + "DateOfValidity": "2004-07-01T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 261, "ZipCode": "8018", @@ -3860,6 +3644,24 @@ "DateOfValidity": "2018-03-01T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 261, + "ZipCode": "8036", + "ZipCodeAddOn": "02", + "MunicipalityName": "Zürich Seebahnstrasse", + "Canton": "ZH", + "DateOfValidity": "2023-02-06T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 261, + "ZipCode": "8045", + "ZipCodeAddOn": "03", + "MunicipalityName": "Zürich Sihlcity", + "Canton": "ZH", + "DateOfValidity": "2017-10-01T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 261, "ZipCode": "8099", @@ -3878,15 +3680,6 @@ "DateOfValidity": "2015-10-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 261, - "ZipCode": "8025", - "ZipCodeAddOn": "01", - "MunicipalityName": "Zürich Uraniastrasse", - "Canton": "ZH", - "DateOfValidity": "2016-10-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 261, "ZipCode": "8085", @@ -3905,6 +3698,42 @@ "DateOfValidity": "2008-05-01T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 291, + "ZipCode": "8452", + "ZipCodeAddOn": "00", + "MunicipalityName": "Adlikon b. Andelfingen", + "Canton": "ZH", + "DateOfValidity": "1994-03-18T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 291, + "ZipCode": "8450", + "ZipCodeAddOn": "00", + "MunicipalityName": "Andelfingen", + "Canton": "ZH", + "DateOfValidity": "1988-06-01T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 291, + "ZipCode": "8450", + "ZipCodeAddOn": "60", + "MunicipalityName": "Andelfingen Zustellung", + "Canton": "ZH", + "DateOfValidity": "2004-12-01T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 291, + "ZipCode": "8457", + "ZipCodeAddOn": "00", + "MunicipalityName": "Humlikon", + "Canton": "ZH", + "DateOfValidity": "1988-05-29T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 292, "ZipCode": "8468", @@ -3980,19 +3809,19 @@ { "BfsCode": 293, "ZipCode": "8820", - "ZipCodeAddOn": "70", - "MunicipalityName": "Wädenswil Dist Ba", + "ZipCodeAddOn": "60", + "MunicipalityName": "Wädenswil Zustellung", "Canton": "ZH", - "DateOfValidity": "2005-09-01T00:00:00", + "DateOfValidity": "2003-07-01T00:00:00", "LanguageCode": 0 }, { "BfsCode": 293, "ZipCode": "8820", - "ZipCodeAddOn": "60", - "MunicipalityName": "Wädenswil Zustellung", + "ZipCodeAddOn": "70", + "MunicipalityName": "Wädenswil Zustellung Pakete", "Canton": "ZH", - "DateOfValidity": "2003-07-01T00:00:00", + "DateOfValidity": "2005-09-01T00:00:00", "LanguageCode": 0 }, { @@ -4058,15 +3887,6 @@ "DateOfValidity": "1993-09-09T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 295, - "ZipCode": "8810", - "ZipCodeAddOn": "03", - "MunicipalityName": "Horgen Bahnhofstrasse", - "Canton": "ZH", - "DateOfValidity": "2020-10-24T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 295, "ZipCode": "8815", @@ -4114,20 +3934,20 @@ }, { "BfsCode": 296, - "ZipCode": "8325", - "ZipCodeAddOn": "00", - "MunicipalityName": "Effretikon Dist Ba", + "ZipCode": "8307", + "ZipCodeAddOn": "76", + "MunicipalityName": "Effretikon PostAuto AG", "Canton": "ZH", - "DateOfValidity": "1998-12-22T00:00:00", + "DateOfValidity": "2009-07-01T00:00:00", "LanguageCode": 0 }, { "BfsCode": 296, - "ZipCode": "8307", - "ZipCodeAddOn": "76", - "MunicipalityName": "Effretikon PostAuto AG", + "ZipCode": "8325", + "ZipCodeAddOn": "00", + "MunicipalityName": "Effretikon Zust Pakete", "Canton": "ZH", - "DateOfValidity": "2009-07-01T00:00:00", + "DateOfValidity": "1998-12-22T00:00:00", "LanguageCode": 0 }, { @@ -4364,15 +4184,6 @@ "DateOfValidity": "2003-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 306, - "ZipCode": "3250", - "ZipCodeAddOn": "03", - "MunicipalityName": "Lyss Werkstrasse", - "Canton": "BE", - "DateOfValidity": "2018-08-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 306, "ZipCode": "3250", @@ -4715,15 +4526,6 @@ "DateOfValidity": "1996-09-12T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 337, - "ZipCode": "4914", - "ZipCodeAddOn": "02", - "MunicipalityName": "Roggwil BE Dorfstrasse", - "Canton": "BE", - "DateOfValidity": "2021-03-11T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 338, "ZipCode": "4938", @@ -4769,15 +4571,6 @@ "DateOfValidity": "1996-09-12T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 342, - "ZipCode": "4922", - "ZipCodeAddOn": "70", - "MunicipalityName": "Bützberg RPZ", - "Canton": "BE", - "DateOfValidity": "2021-07-28T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 342, "ZipCode": "4922", @@ -5104,11 +4897,11 @@ }, { "BfsCode": 351, - "ZipCode": "3000", - "ZipCodeAddOn": "04", - "MunicipalityName": "Bern Bogenschützenstrasse", + "ZipCode": "3040", + "ZipCodeAddOn": "13", + "MunicipalityName": "Bern BPMP", "Canton": "BE", - "DateOfValidity": "2022-07-18T00:00:00", + "DateOfValidity": "2018-08-22T00:00:00", "LanguageCode": 0 }, { @@ -5156,15 +4949,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 351, - "ZipCode": "3018", - "ZipCodeAddOn": "02", - "MunicipalityName": "Bern Freiburgstrasse", - "Canton": "BE", - "DateOfValidity": "2019-07-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 351, "ZipCode": "3000", @@ -5624,15 +5408,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 361, - "ZipCode": "3052", - "ZipCodeAddOn": "01", - "MunicipalityName": "Zollikofen Waldstrasse", - "Canton": "BE", - "DateOfValidity": "2017-11-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 362, "ZipCode": "3063", @@ -5642,15 +5417,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 362, - "ZipCode": "3063", - "ZipCodeAddOn": "02", - "MunicipalityName": "Ittigen Talgut-Zentrum", - "Canton": "BE", - "DateOfValidity": "2020-08-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 362, "ZipCode": "3048", @@ -5696,15 +5462,6 @@ "DateOfValidity": "2019-07-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 363, - "ZipCode": "3071", - "ZipCodeAddOn": "70", - "MunicipalityName": "Ostermundigen Distributions", - "Canton": "BE", - "DateOfValidity": "2004-10-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 363, "ZipCode": "3071", @@ -5732,6 +5489,15 @@ "DateOfValidity": "2019-07-01T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 363, + "ZipCode": "3071", + "ZipCodeAddOn": "70", + "MunicipalityName": "Ostermundigen Zust Pakete", + "Canton": "BE", + "DateOfValidity": "2004-10-01T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 363, "ZipCode": "3071", @@ -5858,24 +5624,6 @@ "DateOfValidity": "1988-06-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 371, - "ZipCode": "2500", - "ZipCodeAddOn": "11", - "MunicipalityName": "Biel/Bienne Bahnhof/Gare GK", - "Canton": "BE", - "DateOfValidity": "2019-07-01T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 371, - "ZipCode": "2510", - "ZipCodeAddOn": "00", - "MunicipalityName": "Biel/Bienne Dist Ba", - "Canton": "BE", - "DateOfValidity": "1998-11-23T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 371, "ZipCode": "2500", @@ -5887,11 +5635,11 @@ }, { "BfsCode": 371, - "ZipCode": "2500", - "ZipCodeAddOn": "12", - "MunicipalityName": "Biel/Bienne Rennweg", + "ZipCode": "2510", + "ZipCodeAddOn": "00", + "MunicipalityName": "Biel/Bienne Zust Pakete", "Canton": "BE", - "DateOfValidity": "2019-11-01T00:00:00", + "DateOfValidity": "1998-11-23T00:00:00", "LanguageCode": 0 }, { @@ -6110,24 +5858,6 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 404, - "ZipCode": "3400", - "ZipCodeAddOn": "03", - "MunicipalityName": "Burgdorf Bahnhofstrasse", - "Canton": "BE", - "DateOfValidity": "2018-11-01T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 404, - "ZipCode": "3400", - "ZipCodeAddOn": "70", - "MunicipalityName": "Burgdorf Dist Fil", - "Canton": "BE", - "DateOfValidity": "1999-01-14T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 404, "ZipCode": "3400", @@ -6155,6 +5885,15 @@ "DateOfValidity": "1999-04-12T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 404, + "ZipCode": "3400", + "ZipCodeAddOn": "70", + "MunicipalityName": "Burgdorf Zustellung Pakete", + "Canton": "BE", + "DateOfValidity": "1999-01-14T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 405, "ZipCode": "3423", @@ -6308,15 +6047,6 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 412, - "ZipCode": "3422", - "ZipCodeAddOn": "60", - "MunicipalityName": "Kirchberg BE Zustellung", - "Canton": "BE", - "DateOfValidity": "2003-09-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 413, "ZipCode": "3425", @@ -6830,15 +6560,6 @@ "DateOfValidity": "1999-07-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 536, - "ZipCode": "3053", - "ZipCodeAddOn": "03", - "MunicipalityName": "Diemerswil", - "Canton": "BE", - "DateOfValidity": "1998-07-23T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 538, "ZipCode": "3313", @@ -6983,6 +6704,15 @@ "DateOfValidity": "1996-05-07T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 546, + "ZipCode": "3053", + "ZipCodeAddOn": "03", + "MunicipalityName": "Diemerswil", + "Canton": "BE", + "DateOfValidity": "1998-07-23T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 546, "ZipCode": "3053", @@ -7010,15 +6740,6 @@ "DateOfValidity": "1996-06-10T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 551, - "ZipCode": "3322", - "ZipCodeAddOn": "04", - "MunicipalityName": "Urtenen-Schönbühl Z-Platz", - "Canton": "BE", - "DateOfValidity": "2021-01-13T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 551, "ZipCode": "3322", @@ -7406,15 +7127,6 @@ "DateOfValidity": "1996-09-04T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 581, - "ZipCode": "3800", - "ZipCodeAddOn": "01", - "MunicipalityName": "Interlaken Bönigstrasse", - "Canton": "BE", - "DateOfValidity": "2016-05-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 581, "ZipCode": "3802", @@ -7919,6 +7631,15 @@ "DateOfValidity": "1988-01-01T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 627, + "ZipCode": "3076", + "ZipCodeAddOn": "02", + "MunicipalityName": "Worb Bollstrasse", + "Canton": "BE", + "DateOfValidity": "1993-11-10T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 627, "ZipCode": "3076", @@ -8567,15 +8288,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 743, - "ZipCode": "2560", - "ZipCodeAddOn": "02", - "MunicipalityName": "Nidau Weyermattstrasse", - "Canton": "BE", - "DateOfValidity": "2019-04-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 744, "ZipCode": "2552", @@ -8855,15 +8567,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 769, - "ZipCode": "3752", - "ZipCodeAddOn": "02", - "MunicipalityName": "Wimmis Herrenmattestr", - "Canton": "BE", - "DateOfValidity": "2020-10-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 770, "ZipCode": "3631", @@ -9134,15 +8837,6 @@ "DateOfValidity": "1996-09-04T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 843, - "ZipCode": "3780", - "ZipCodeAddOn": "60", - "MunicipalityName": "Gstaad Zustellung", - "Canton": "BE", - "DateOfValidity": "2004-10-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 843, "ZipCode": "3792", @@ -9296,15 +8990,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 861, - "ZipCode": "3123", - "ZipCodeAddOn": "02", - "MunicipalityName": "Belp Rubigenstrasse", - "Canton": "BE", - "DateOfValidity": "2022-03-28T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 861, "ZipCode": "3123", @@ -9998,15 +9683,6 @@ "DateOfValidity": "2003-09-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 939, - "ZipCode": "3613", - "ZipCodeAddOn": "03", - "MunicipalityName": "Steffisburg alte Bernstr", - "Canton": "BE", - "DateOfValidity": "2016-07-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 940, "ZipCode": "3623", @@ -10043,15 +9719,6 @@ "DateOfValidity": "1989-06-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 942, - "ZipCode": "3645", - "ZipCodeAddOn": "03", - "MunicipalityName": "Gwatt (Thun) Lohnerstrasse", - "Canton": "BE", - "DateOfValidity": "2018-07-13T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 942, "ZipCode": "3600", @@ -10088,15 +9755,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 942, - "ZipCode": "3605", - "ZipCodeAddOn": "00", - "MunicipalityName": "Thun", - "Canton": "BE", - "DateOfValidity": "1986-05-21T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 942, "ZipCode": "3607", @@ -10133,15 +9791,6 @@ "DateOfValidity": "1999-03-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 942, - "ZipCode": "3600", - "ZipCodeAddOn": "03", - "MunicipalityName": "Thun Aarestrasse", - "Canton": "BE", - "DateOfValidity": "2019-05-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 942, "ZipCode": "3601", @@ -10155,7 +9804,7 @@ "BfsCode": 942, "ZipCode": "3600", "ZipCodeAddOn": "70", - "MunicipalityName": "Thun Dist Ba", + "MunicipalityName": "Thun Zustellung Pakete", "Canton": "BE", "DateOfValidity": "1998-11-23T00:00:00", "LanguageCode": 0 @@ -10529,15 +10178,6 @@ "DateOfValidity": "1996-09-12T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 979, - "ZipCode": "3360", - "ZipCodeAddOn": "02", - "MunicipalityName": "Herzogenbuchsee Lagerstr", - "Canton": "BE", - "DateOfValidity": "2020-02-07T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 979, "ZipCode": "3360", @@ -10709,15 +10349,6 @@ "DateOfValidity": "2003-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 992, - "ZipCode": "3380", - "ZipCodeAddOn": "03", - "MunicipalityName": "Wangen a.d.A. Bahnhofallee", - "Canton": "BE", - "DateOfValidity": "2020-12-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 992, "ZipCode": "3380", @@ -10970,15 +10601,6 @@ "DateOfValidity": "2003-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1024, - "ZipCode": "6032", - "ZipCodeAddOn": "02", - "MunicipalityName": "Emmen Seetalstrasse", - "Canton": "LU", - "DateOfValidity": "2016-09-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1024, "ZipCode": "6020", @@ -11150,15 +10772,6 @@ "DateOfValidity": "1994-06-02T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1031, - "ZipCode": "6280", - "ZipCodeAddOn": "03", - "MunicipalityName": "Hochdorf Hauptstrasse", - "Canton": "LU", - "DateOfValidity": "2019-04-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1031, "ZipCode": "6280", @@ -11253,7 +10866,7 @@ "BfsCode": 1040, "ZipCode": "6023", "ZipCodeAddOn": "70", - "MunicipalityName": "Rothenburg DIST BA", + "MunicipalityName": "Rothenburg Zust Pakete", "Canton": "LU", "DateOfValidity": "2011-05-01T00:00:00", "LanguageCode": 0 @@ -11289,20 +10902,11 @@ "BfsCode": 1051, "ZipCode": "6043", "ZipCodeAddOn": "70", - "MunicipalityName": "Adligenswil Dist Hub", + "MunicipalityName": "Adligenswil Zust Pakete", "Canton": "LU", "DateOfValidity": "2021-07-23T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1051, - "ZipCode": "6043", - "ZipCodeAddOn": "03", - "MunicipalityName": "Adligenswil Ebikonerstrasse", - "Canton": "LU", - "DateOfValidity": "2019-08-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1051, "ZipCode": "6043", @@ -11321,15 +10925,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1052, - "ZipCode": "6033", - "ZipCodeAddOn": "02", - "MunicipalityName": "Buchrain Schiltwald", - "Canton": "LU", - "DateOfValidity": "2018-12-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1052, "ZipCode": "6035", @@ -11375,15 +10970,6 @@ "DateOfValidity": "2017-11-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1054, - "ZipCode": "6030", - "ZipCodeAddOn": "02", - "MunicipalityName": "Ebikon Riedmattstrasse", - "Canton": "LU", - "DateOfValidity": "2020-10-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1054, "ZipCode": "6030", @@ -11429,15 +11015,6 @@ "DateOfValidity": "1996-04-10T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1058, - "ZipCode": "6048", - "ZipCodeAddOn": "01", - "MunicipalityName": "Horw Gemeindehausplatz", - "Canton": "LU", - "DateOfValidity": "2017-11-09T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1058, "ZipCode": "6047", @@ -11540,10 +11117,10 @@ { "BfsCode": 1059, "ZipCode": "6010", - "ZipCodeAddOn": "05", - "MunicipalityName": "Kriens Luzernerstrasse", + "ZipCodeAddOn": "70", + "MunicipalityName": "Kriens Zustellung Pakete", "Canton": "LU", - "DateOfValidity": "2017-11-01T00:00:00", + "DateOfValidity": "2022-11-07T00:00:00", "LanguageCode": 0 }, { @@ -11717,33 +11294,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1061, - "ZipCode": "6000", - "ZipCodeAddOn": "17", - "MunicipalityName": "Luzern Altstadt", - "Canton": "LU", - "DateOfValidity": "2012-08-01T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 1061, - "ZipCode": "6005", - "ZipCodeAddOn": "03", - "MunicipalityName": "Luzern Frohburgstrasse", - "Canton": "LU", - "DateOfValidity": "2021-11-01T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 1061, - "ZipCode": "6003", - "ZipCodeAddOn": "02", - "MunicipalityName": "Luzern Hirschengraben", - "Canton": "LU", - "DateOfValidity": "2021-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1061, "ZipCode": "6000", @@ -11762,15 +11312,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1061, - "ZipCode": "6014", - "ZipCodeAddOn": "01", - "MunicipalityName": "Luzern Littau Fanghöfli", - "Canton": "LU", - "DateOfValidity": "2017-12-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1061, "ZipCode": "6000", @@ -11843,15 +11384,6 @@ "DateOfValidity": "1999-12-09T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1062, - "ZipCode": "6102", - "ZipCodeAddOn": "02", - "MunicipalityName": "Malters Luzernstrasse", - "Canton": "LU", - "DateOfValidity": "2020-08-27T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1062, "ZipCode": "6102", @@ -12023,15 +11555,6 @@ "DateOfValidity": "1995-12-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1082, - "ZipCode": "6233", - "ZipCodeAddOn": "01", - "MunicipalityName": "Büron Industriestrasse", - "Canton": "LU", - "DateOfValidity": "2018-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1083, "ZipCode": "6018", @@ -12131,15 +11654,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1093, - "ZipCode": "6203", - "ZipCodeAddOn": "01", - "MunicipalityName": "Sempach Bahnhof GK", - "Canton": "LU", - "DateOfValidity": "2014-04-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1093, "ZipCode": "6203", @@ -12266,24 +11780,6 @@ "DateOfValidity": "1995-12-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1103, - "ZipCode": "6210", - "ZipCodeAddOn": "01", - "MunicipalityName": "Sursee Schellenrain", - "Canton": "LU", - "DateOfValidity": "2019-12-02T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 1103, - "ZipCode": "6210", - "ZipCodeAddOn": "02", - "MunicipalityName": "Sursee Wassergrabe", - "Canton": "LU", - "DateOfValidity": "2022-03-16T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1103, "ZipCode": "6210", @@ -12581,15 +12077,6 @@ "DateOfValidity": "1998-05-25T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1140, - "ZipCode": "6260", - "ZipCodeAddOn": "05", - "MunicipalityName": "Mehlsecken Dörflimatte", - "Canton": "LU", - "DateOfValidity": "2020-08-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1140, "ZipCode": "6260", @@ -12671,15 +12158,6 @@ "DateOfValidity": "1995-12-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1146, - "ZipCode": "6242", - "ZipCodeAddOn": "03", - "MunicipalityName": "Wauwil Dorfstrasse", - "Canton": "LU", - "DateOfValidity": "2015-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1147, "ZipCode": "6260", @@ -12764,28 +12242,19 @@ { "BfsCode": 1151, "ZipCode": "6130", - "ZipCodeAddOn": "70", - "MunicipalityName": "Willisau Dist Hub", - "Canton": "LU", - "DateOfValidity": "2019-02-17T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 1151, - "ZipCode": "6130", - "ZipCodeAddOn": "01", - "MunicipalityName": "Willisau Rotmatt", + "ZipCodeAddOn": "60", + "MunicipalityName": "Willisau Zustellung", "Canton": "LU", - "DateOfValidity": "2014-11-01T00:00:00", + "DateOfValidity": "2003-09-01T00:00:00", "LanguageCode": 0 }, { "BfsCode": 1151, "ZipCode": "6130", - "ZipCodeAddOn": "60", - "MunicipalityName": "Willisau Zustellung", + "ZipCodeAddOn": "70", + "MunicipalityName": "Willisau Zustellung Pakete", "Canton": "LU", - "DateOfValidity": "2003-09-01T00:00:00", + "DateOfValidity": "2019-02-17T00:00:00", "LanguageCode": 0 }, { @@ -13076,15 +12545,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1301, - "ZipCode": "8840", - "ZipCodeAddOn": "03", - "MunicipalityName": "Einsiedeln Bahnhofplatz", - "Canton": "SZ", - "DateOfValidity": "2021-04-13T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1301, "ZipCode": "8840", @@ -13202,15 +12662,6 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1322, - "ZipCode": "8808", - "ZipCodeAddOn": "02", - "MunicipalityName": "Pfäffikon SZ Poststrasse", - "Canton": "SZ", - "DateOfValidity": "2022-01-06T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1322, "ZipCode": "8808", @@ -13251,20 +12702,11 @@ "BfsCode": 1331, "ZipCode": "6403", "ZipCodeAddOn": "70", - "MunicipalityName": "Küssnacht a.R. Dist Hub", + "MunicipalityName": "Küssnacht a. R. Zust Pakete", "Canton": "SZ", "DateOfValidity": "2016-09-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1331, - "ZipCode": "6403", - "ZipCodeAddOn": "01", - "MunicipalityName": "Küssnacht a.R. Zugstrasse", - "Canton": "SZ", - "DateOfValidity": "2017-11-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1331, "ZipCode": "6403", @@ -13706,15 +13148,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1401, - "ZipCode": "6055", - "ZipCodeAddOn": "02", - "MunicipalityName": "Alpnach Dorf Brünigstrasse", - "Canton": "OW", - "DateOfValidity": "2019-10-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1401, "ZipCode": "6055", @@ -14084,15 +13517,6 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1509, - "ZipCode": "6370", - "ZipCodeAddOn": "01", - "MunicipalityName": "Stans Bahnhofplatz", - "Canton": "NW", - "DateOfValidity": "2017-12-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1509, "ZipCode": "6370", @@ -14221,20 +13645,20 @@ }, { "BfsCode": 1630, - "ZipCode": "8867", - "ZipCodeAddOn": "70", - "MunicipalityName": "Niederurnen Dist Hub", + "ZipCode": "8870", + "ZipCodeAddOn": "00", + "MunicipalityName": "Niederurnen PF", "Canton": "GL", - "DateOfValidity": "1999-04-06T00:00:00", + "DateOfValidity": "2021-10-18T00:00:00", "LanguageCode": 0 }, { "BfsCode": 1630, - "ZipCode": "8870", - "ZipCodeAddOn": "00", - "MunicipalityName": "Niederurnen PF", + "ZipCode": "8867", + "ZipCodeAddOn": "70", + "MunicipalityName": "Niederurnen Zust Pakete", "Canton": "GL", - "DateOfValidity": "2021-10-18T00:00:00", + "DateOfValidity": "1999-04-06T00:00:00", "LanguageCode": 0 }, { @@ -14529,20 +13953,11 @@ "BfsCode": 1701, "ZipCode": "6346", "ZipCodeAddOn": "70", - "MunicipalityName": "Baar 3 Dist Ba", + "MunicipalityName": "Baar 3 Zustellung Pakete", "Canton": "ZG", "DateOfValidity": "2006-08-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1701, - "ZipCode": "6340", - "ZipCodeAddOn": "03", - "MunicipalityName": "Baar Dorfstrasse", - "Canton": "ZG", - "DateOfValidity": "2019-02-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1702, "ZipCode": "6330", @@ -14552,15 +13967,6 @@ "DateOfValidity": "1996-03-04T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 1702, - "ZipCode": "6330", - "ZipCodeAddOn": "03", - "MunicipalityName": "Cham Hinterbergstrasse", - "Canton": "ZG", - "DateOfValidity": "2017-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 1702, "ZipCode": "6332", @@ -14871,7 +14277,7 @@ "BfsCode": 2016, "ZipCode": "1532", "ZipCodeAddOn": "70", - "MunicipalityName": "Fétigny Dist Fil", + "MunicipalityName": "Fétigny Distribution colis", "Canton": "FR", "DateOfValidity": "2008-10-01T00:00:00", "LanguageCode": 0 @@ -15416,15 +14822,6 @@ "DateOfValidity": "2002-11-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2096, - "ZipCode": "1680", - "ZipCodeAddOn": "03", - "MunicipalityName": "Romont FR Rte Anc. Ferme", - "Canton": "FR", - "DateOfValidity": "2022-08-22T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2097, "ZipCode": "1675", @@ -15875,15 +15272,6 @@ "DateOfValidity": "2020-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2125, - "ZipCode": "1630", - "ZipCodeAddOn": "03", - "MunicipalityName": "Bulle rue du Moulin", - "Canton": "FR", - "DateOfValidity": "2019-07-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2125, "ZipCode": "1635", @@ -16406,15 +15794,6 @@ "DateOfValidity": "1996-09-05T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2196, - "ZipCode": "1700", - "ZipCodeAddOn": "75", - "MunicipalityName": "Fribourg Caserne", - "Canton": "FR", - "DateOfValidity": "2003-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2196, "ZipCode": "1700", @@ -16424,15 +15803,6 @@ "DateOfValidity": "2007-03-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2196, - "ZipCode": "1700", - "ZipCodeAddOn": "02", - "MunicipalityName": "Fribourg Route de la Glâne", - "Canton": "FR", - "DateOfValidity": "2022-09-12T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2197, "ZipCode": "1762", @@ -16446,7 +15816,7 @@ "BfsCode": 2197, "ZipCode": "1762", "ZipCodeAddOn": "70", - "MunicipalityName": "Givisiez Dist Ba", + "MunicipalityName": "Givisiez Distribution", "Canton": "FR", "DateOfValidity": "1998-11-23T00:00:00", "LanguageCode": 0 @@ -16496,15 +15866,6 @@ "DateOfValidity": "1996-09-05T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2206, - "ZipCode": "1723", - "ZipCodeAddOn": "05", - "MunicipalityName": "Marly Route du Châtelet", - "Canton": "FR", - "DateOfValidity": "2021-06-10T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2208, "ZipCode": "1753", @@ -17720,15 +17081,6 @@ "DateOfValidity": "2003-07-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2402, - "ZipCode": "4620", - "ZipCodeAddOn": "70", - "MunicipalityName": "Härkingen Dist Ba", - "Canton": "SO", - "DateOfValidity": "1998-11-23T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2402, "ZipCode": "4620", @@ -17756,6 +17108,15 @@ "DateOfValidity": "2007-10-01T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 2402, + "ZipCode": "4620", + "ZipCodeAddOn": "70", + "MunicipalityName": "Härkingen Zustellung", + "Canton": "SO", + "DateOfValidity": "1998-11-23T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 2403, "ZipCode": "4703", @@ -17810,15 +17171,6 @@ "DateOfValidity": "2007-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2407, - "ZipCode": "4702", - "ZipCodeAddOn": "01", - "MunicipalityName": "Oensingen Solothurnstrasse", - "Canton": "SO", - "DateOfValidity": "2018-07-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2407, "ZipCode": "4702", @@ -18593,15 +17945,6 @@ "DateOfValidity": "1996-09-12T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2534, - "ZipCode": "4528", - "ZipCodeAddOn": "02", - "MunicipalityName": "Zuchwil Hauptstrasse", - "Canton": "SO", - "DateOfValidity": "2020-05-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2535, "ZipCode": "4558", @@ -18683,6 +18026,15 @@ "DateOfValidity": "1996-09-12T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 2543, + "ZipCode": "2544", + "ZipCodeAddOn": "60", + "MunicipalityName": "Bettlach Zustellung", + "Canton": "SO", + "DateOfValidity": "2022-11-21T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 2544, "ZipCode": "4532", @@ -18719,15 +18071,6 @@ "DateOfValidity": "1996-09-12T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2546, - "ZipCode": "2540", - "ZipCodeAddOn": "03", - "MunicipalityName": "Grenchen Maienstrasse", - "Canton": "SO", - "DateOfValidity": "2022-05-23T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2546, "ZipCode": "2540", @@ -18737,15 +18080,6 @@ "DateOfValidity": "1996-09-12T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2546, - "ZipCode": "2540", - "ZipCodeAddOn": "60", - "MunicipalityName": "Grenchen Zustellung", - "Canton": "SO", - "DateOfValidity": "2002-05-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2547, "ZipCode": "4524", @@ -19007,15 +18341,6 @@ "DateOfValidity": "2013-04-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2581, - "ZipCode": "4600", - "ZipCodeAddOn": "04", - "MunicipalityName": "Olten Bahnhofstrasse", - "Canton": "SO", - "DateOfValidity": "2020-08-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2581, "ZipCode": "4600", @@ -19142,15 +18467,6 @@ "DateOfValidity": "1999-04-12T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2601, - "ZipCode": "4503", - "ZipCodeAddOn": "02", - "MunicipalityName": "Solothurn Allmendstrasse", - "Canton": "SO", - "DateOfValidity": "2021-08-19T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2601, "ZipCode": "4501", @@ -19601,15 +18917,6 @@ "DateOfValidity": "1987-10-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2701, - "ZipCode": "4002", - "ZipCodeAddOn": "00", - "MunicipalityName": "Basel 2 Annahme", - "Canton": "BS", - "DateOfValidity": "2011-03-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2701, "ZipCode": "4000", @@ -19648,11 +18955,11 @@ }, { "BfsCode": 2701, - "ZipCode": "4009", - "ZipCodeAddOn": "02", - "MunicipalityName": "Basel Ahornstrasse", + "ZipCode": "4002", + "ZipCodeAddOn": "00", + "MunicipalityName": "Basel Bahnhof", "Canton": "BS", - "DateOfValidity": "2021-08-09T00:00:00", + "DateOfValidity": "2011-03-01T00:00:00", "LanguageCode": 0 }, { @@ -19673,15 +18980,6 @@ "DateOfValidity": "2021-07-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2701, - "ZipCode": "4000", - "ZipCodeAddOn": "70", - "MunicipalityName": "Basel Dist Ba", - "Canton": "BS", - "DateOfValidity": "1998-11-23T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2701, "ZipCode": "4001", @@ -19691,15 +18989,6 @@ "DateOfValidity": "2021-10-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2701, - "ZipCode": "4057", - "ZipCodeAddOn": "02", - "MunicipalityName": "Basel Hochbergerstrasse", - "Canton": "BS", - "DateOfValidity": "2021-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2701, "ZipCode": "4000", @@ -19713,7 +19002,7 @@ "BfsCode": 2701, "ZipCode": "4001", "ZipCodeAddOn": "03", - "MunicipalityName": "Basel Rüdengasse", + "MunicipalityName": "Basel Rümelinsplatz", "Canton": "BS", "DateOfValidity": "2021-11-15T00:00:00", "LanguageCode": 0 @@ -19808,6 +19097,15 @@ "DateOfValidity": "2022-03-07T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 2701, + "ZipCode": "4000", + "ZipCodeAddOn": "70", + "MunicipalityName": "Basel Zustellung Pakete", + "Canton": "BS", + "DateOfValidity": "1998-11-23T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 2702, "ZipCode": "4126", @@ -19835,15 +19133,6 @@ "DateOfValidity": "1993-09-09T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2703, - "ZipCode": "4125", - "ZipCodeAddOn": "03", - "MunicipalityName": "Riehen Bahnhofstrasse", - "Canton": "BS", - "DateOfValidity": "2021-07-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2703, "ZipCode": "4125", @@ -19862,15 +19151,6 @@ "DateOfValidity": "1997-08-18T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2761, - "ZipCode": "4147", - "ZipCodeAddOn": "02", - "MunicipalityName": "Aesch BL Hauptstrasse", - "Canton": "BL", - "DateOfValidity": "2019-09-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2762, "ZipCode": "4123", @@ -19889,15 +19169,6 @@ "DateOfValidity": "1993-12-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2762, - "ZipCode": "4123", - "ZipCodeAddOn": "04", - "MunicipalityName": "Allschwil Baslerstrasse", - "Canton": "BL", - "DateOfValidity": "2019-12-09T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2762, "ZipCode": "4123", @@ -19943,15 +19214,6 @@ "DateOfValidity": "2012-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2763, - "ZipCode": "4144", - "ZipCodeAddOn": "03", - "MunicipalityName": "Arlesheim Postplatz", - "Canton": "BL", - "DateOfValidity": "2018-11-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2764, "ZipCode": "4105", @@ -20006,15 +19268,6 @@ "DateOfValidity": "1993-12-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2766, - "ZipCode": "4127", - "ZipCodeAddOn": "03", - "MunicipalityName": "Birsfelden Hauptstrasse", - "Canton": "BL", - "DateOfValidity": "2018-11-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2767, "ZipCode": "4103", @@ -20078,24 +19331,6 @@ "DateOfValidity": "2003-06-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2769, - "ZipCode": "4142", - "ZipCodeAddOn": "04", - "MunicipalityName": "Münchenstein Stöckackerstr", - "Canton": "BL", - "DateOfValidity": "2021-08-01T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 2769, - "ZipCode": "4142", - "ZipCodeAddOn": "03", - "MunicipalityName": "Münchenstein Zollweiden", - "Canton": "BL", - "DateOfValidity": "1993-12-21T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2770, "ZipCode": "4132", @@ -20150,15 +19385,6 @@ "DateOfValidity": "1993-12-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2771, - "ZipCode": "4104", - "ZipCodeAddOn": "02", - "MunicipalityName": "Oberwil BL Konsumstrasse", - "Canton": "BL", - "DateOfValidity": "2021-08-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2771, "ZipCode": "4104", @@ -20195,15 +19421,6 @@ "DateOfValidity": "1993-12-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2773, - "ZipCode": "4153", - "ZipCodeAddOn": "03", - "MunicipalityName": "Reinach BL Austrasse", - "Canton": "BL", - "DateOfValidity": "2019-09-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2773, "ZipCode": "4153", @@ -20447,15 +19664,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2828, - "ZipCode": "4415", - "ZipCodeAddOn": "02", - "MunicipalityName": "Lausen Grammontstrasse", - "Canton": "BL", - "DateOfValidity": "2020-10-05T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2829, "ZipCode": "4410", @@ -20468,19 +19676,19 @@ { "BfsCode": 2829, "ZipCode": "4410", - "ZipCodeAddOn": "70", - "MunicipalityName": "Liestal Dist Ba", + "ZipCodeAddOn": "75", + "MunicipalityName": "Liestal Kaserne", "Canton": "BL", - "DateOfValidity": "1999-04-06T00:00:00", + "DateOfValidity": "2003-01-01T00:00:00", "LanguageCode": 0 }, { "BfsCode": 2829, "ZipCode": "4410", - "ZipCodeAddOn": "75", - "MunicipalityName": "Liestal Kaserne", + "ZipCodeAddOn": "70", + "MunicipalityName": "Liestal Zustellung", "Canton": "BL", - "DateOfValidity": "2003-01-01T00:00:00", + "DateOfValidity": "1999-04-06T00:00:00", "LanguageCode": 0 }, { @@ -20510,15 +19718,6 @@ "DateOfValidity": "1993-09-09T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2831, - "ZipCode": "4133", - "ZipCodeAddOn": "03", - "MunicipalityName": "Pratteln Bahnhofstrasse", - "Canton": "BL", - "DateOfValidity": "2020-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2831, "ZipCode": "4130", @@ -20539,11 +19738,11 @@ }, { "BfsCode": 2831, - "ZipCode": "4133", - "ZipCodeAddOn": "04", - "MunicipalityName": "Pratteln Hohenrainstrasse", + "ZipCode": "4135", + "ZipCodeAddOn": "00", + "MunicipalityName": "Pratteln INT", "Canton": "BL", - "DateOfValidity": "2022-08-22T00:00:00", + "DateOfValidity": "2023-06-19T00:00:00", "LanguageCode": 0 }, { @@ -20555,15 +19754,6 @@ "DateOfValidity": "2013-07-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2831, - "ZipCode": "4133", - "ZipCodeAddOn": "70", - "MunicipalityName": "Pratteln RPZ", - "Canton": "BL", - "DateOfValidity": "2020-05-28T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2831, "ZipCode": "4134", @@ -20582,6 +19772,15 @@ "DateOfValidity": "2007-09-01T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 2831, + "ZipCode": "4133", + "ZipCodeAddOn": "70", + "MunicipalityName": "Pratteln Zustellung", + "Canton": "BL", + "DateOfValidity": "2020-05-28T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 2832, "ZipCode": "4433", @@ -20807,15 +20006,6 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2861, - "ZipCode": "4450", - "ZipCodeAddOn": "01", - "MunicipalityName": "Sissach Postgasse", - "Canton": "BL", - "DateOfValidity": "2018-02-12T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2862, "ZipCode": "4492", @@ -21212,15 +20402,6 @@ "DateOfValidity": "1996-03-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2937, - "ZipCode": "8212", - "ZipCodeAddOn": "03", - "MunicipalityName": "Neuhausen Rundbuck", - "Canton": "SH", - "DateOfValidity": "2011-11-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2938, "ZipCode": "8455", @@ -21331,20 +20512,20 @@ }, { "BfsCode": 2939, - "ZipCode": "8210", - "ZipCodeAddOn": "70", - "MunicipalityName": "Schaffhausen Dist Ba", + "ZipCode": "8200", + "ZipCodeAddOn": "48", + "MunicipalityName": "Schaffhausen PF", "Canton": "SH", - "DateOfValidity": "2001-07-01T00:00:00", + "DateOfValidity": "2008-10-01T00:00:00", "LanguageCode": 0 }, { "BfsCode": 2939, - "ZipCode": "8200", - "ZipCodeAddOn": "48", - "MunicipalityName": "Schaffhausen PF", + "ZipCode": "8210", + "ZipCodeAddOn": "70", + "MunicipalityName": "Schaffhausen Zust Pakete", "Canton": "SH", - "DateOfValidity": "2008-10-01T00:00:00", + "DateOfValidity": "2001-07-01T00:00:00", "LanguageCode": 0 }, { @@ -21446,15 +20627,6 @@ "DateOfValidity": "1997-01-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 2971, - "ZipCode": "8215", - "ZipCodeAddOn": "02", - "MunicipalityName": "Hallau Neunkircherstrasse", - "Canton": "SH", - "DateOfValidity": "2021-08-19T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 2972, "ZipCode": "8216", @@ -21752,15 +20924,6 @@ "DateOfValidity": "1992-11-16T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 3037, - "ZipCode": "9428", - "ZipCodeAddOn": "03", - "MunicipalityName": "Platz AR", - "Canton": "AR", - "DateOfValidity": "1996-06-14T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 3037, "ZipCode": "9428", @@ -22112,15 +21275,6 @@ "DateOfValidity": "1999-04-08T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 3203, - "ZipCode": "9000", - "ZipCodeAddOn": "70", - "MunicipalityName": "St. Gallen Dist Ba", - "Canton": "SG", - "DateOfValidity": "1998-11-23T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 3203, "ZipCode": "9000", @@ -22193,6 +21347,15 @@ "DateOfValidity": "1993-07-21T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 3203, + "ZipCode": "9000", + "ZipCodeAddOn": "70", + "MunicipalityName": "St. Gallen Zust Pakete", + "Canton": "SG", + "DateOfValidity": "1998-11-23T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 3204, "ZipCode": "9300", @@ -22358,19 +21521,19 @@ { "BfsCode": 3233, "ZipCode": "9442", - "ZipCodeAddOn": "70", - "MunicipalityName": "Berneck Dist Ba", + "ZipCodeAddOn": "60", + "MunicipalityName": "Berneck Zustellung", "Canton": "SG", - "DateOfValidity": "1998-11-23T00:00:00", + "DateOfValidity": "2015-09-01T00:00:00", "LanguageCode": 0 }, { "BfsCode": 3233, "ZipCode": "9442", - "ZipCodeAddOn": "60", - "MunicipalityName": "Berneck Zustellung", + "ZipCodeAddOn": "70", + "MunicipalityName": "Berneck Zustellung Pakete", "Canton": "SG", - "DateOfValidity": "2015-09-01T00:00:00", + "DateOfValidity": "1998-11-23T00:00:00", "LanguageCode": 0 }, { @@ -22418,15 +21581,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 3237, - "ZipCode": "9423", - "ZipCodeAddOn": "03", - "MunicipalityName": "Altenrhein Dorfstrasse", - "Canton": "SG", - "DateOfValidity": "2015-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 3237, "ZipCode": "9422", @@ -22454,15 +21608,6 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 3238, - "ZipCode": "9443", - "ZipCodeAddOn": "01", - "MunicipalityName": "Widnau Industriestrasse", - "Canton": "SG", - "DateOfValidity": "2017-03-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 3251, "ZipCode": "9450", @@ -22589,15 +21734,6 @@ "DateOfValidity": "1994-03-08T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 3256, - "ZipCode": "9464", - "ZipCodeAddOn": "03", - "MunicipalityName": "Rüthi (Rheintal) Staatstr", - "Canton": "SG", - "DateOfValidity": "2015-06-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 3271, "ZipCode": "9470", @@ -22670,15 +21806,6 @@ "DateOfValidity": "1999-01-27T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 3273, - "ZipCode": "9472", - "ZipCodeAddOn": "03", - "MunicipalityName": "Grabs Staatsstrasse", - "Canton": "SG", - "DateOfValidity": "2020-08-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 3273, "ZipCode": "9472", @@ -23102,6 +22229,15 @@ "DateOfValidity": "1993-02-02T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 3296, + "ZipCode": "7320", + "ZipCodeAddOn": "02", + "MunicipalityName": "Sargans Langgrabenstrasse", + "Canton": "SG", + "DateOfValidity": "2022-12-05T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 3296, "ZipCode": "7320", @@ -23273,15 +22409,6 @@ "DateOfValidity": "1992-05-19T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 3339, - "ZipCode": "8730", - "ZipCodeAddOn": "01", - "MunicipalityName": "Uznach Zürcherstrasse", - "Canton": "SG", - "DateOfValidity": "2017-06-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 3339, "ZipCode": "8730", @@ -23309,15 +22436,6 @@ "DateOfValidity": "1996-06-10T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 3340, - "ZipCode": "8645", - "ZipCodeAddOn": "01", - "MunicipalityName": "Jona Buech Gewerbe", - "Canton": "SG", - "DateOfValidity": "2011-02-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 3340, "ZipCode": "8640", @@ -23552,24 +22670,6 @@ "DateOfValidity": "1996-03-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 3372, - "ZipCode": "9633", - "ZipCodeAddOn": "02", - "MunicipalityName": "Bächli (Hemberg)", - "Canton": "SG", - "DateOfValidity": "1998-12-14T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 3372, - "ZipCode": "9633", - "ZipCodeAddOn": "00", - "MunicipalityName": "Hemberg", - "Canton": "SG", - "DateOfValidity": "1986-05-21T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 3374, "ZipCode": "9620", @@ -23579,87 +22679,6 @@ "DateOfValidity": "1996-03-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 3375, - "ZipCode": "9126", - "ZipCodeAddOn": "00", - "MunicipalityName": "Necker", - "Canton": "SG", - "DateOfValidity": "1986-05-21T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 3375, - "ZipCode": "9621", - "ZipCodeAddOn": "00", - "MunicipalityName": "Oberhelfenschwil", - "Canton": "SG", - "DateOfValidity": "1991-10-25T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 3378, - "ZipCode": "9125", - "ZipCodeAddOn": "00", - "MunicipalityName": "Brunnadern", - "Canton": "SG", - "DateOfValidity": "1991-03-19T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 3378, - "ZipCode": "9115", - "ZipCodeAddOn": "00", - "MunicipalityName": "Dicken", - "Canton": "SG", - "DateOfValidity": "1986-05-21T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 3378, - "ZipCode": "9122", - "ZipCodeAddOn": "02", - "MunicipalityName": "Ebersol", - "Canton": "SG", - "DateOfValidity": "1989-09-01T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 3378, - "ZipCode": "9114", - "ZipCodeAddOn": "00", - "MunicipalityName": "Hoffeld", - "Canton": "SG", - "DateOfValidity": "1986-05-21T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 3378, - "ZipCode": "9122", - "ZipCodeAddOn": "00", - "MunicipalityName": "Mogelsberg", - "Canton": "SG", - "DateOfValidity": "1989-09-01T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 3378, - "ZipCode": "9123", - "ZipCodeAddOn": "00", - "MunicipalityName": "Nassen", - "Canton": "SG", - "DateOfValidity": "2000-06-30T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 3378, - "ZipCode": "9127", - "ZipCodeAddOn": "00", - "MunicipalityName": "St. Peterzell", - "Canton": "SG", - "DateOfValidity": "1999-10-21T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 3379, "ZipCode": "9622", @@ -23849,6 +22868,105 @@ "DateOfValidity": "1996-03-07T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 3396, + "ZipCode": "9633", + "ZipCodeAddOn": "02", + "MunicipalityName": "Bächli (Hemberg)", + "Canton": "SG", + "DateOfValidity": "1998-12-14T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 3396, + "ZipCode": "9125", + "ZipCodeAddOn": "00", + "MunicipalityName": "Brunnadern", + "Canton": "SG", + "DateOfValidity": "1991-03-19T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 3396, + "ZipCode": "9115", + "ZipCodeAddOn": "00", + "MunicipalityName": "Dicken", + "Canton": "SG", + "DateOfValidity": "1986-05-21T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 3396, + "ZipCode": "9122", + "ZipCodeAddOn": "02", + "MunicipalityName": "Ebersol", + "Canton": "SG", + "DateOfValidity": "1989-09-01T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 3396, + "ZipCode": "9633", + "ZipCodeAddOn": "00", + "MunicipalityName": "Hemberg", + "Canton": "SG", + "DateOfValidity": "1986-05-21T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 3396, + "ZipCode": "9114", + "ZipCodeAddOn": "00", + "MunicipalityName": "Hoffeld", + "Canton": "SG", + "DateOfValidity": "1986-05-21T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 3396, + "ZipCode": "9122", + "ZipCodeAddOn": "00", + "MunicipalityName": "Mogelsberg", + "Canton": "SG", + "DateOfValidity": "1989-09-01T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 3396, + "ZipCode": "9123", + "ZipCodeAddOn": "00", + "MunicipalityName": "Nassen", + "Canton": "SG", + "DateOfValidity": "2000-06-30T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 3396, + "ZipCode": "9126", + "ZipCodeAddOn": "00", + "MunicipalityName": "Necker", + "Canton": "SG", + "DateOfValidity": "1986-05-21T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 3396, + "ZipCode": "9621", + "ZipCodeAddOn": "00", + "MunicipalityName": "Oberhelfenschwil", + "Canton": "SG", + "DateOfValidity": "1991-10-25T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 3396, + "ZipCode": "9127", + "ZipCodeAddOn": "00", + "MunicipalityName": "St. Peterzell", + "Canton": "SG", + "DateOfValidity": "1999-10-21T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 3401, "ZipCode": "9113", @@ -24083,15 +23201,6 @@ "DateOfValidity": "1996-03-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 3424, - "ZipCode": "9245", - "ZipCodeAddOn": "03", - "MunicipalityName": "Oberbüren Unterdorf", - "Canton": "SG", - "DateOfValidity": "2021-08-19T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 3424, "ZipCode": "9245", @@ -24119,15 +23228,6 @@ "DateOfValidity": "1996-03-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 3426, - "ZipCode": "9524", - "ZipCodeAddOn": "03", - "MunicipalityName": "Zuzwil SG Gewerbestrasse", - "Canton": "SG", - "DateOfValidity": "2015-10-19T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 3427, "ZipCode": "9552", @@ -24173,15 +23273,6 @@ "DateOfValidity": "1998-03-20T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 3427, - "ZipCode": "9500", - "ZipCodeAddOn": "03", - "MunicipalityName": "Wil SG Hubstrasse", - "Canton": "SG", - "DateOfValidity": "2019-04-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 3441, "ZipCode": "9204", @@ -24290,6 +23381,15 @@ "DateOfValidity": "1997-02-24T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 3443, + "ZipCode": "9200", + "ZipCodeAddOn": "70", + "MunicipalityName": "Gossau SG Zustellung Pakete", + "Canton": "SG", + "DateOfValidity": "2023-02-06T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 3444, "ZipCode": "9304", @@ -26306,15 +25406,6 @@ "DateOfValidity": "1990-08-03T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 3851, - "ZipCode": "7270", - "ZipCodeAddOn": "03", - "MunicipalityName": "Davos Platz Talstrasse", - "Canton": "GR", - "DateOfValidity": "2021-07-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 3851, "ZipCode": "7494", @@ -26804,8 +25895,8 @@ { "BfsCode": 3946, "ZipCode": "7200", - "ZipCodeAddOn": "70", - "MunicipalityName": "Untervaz RPZ", + "ZipCodeAddOn": "00", + "MunicipalityName": "Untervaz Sortierung", "Canton": "GR", "DateOfValidity": "2019-06-03T00:00:00", "LanguageCode": 0 @@ -26813,8 +25904,8 @@ { "BfsCode": 3946, "ZipCode": "7200", - "ZipCodeAddOn": "00", - "MunicipalityName": "Untervaz Sortierung", + "ZipCodeAddOn": "70", + "MunicipalityName": "Untervaz Zustellung", "Canton": "GR", "DateOfValidity": "2019-06-03T00:00:00", "LanguageCode": 0 @@ -27287,15 +26378,6 @@ "DateOfValidity": "1998-03-20T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4001, - "ZipCode": "5000", - "ZipCodeAddOn": "60", - "MunicipalityName": "Aarau 1 Zustellung", - "Canton": "AG", - "DateOfValidity": "1999-03-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4001, "ZipCode": "5000", @@ -27341,15 +26423,6 @@ "DateOfValidity": "2015-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4001, - "ZipCode": "5004", - "ZipCodeAddOn": "03", - "MunicipalityName": "Aarau Tellistrasse", - "Canton": "AG", - "DateOfValidity": "2015-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4002, "ZipCode": "5023", @@ -27388,18 +26461,27 @@ }, { "BfsCode": 4003, - "ZipCode": "5033", - "ZipCodeAddOn": "70", - "MunicipalityName": "Buchs AG RPZ", + "ZipCode": "5030", + "ZipCodeAddOn": "00", + "MunicipalityName": "Buchs AG Sortierung", "Canton": "AG", "DateOfValidity": "2021-09-01T00:00:00", "LanguageCode": 0 }, { "BfsCode": 4003, - "ZipCode": "5030", - "ZipCodeAddOn": "00", - "MunicipalityName": "Buchs AG Sortierung", + "ZipCode": "5033", + "ZipCodeAddOn": "60", + "MunicipalityName": "Buchs AG Zustellung", + "Canton": "AG", + "DateOfValidity": "2003-10-01T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 4003, + "ZipCode": "5033", + "ZipCodeAddOn": "70", + "MunicipalityName": "Buchs AG Zustellung Pakete", "Canton": "AG", "DateOfValidity": "2021-09-01T00:00:00", "LanguageCode": 0 @@ -27602,15 +26684,6 @@ "DateOfValidity": "1995-12-18T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4021, - "ZipCode": "5405", - "ZipCodeAddOn": "01", - "MunicipalityName": "Baden Täfern Gewerbe", - "Canton": "AG", - "DateOfValidity": "2011-02-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4021, "ZipCode": "5405", @@ -27728,15 +26801,6 @@ "DateOfValidity": "1995-12-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4032, - "ZipCode": "5506", - "ZipCodeAddOn": "70", - "MunicipalityName": "Mägenwil Dist Ba", - "Canton": "AG", - "DateOfValidity": "2009-05-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4032, "ZipCode": "5506", @@ -27773,6 +26837,15 @@ "DateOfValidity": "2013-07-01T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 4032, + "ZipCode": "5506", + "ZipCodeAddOn": "70", + "MunicipalityName": "Mägenwil Zustellung Pakete", + "Canton": "AG", + "DateOfValidity": "2009-05-01T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 4033, "ZipCode": "5507", @@ -27962,15 +27035,6 @@ "DateOfValidity": "1998-10-27T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4045, - "ZipCode": "5430", - "ZipCodeAddOn": "04", - "MunicipalityName": "Wettingen Seminarstrasse", - "Canton": "AG", - "DateOfValidity": "2020-01-11T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4046, "ZipCode": "5512", @@ -27989,15 +27053,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4047, - "ZipCode": "5303", - "ZipCodeAddOn": "01", - "MunicipalityName": "Würenlingen Dorfstrasse", - "Canton": "AG", - "DateOfValidity": "2018-08-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4048, "ZipCode": "8109", @@ -28457,15 +27512,6 @@ "DateOfValidity": "2004-12-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4095, - "ZipCode": "5200", - "ZipCodeAddOn": "04", - "MunicipalityName": "Brugg AG Bahnhofstrasse", - "Canton": "AG", - "DateOfValidity": "2018-08-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4095, "ZipCode": "5200", @@ -28700,15 +27746,6 @@ "DateOfValidity": "1995-12-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4133, - "ZipCode": "5736", - "ZipCodeAddOn": "00", - "MunicipalityName": "Burg AG", - "Canton": "AG", - "DateOfValidity": "2001-09-06T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4134, "ZipCode": "5724", @@ -28756,20 +27793,20 @@ }, { "BfsCode": 4139, - "ZipCode": "5737", + "ZipCode": "5736", "ZipCodeAddOn": "00", - "MunicipalityName": "Menziken", + "MunicipalityName": "Burg AG", "Canton": "AG", - "DateOfValidity": "1995-12-07T00:00:00", + "DateOfValidity": "2001-09-06T00:00:00", "LanguageCode": 0 }, { "BfsCode": 4139, "ZipCode": "5737", - "ZipCodeAddOn": "02", - "MunicipalityName": "Menziken Gässli", + "ZipCodeAddOn": "00", + "MunicipalityName": "Menziken", "Canton": "AG", - "DateOfValidity": "2022-10-17T00:00:00", + "DateOfValidity": "1995-12-07T00:00:00", "LanguageCode": 0 }, { @@ -28790,15 +27827,6 @@ "DateOfValidity": "1995-12-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4141, - "ZipCode": "5734", - "ZipCodeAddOn": "01", - "MunicipalityName": "Reinach AG Postplatz", - "Canton": "AG", - "DateOfValidity": "2017-11-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4141, "ZipCode": "5734", @@ -28907,15 +27935,6 @@ "DateOfValidity": "1995-12-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4163, - "ZipCode": "5070", - "ZipCodeAddOn": "02", - "MunicipalityName": "Frick Widenplatz", - "Canton": "AG", - "DateOfValidity": "2019-10-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4163, "ZipCode": "5070", @@ -28943,15 +27962,6 @@ "DateOfValidity": "1995-12-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4166, - "ZipCode": "5027", - "ZipCodeAddOn": "00", - "MunicipalityName": "Herznach", - "Canton": "AG", - "DateOfValidity": "1995-12-07T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4169, "ZipCode": "5083", @@ -29042,15 +28052,6 @@ "DateOfValidity": "1995-12-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4179, - "ZipCode": "5028", - "ZipCodeAddOn": "00", - "MunicipalityName": "Ueken", - "Canton": "AG", - "DateOfValidity": "1995-12-18T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4181, "ZipCode": "5064", @@ -29159,6 +28160,24 @@ "DateOfValidity": "1996-12-16T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 4186, + "ZipCode": "5027", + "ZipCodeAddOn": "00", + "MunicipalityName": "Herznach", + "Canton": "AG", + "DateOfValidity": "1995-12-07T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 4186, + "ZipCode": "5028", + "ZipCodeAddOn": "00", + "MunicipalityName": "Ueken", + "Canton": "AG", + "DateOfValidity": "1995-12-18T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 4191, "ZipCode": "5600", @@ -29222,15 +28241,6 @@ "DateOfValidity": "1996-02-19T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4196, - "ZipCode": "5615", - "ZipCodeAddOn": "02", - "MunicipalityName": "Fahrwangen Hintergasse", - "Canton": "AG", - "DateOfValidity": "2020-08-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4197, "ZipCode": "5705", @@ -29285,15 +28295,6 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4201, - "ZipCode": "5600", - "ZipCodeAddOn": "04", - "MunicipalityName": "Lenzburg Murackerstrasse", - "Canton": "AG", - "DateOfValidity": "2019-09-09T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4201, "ZipCode": "5600", @@ -29348,15 +28349,6 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4203, - "ZipCode": "5103", - "ZipCodeAddOn": "03", - "MunicipalityName": "Wildegg Poststrasse", - "Canton": "AG", - "DateOfValidity": "2021-03-05T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4204, "ZipCode": "5702", @@ -29393,6 +28385,15 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 4207, + "ZipCode": "5503", + "ZipCodeAddOn": "02", + "MunicipalityName": "Schafisheim Coop Verteilz.", + "Canton": "AG", + "DateOfValidity": "2022-11-07T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 4208, "ZipCode": "5707", @@ -29582,15 +28583,6 @@ "DateOfValidity": "1995-12-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4236, - "ZipCode": "5630", - "ZipCodeAddOn": "02", - "MunicipalityName": "Muri AG Seetalstrasse", - "Canton": "AG", - "DateOfValidity": "2019-12-02T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4236, "ZipCode": "5630", @@ -29789,6 +28781,15 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 4258, + "ZipCode": "4310", + "ZipCodeAddOn": "03", + "MunicipalityName": "Rheinfelden Kaiserstrasse", + "Canton": "AG", + "DateOfValidity": "2023-05-08T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 4258, "ZipCode": "4310", @@ -29996,15 +28997,6 @@ "DateOfValidity": "2018-05-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4280, - "ZipCode": "4665", - "ZipCodeAddOn": "03", - "MunicipalityName": "Oftringen Tychboden", - "Canton": "AG", - "DateOfValidity": "2020-10-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4281, "ZipCode": "5056", @@ -30032,15 +29024,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4282, - "ZipCode": "4852", - "ZipCodeAddOn": "02", - "MunicipalityName": "Rothrist Bernstrasse", - "Canton": "AG", - "DateOfValidity": "2018-11-02T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4283, "ZipCode": "5745", @@ -30081,7 +29064,7 @@ "BfsCode": 4285, "ZipCode": "4802", "ZipCodeAddOn": "70", - "MunicipalityName": "Strengelbach Dist Hub", + "MunicipalityName": "Strengelbach Zustellung", "Canton": "AG", "DateOfValidity": "2021-05-28T00:00:00", "LanguageCode": 0 @@ -30248,15 +29231,6 @@ "DateOfValidity": "1987-10-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4289, - "ZipCode": "4800", - "ZipCodeAddOn": "02", - "MunicipalityName": "Zofingen Untere Grabenstr", - "Canton": "AG", - "DateOfValidity": "2020-08-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4289, "ZipCode": "4800", @@ -30347,15 +29321,6 @@ "DateOfValidity": "1999-01-27T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4309, - "ZipCode": "5313", - "ZipCodeAddOn": "02", - "MunicipalityName": "Klingnau Wiesenweg", - "Canton": "AG", - "DateOfValidity": "2014-07-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4310, "ZipCode": "5322", @@ -30446,15 +29411,6 @@ "DateOfValidity": "1995-04-03T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4324, - "ZipCode": "5330", - "ZipCodeAddOn": "01", - "MunicipalityName": "Bad Zurzach Hauptstrasse", - "Canton": "AG", - "DateOfValidity": "2018-08-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4324, "ZipCode": "5333", @@ -30538,11 +29494,11 @@ }, { "BfsCode": 4401, - "ZipCode": "9320", - "ZipCodeAddOn": "01", - "MunicipalityName": "Arbon Stickereistrasse", + "ZipCode": "9321", + "ZipCodeAddOn": "00", + "MunicipalityName": "Arbon MS", "Canton": "TG", - "DateOfValidity": "2016-09-01T00:00:00", + "DateOfValidity": "2023-09-04T00:00:00", "LanguageCode": 0 }, { @@ -30671,6 +29627,15 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 4436, + "ZipCode": "8590", + "ZipCodeAddOn": "70", + "MunicipalityName": "Romanshorn Zust Pakete", + "Canton": "TG", + "DateOfValidity": "1999-04-06T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 4436, "ZipCode": "8590", @@ -30713,7 +29678,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Amriswil", "Canton": "TG", - "DateOfValidity": "1996-08-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30722,7 +29687,7 @@ "ZipCodeAddOn": "60", "MunicipalityName": "Amriswil Zustellung", "Canton": "TG", - "DateOfValidity": "2003-09-01T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30740,7 +29705,7 @@ "ZipCodeAddOn": "05", "MunicipalityName": "Hagenwil b. Amriswil", "Canton": "TG", - "DateOfValidity": "1998-09-08T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30749,7 +29714,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Oberaach", "Canton": "TG", - "DateOfValidity": "1996-08-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30758,7 +29723,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Schocherswil", "Canton": "TG", - "DateOfValidity": "2000-10-01T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30767,7 +29732,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Bischofszell", "Canton": "TG", - "DateOfValidity": "1997-05-07T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30776,7 +29741,7 @@ "ZipCodeAddOn": "60", "MunicipalityName": "Bischofszell Zustellung", "Canton": "TG", - "DateOfValidity": "2003-05-01T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30785,7 +29750,7 @@ "ZipCodeAddOn": "01", "MunicipalityName": "Halden", "Canton": "TG", - "DateOfValidity": "1997-10-23T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30794,7 +29759,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Schweizersholz", "Canton": "TG", - "DateOfValidity": "1996-07-12T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30803,7 +29768,7 @@ "ZipCodeAddOn": "07", "MunicipalityName": "Buchackern", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30812,7 +29777,7 @@ "ZipCodeAddOn": "08", "MunicipalityName": "Engishofen", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30821,7 +29786,7 @@ "ZipCodeAddOn": "09", "MunicipalityName": "Ennetaach", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30830,7 +29795,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Erlen", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30839,7 +29804,7 @@ "ZipCodeAddOn": "03", "MunicipalityName": "Kümmertshausen", "Canton": "TG", - "DateOfValidity": "1998-09-10T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30848,7 +29813,7 @@ "ZipCodeAddOn": "05", "MunicipalityName": "Riedt b. Erlen", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30857,7 +29822,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Hauptwil", "Canton": "TG", - "DateOfValidity": "1996-07-12T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30866,7 +29831,7 @@ "ZipCodeAddOn": "01", "MunicipalityName": "St. Pelagiberg", "Canton": "TG", - "DateOfValidity": "1996-07-12T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30875,7 +29840,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Wilen (Gottshaus)", "Canton": "TG", - "DateOfValidity": "1996-07-12T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30884,7 +29849,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Heldswil", "Canton": "TG", - "DateOfValidity": "1999-06-14T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30893,7 +29858,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Hohentannen", "Canton": "TG", - "DateOfValidity": "1999-06-14T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30902,7 +29867,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Buhwil", "Canton": "TG", - "DateOfValidity": "1996-11-22T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30911,7 +29876,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Kradolf", "Canton": "TG", - "DateOfValidity": "1998-08-18T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30920,7 +29885,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Neukirch an der Thur", "Canton": "TG", - "DateOfValidity": "1997-03-10T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30929,7 +29894,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Schönenberg an der Thur", "Canton": "TG", - "DateOfValidity": "1998-08-18T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30938,7 +29903,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Donzhausen", "Canton": "TG", - "DateOfValidity": "1996-08-27T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30947,7 +29912,7 @@ "ZipCodeAddOn": "01", "MunicipalityName": "Götighofen", "Canton": "TG", - "DateOfValidity": "1996-08-27T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30956,7 +29921,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Sulgen", "Canton": "TG", - "DateOfValidity": "1996-09-09T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30965,7 +29930,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Sitterdorf", "Canton": "TG", - "DateOfValidity": "1997-04-11T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30974,7 +29939,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Zihlschlacht", "Canton": "TG", - "DateOfValidity": "1997-04-11T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30983,7 +29948,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Basadingen", "Canton": "TG", - "DateOfValidity": "1999-06-14T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -30992,7 +29957,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Schlattingen", "Canton": "TG", - "DateOfValidity": "1999-06-14T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31001,25 +29966,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Diessenhofen", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 4545, - "ZipCode": "8253", - "ZipCodeAddOn": "01", - "MunicipalityName": "Diessenhofen Bahnhofstrasse", - "Canton": "TG", - "DateOfValidity": "2017-03-01T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 4545, - "ZipCode": "8253", - "ZipCodeAddOn": "60", - "MunicipalityName": "Diessenhofen Zustellung", - "Canton": "TG", - "DateOfValidity": "2003-07-01T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31028,7 +29975,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Willisdorf", "Canton": "TG", - "DateOfValidity": "1996-08-13T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31037,7 +29984,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Schlatt TG", "Canton": "TG", - "DateOfValidity": "1999-06-14T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -31175,6 +30122,15 @@ "DateOfValidity": "1998-11-23T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 4566, + "ZipCode": "8502", + "ZipCodeAddOn": "00", + "MunicipalityName": "Frauenfeld Huben", + "Canton": "TG", + "DateOfValidity": "1996-08-06T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 4566, "ZipCode": "8510", @@ -31202,15 +30158,6 @@ "DateOfValidity": "1997-10-23T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4566, - "ZipCode": "8500", - "ZipCodeAddOn": "05", - "MunicipalityName": "Frauenfeld Murgstrasse", - "Canton": "TG", - "DateOfValidity": "2015-10-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4566, "ZipCode": "8520", @@ -31229,15 +30176,6 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4566, - "ZipCode": "8502", - "ZipCodeAddOn": "00", - "MunicipalityName": "Frauenfeld Talbach", - "Canton": "TG", - "DateOfValidity": "1996-08-06T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4566, "ZipCode": "8500", @@ -31706,20 +30644,11 @@ "DateOfValidity": "1996-03-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4696, - "ZipCode": "8274", - "ZipCodeAddOn": "03", - "MunicipalityName": "Tägerwilen Hauptstrasse", - "Canton": "TG", - "DateOfValidity": "2021-07-02T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4696, "ZipCode": "8274", "ZipCodeAddOn": "70", - "MunicipalityName": "Tägerwilen Zustellung P", + "MunicipalityName": "Tägerwilen Zust Pakete", "Canton": "TG", "DateOfValidity": "2001-02-12T00:00:00", "LanguageCode": 0 @@ -31997,19 +30926,19 @@ { "BfsCode": 4761, "ZipCode": "8370", - "ZipCodeAddOn": "70", - "MunicipalityName": "Sirnach Distributionsbasis", + "ZipCodeAddOn": "60", + "MunicipalityName": "Sirnach Zustellung", "Canton": "TG", - "DateOfValidity": "2016-10-01T00:00:00", + "DateOfValidity": "2017-03-10T00:00:00", "LanguageCode": 0 }, { "BfsCode": 4761, "ZipCode": "8370", - "ZipCodeAddOn": "60", - "MunicipalityName": "Sirnach Zustellung", + "ZipCodeAddOn": "70", + "MunicipalityName": "Sirnach Zustellung Pakete", "Canton": "TG", - "DateOfValidity": "2017-03-10T00:00:00", + "DateOfValidity": "2016-10-01T00:00:00", "LanguageCode": 0 }, { @@ -32099,7 +31028,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Berlingen", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32108,7 +31037,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Eschenz", "Canton": "TG", - "DateOfValidity": "1996-08-05T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32117,7 +31046,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Herdern", "Canton": "TG", - "DateOfValidity": "1999-12-09T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32126,7 +31055,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Lanzenneunforn", "Canton": "TG", - "DateOfValidity": "1998-02-16T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32135,7 +31064,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Homburg", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32144,7 +31073,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Hörhausen", "Canton": "TG", - "DateOfValidity": "1999-06-14T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32153,7 +31082,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Hüttwilen", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32162,7 +31091,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Nussbaumen TG", "Canton": "TG", - "DateOfValidity": "1997-04-11T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32171,7 +31100,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Uerschhausen", "Canton": "TG", - "DateOfValidity": "1998-04-14T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32180,7 +31109,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Mammern", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32189,7 +31118,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Müllheim Dorf", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32198,7 +31127,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Dettighofen", "Canton": "TG", - "DateOfValidity": "1999-11-16T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32207,7 +31136,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Pfyn", "Canton": "TG", - "DateOfValidity": "1998-02-16T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32216,7 +31145,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Raperswilen", "Canton": "TG", - "DateOfValidity": "1997-04-11T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32225,7 +31154,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Fruthwilen", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32234,7 +31163,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Mannenbach-Salenstein", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32243,7 +31172,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Salenstein", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32252,7 +31181,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Steckborn", "Canton": "TG", - "DateOfValidity": "1997-04-11T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32261,7 +31190,7 @@ "ZipCodeAddOn": "02", "MunicipalityName": "Etzwilen", "Canton": "TG", - "DateOfValidity": "1996-06-11T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32270,7 +31199,7 @@ "ZipCodeAddOn": "00", "MunicipalityName": "Kaltenbach", "Canton": "TG", - "DateOfValidity": "1996-03-06T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32279,7 +31208,7 @@ "ZipCodeAddOn": "03", "MunicipalityName": "Rheinklingen", "Canton": "TG", - "DateOfValidity": "1996-06-11T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32288,7 +31217,7 @@ "ZipCodeAddOn": "04", "MunicipalityName": "Wagenhausen", "Canton": "TG", - "DateOfValidity": "1996-09-20T00:00:00", + "DateOfValidity": "2011-01-01T00:00:00", "LanguageCode": 0 }, { @@ -32369,7 +31298,7 @@ "ZipCodeAddOn": "10", "MunicipalityName": "Buch b. Kümmertshausen", "Canton": "TG", - "DateOfValidity": "1999-11-22T00:00:00", + "DateOfValidity": "2005-12-01T00:00:00", "LanguageCode": 0 }, { @@ -32408,15 +31337,6 @@ "DateOfValidity": "1996-03-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 4911, - "ZipCode": "8575", - "ZipCodeAddOn": "03", - "MunicipalityName": "Bürglen TG Postweg", - "Canton": "TG", - "DateOfValidity": "2018-10-12T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 4911, "ZipCode": "8575", @@ -32916,7 +31836,7 @@ "BfsCode": 5003, "ZipCode": "6591", "ZipCodeAddOn": "70", - "MunicipalityName": "Cadenazzo CPR", + "MunicipalityName": "Cadenazzo Dist pacchi", "Canton": "TI", "DateOfValidity": "2018-10-01T00:00:00", "LanguageCode": 0 @@ -33218,15 +32138,6 @@ "DateOfValidity": "2003-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5061, - "ZipCode": "6780", - "ZipCodeAddOn": "02", - "MunicipalityName": "Madrano", - "Canton": "TI", - "DateOfValidity": "1995-12-06T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5063, "ZipCode": "6781", @@ -33668,15 +32579,6 @@ "DateOfValidity": "2003-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5115, - "ZipCode": "6616", - "ZipCodeAddOn": "01", - "MunicipalityName": "Losone via Locarno", - "Canton": "TI", - "DateOfValidity": "2017-05-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5117, "ZipCode": "6647", @@ -34145,15 +33047,6 @@ "DateOfValidity": "1989-06-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5189, - "ZipCode": "6814", - "ZipCodeAddOn": "04", - "MunicipalityName": "Lamone-Cadempino via Cant.", - "Canton": "TI", - "DateOfValidity": "2018-11-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5192, "ZipCode": "6974", @@ -34415,15 +33308,6 @@ "DateOfValidity": "1996-06-10T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5192, - "ZipCode": "6907", - "ZipCodeAddOn": "00", - "MunicipalityName": "Lugano 7 Loreto Caselle", - "Canton": "TI", - "DateOfValidity": "1996-06-10T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5192, "ZipCode": "6900", @@ -35333,15 +34217,6 @@ "DateOfValidity": "1995-09-07T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5254, - "ZipCode": "6850", - "ZipCodeAddOn": "03", - "MunicipalityName": "Mendrisio Staz St.Franscini", - "Canton": "TI", - "DateOfValidity": "2021-10-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5254, "ZipCode": "6850", @@ -35423,15 +34298,6 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5260, - "ZipCode": "6883", - "ZipCodeAddOn": "02", - "MunicipalityName": "Novazzano Via P. Bernasconi", - "Canton": "TI", - "DateOfValidity": "2019-11-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5263, "ZipCode": "6826", @@ -36107,15 +34973,6 @@ "DateOfValidity": "1996-09-03T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5401, - "ZipCode": "1860", - "ZipCodeAddOn": "02", - "MunicipalityName": "Aigle Chemin des Orlons", - "Canton": "VD", - "DateOfValidity": "2022-06-20T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5401, "ZipCode": "1860", @@ -36404,15 +35261,6 @@ "DateOfValidity": "1998-09-10T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5414, - "ZipCode": "1844", - "ZipCodeAddOn": "02", - "MunicipalityName": "Villeneuve VD Ave C.d.S.", - "Canton": "VD", - "DateOfValidity": "2021-06-08T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5415, "ZipCode": "1853", @@ -36831,7 +35679,7 @@ "BfsCode": 5480, "ZipCode": "1310", "ZipCodeAddOn": "70", - "MunicipalityName": "Daillens Dist Ba", + "MunicipalityName": "Daillens Distribution", "Canton": "VD", "DateOfValidity": "1998-11-23T00:00:00", "LanguageCode": 0 @@ -37583,6 +36431,15 @@ "DateOfValidity": "1997-02-21T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 5568, + "ZipCode": "1450", + "ZipCodeAddOn": "04", + "MunicipalityName": "Ste-Croix Rue Neuve", + "Canton": "VD", + "DateOfValidity": "2023-05-01T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 5571, "ZipCode": "1423", @@ -37682,15 +36539,6 @@ "DateOfValidity": "1999-04-08T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5583, - "ZipCode": "1023", - "ZipCodeAddOn": "03", - "MunicipalityName": "Crissier Chemin de Chisaz", - "Canton": "VD", - "DateOfValidity": "2020-08-19T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5583, "ZipCode": "1023", @@ -38231,15 +37079,6 @@ "DateOfValidity": "2003-09-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5606, - "ZipCode": "1095", - "ZipCodeAddOn": "02", - "MunicipalityName": "Lutry Route de Lavaux", - "Canton": "VD", - "DateOfValidity": "2022-04-04T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5607, "ZipCode": "1070", @@ -38393,15 +37232,6 @@ "DateOfValidity": "2003-06-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5624, - "ZipCode": "1030", - "ZipCodeAddOn": "02", - "MunicipalityName": "Bussigny rue de l'Industrie", - "Canton": "VD", - "DateOfValidity": "2021-11-25T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5627, "ZipCode": "1022", @@ -38465,15 +37295,6 @@ "DateOfValidity": "1999-04-20T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5633, - "ZipCode": "1026", - "ZipCodeAddOn": "04", - "MunicipalityName": "Echandens-Denges Rte Lonay", - "Canton": "VD", - "DateOfValidity": "2021-10-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5634, "ZipCode": "1114", @@ -38609,15 +37430,6 @@ "DateOfValidity": "1989-11-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5642, - "ZipCode": "1110", - "ZipCodeAddOn": "04", - "MunicipalityName": "Morges Rue des Charpentiers", - "Canton": "VD", - "DateOfValidity": "2020-08-27T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5643, "ZipCode": "1028", @@ -39278,19 +38090,19 @@ { "BfsCode": 5721, "ZipCode": "1196", - "ZipCodeAddOn": "70", - "MunicipalityName": "Gland Dist Fil", + "ZipCodeAddOn": "60", + "MunicipalityName": "Gland Distribution", "Canton": "VD", - "DateOfValidity": "1999-04-06T00:00:00", + "DateOfValidity": "2003-10-01T00:00:00", "LanguageCode": 0 }, { "BfsCode": 5721, "ZipCode": "1196", - "ZipCodeAddOn": "60", - "MunicipalityName": "Gland Distribution", + "ZipCodeAddOn": "70", + "MunicipalityName": "Gland Distribution colis", "Canton": "VD", - "DateOfValidity": "2003-10-01T00:00:00", + "DateOfValidity": "1999-04-06T00:00:00", "LanguageCode": 0 }, { @@ -40022,15 +38834,6 @@ "DateOfValidity": "2003-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5822, - "ZipCode": "1530", - "ZipCodeAddOn": "60", - "MunicipalityName": "Payerne Distribution", - "Canton": "VD", - "DateOfValidity": "2003-07-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5822, "ZipCode": "1551", @@ -40328,15 +39131,6 @@ "DateOfValidity": "1989-06-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5861, - "ZipCode": "1180", - "ZipCodeAddOn": "03", - "MunicipalityName": "Rolle Chemin du Famolens", - "Canton": "VD", - "DateOfValidity": "2021-07-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5861, "ZipCode": "1180", @@ -40589,15 +39383,6 @@ "DateOfValidity": "1995-09-05T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5886, - "ZipCode": "1815", - "ZipCodeAddOn": "02", - "MunicipalityName": "Clarens rue du Collège", - "Canton": "VD", - "DateOfValidity": "2021-01-13T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5886, "ZipCode": "1823", @@ -40715,15 +39500,6 @@ "DateOfValidity": "1999-04-08T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5890, - "ZipCode": "1800", - "ZipCodeAddOn": "04", - "MunicipalityName": "Vevey Av. du Général-Guisan", - "Canton": "VD", - "DateOfValidity": "2019-11-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5890, "ZipCode": "1800", @@ -40958,15 +39734,6 @@ "DateOfValidity": "2001-04-23T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5922, - "ZipCode": "1440", - "ZipCodeAddOn": "02", - "MunicipalityName": "Montagny-Chamard En Chamard", - "Canton": "VD", - "DateOfValidity": "2021-02-23T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5922, "ZipCode": "1442", @@ -41183,15 +39950,6 @@ "DateOfValidity": "1997-12-11T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 5938, - "ZipCode": "1400", - "ZipCodeAddOn": "04", - "MunicipalityName": "Yverdon-les-Bains Ch-Lovats", - "Canton": "VD", - "DateOfValidity": "2019-11-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 5939, "ZipCode": "1462", @@ -41255,15 +40013,6 @@ "DateOfValidity": "1996-09-02T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6002, - "ZipCode": "3902", - "ZipCodeAddOn": "02", - "MunicipalityName": "Glis Gliserallee", - "Canton": "VS", - "DateOfValidity": "2020-12-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6004, "ZipCode": "3939", @@ -41660,6 +40409,15 @@ "DateOfValidity": "2019-06-03T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 6025, + "ZipCode": "1963", + "ZipCodeAddOn": "70", + "MunicipalityName": "Vétroz Distribution", + "Canton": "VS", + "DateOfValidity": "2022-11-07T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 6025, "ZipCode": "1960", @@ -42794,15 +41552,6 @@ "DateOfValidity": "2007-02-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6136, - "ZipCode": "1920", - "ZipCodeAddOn": "04", - "MunicipalityName": "Martigny rue des Avouillons", - "Canton": "VS", - "DateOfValidity": "2020-03-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6137, "ZipCode": "1921", @@ -42947,15 +41696,6 @@ "DateOfValidity": "1996-09-03T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6153, - "ZipCode": "1870", - "ZipCodeAddOn": "03", - "MunicipalityName": "Monthey Avenue de la gare", - "Canton": "VS", - "DateOfValidity": "2018-04-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6154, "ZipCode": "1897", @@ -43388,15 +42128,6 @@ "DateOfValidity": "1997-02-25T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6217, - "ZipCode": "1890", - "ZipCodeAddOn": "75", - "MunicipalityName": "St-Maurice Caserne", - "Canton": "VS", - "DateOfValidity": "2003-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6218, "ZipCode": "1923", @@ -43505,29 +42236,11 @@ "DateOfValidity": "1988-05-29T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6240, - "ZipCode": "3963", - "ZipCodeAddOn": "00", - "MunicipalityName": "Crans-Montana", - "Canton": "VS", - "DateOfValidity": "1997-02-24T00:00:00", - "LanguageCode": 0 - }, - { - "BfsCode": 6240, - "ZipCode": "3963", - "ZipCodeAddOn": "02", - "MunicipalityName": "Crans-Montana 2", - "Canton": "VS", - "DateOfValidity": "2003-06-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6240, "ZipCode": "3963", "ZipCodeAddOn": "05", - "MunicipalityName": "Crans-sur-Sierre", + "MunicipalityName": "Crans VS", "Canton": "VS", "DateOfValidity": "2018-07-07T00:00:00", "LanguageCode": 0 @@ -43622,15 +42335,6 @@ "DateOfValidity": "2003-08-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6248, - "ZipCode": "3960", - "ZipCodeAddOn": "06", - "MunicipalityName": "Sierre Rue de l'Ile Falcon", - "Canton": "VS", - "DateOfValidity": "2022-03-07T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6252, "ZipCode": "3961", @@ -43748,6 +42452,15 @@ "DateOfValidity": "1986-05-21T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 6253, + "ZipCode": "3963", + "ZipCodeAddOn": "00", + "MunicipalityName": "Crans-Montana", + "Canton": "VS", + "DateOfValidity": "1997-02-24T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 6253, "ZipCode": "3963", @@ -44045,15 +42758,6 @@ "DateOfValidity": "2003-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6266, - "ZipCode": "1950", - "ZipCodeAddOn": "06", - "MunicipalityName": "Sion rue des Casernes", - "Canton": "VS", - "DateOfValidity": "2022-05-30T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6266, "ZipCode": "1953", @@ -44099,6 +42803,15 @@ "DateOfValidity": "1996-09-02T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 6281, + "ZipCode": "3937", + "ZipCodeAddOn": "80", + "MunicipalityName": "SAB Baltschieder", + "Canton": "VS", + "DateOfValidity": "2022-11-14T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 6282, "ZipCode": "3922", @@ -44409,7 +43122,7 @@ "BfsCode": 6416, "ZipCode": "2013", "ZipCodeAddOn": "70", - "MunicipalityName": "Boudry Dist Ba", + "MunicipalityName": "Boudry Distribution colis", "Canton": "NE", "DateOfValidity": "2004-07-01T00:00:00", "LanguageCode": 0 @@ -44432,15 +43145,6 @@ "DateOfValidity": "2003-01-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6416, - "ZipCode": "2013", - "ZipCodeAddOn": "60", - "MunicipalityName": "Colombier NE Distribution", - "Canton": "NE", - "DateOfValidity": "2003-06-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6417, "ZipCode": "2022", @@ -44819,15 +43523,6 @@ "DateOfValidity": "1996-09-10T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6458, - "ZipCode": "2035", - "ZipCodeAddOn": "60", - "MunicipalityName": "Corcelles NE Distribution", - "Canton": "NE", - "DateOfValidity": "2005-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6458, "ZipCode": "2036", @@ -44936,15 +43631,6 @@ "DateOfValidity": "1997-07-28T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6458, - "ZipCode": "2001", - "ZipCodeAddOn": "03", - "MunicipalityName": "Neuchâtel Place Numa-Droz", - "Canton": "NE", - "DateOfValidity": "2021-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6458, "ZipCode": "2006", @@ -45008,15 +43694,6 @@ "DateOfValidity": "2003-11-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6461, - "ZipCode": "2074", - "ZipCodeAddOn": "03", - "MunicipalityName": "Marin-Epagnier Fleur de Lys", - "Canton": "NE", - "DateOfValidity": "2019-05-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6461, "ZipCode": "2075", @@ -45431,15 +44108,6 @@ "DateOfValidity": "1988-09-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6608, - "ZipCode": "1227", - "ZipCodeAddOn": "02", - "MunicipalityName": "Les Acacias Rte Grand-Lancy", - "Canton": "GE", - "DateOfValidity": "2021-11-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6609, "ZipCode": "1236", @@ -45719,15 +44387,6 @@ "DateOfValidity": "2019-11-22T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6621, - "ZipCode": "1240", - "ZipCodeAddOn": "00", - "MunicipalityName": "Genève", - "Canton": "GE", - "DateOfValidity": "2004-05-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6621, "ZipCode": "1200", @@ -46074,7 +44733,7 @@ "BfsCode": 6621, "ZipCode": "1200", "ZipCodeAddOn": "70", - "MunicipalityName": "Genève Dist Ba", + "MunicipalityName": "Genève Distribution colis", "Canton": "GE", "DateOfValidity": "1998-11-23T00:00:00", "LanguageCode": 0 @@ -46376,15 +45035,6 @@ "DateOfValidity": "1996-06-10T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6633, - "ZipCode": "1228", - "ZipCodeAddOn": "02", - "MunicipalityName": "Plan-les-Ouates Place des A", - "Canton": "GE", - "DateOfValidity": "2021-01-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6634, "ZipCode": "1292", @@ -46439,15 +45089,6 @@ "DateOfValidity": "1996-08-06T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6638, - "ZipCode": "1242", - "ZipCodeAddOn": "02", - "MunicipalityName": "Satigny Rte d'Aire-la-Ville", - "Canton": "GE", - "DateOfValidity": "2022-08-22T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6639, "ZipCode": "1286", @@ -46556,15 +45197,6 @@ "DateOfValidity": "2003-11-01T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6643, - "ZipCode": "1214", - "ZipCodeAddOn": "02", - "MunicipalityName": "Vernier rue du Village", - "Canton": "GE", - "DateOfValidity": "2021-10-07T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6644, "ZipCode": "1290", @@ -46749,7 +45381,7 @@ "BfsCode": 6711, "ZipCode": "2800", "ZipCodeAddOn": "70", - "MunicipalityName": "Delémont Dist Ba", + "MunicipalityName": "Delémont Distribution colis", "Canton": "JU", "DateOfValidity": "1999-06-30T00:00:00", "LanguageCode": 0 @@ -46955,17 +45587,17 @@ { "BfsCode": 6743, "ZipCode": "2345", - "ZipCodeAddOn": "00", - "MunicipalityName": "Les Breuleux", + "ZipCodeAddOn": "03", + "MunicipalityName": "La Chaux-des-Breuleux", "Canton": "JU", "DateOfValidity": "1996-09-19T00:00:00", "LanguageCode": 0 }, { - "BfsCode": 6744, + "BfsCode": 6743, "ZipCode": "2345", - "ZipCodeAddOn": "03", - "MunicipalityName": "La Chaux-des-Breuleux", + "ZipCodeAddOn": "00", + "MunicipalityName": "Les Breuleux", "Canton": "JU", "DateOfValidity": "1996-09-19T00:00:00", "LanguageCode": 0 @@ -47222,15 +45854,6 @@ "DateOfValidity": "1996-09-13T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6787, - "ZipCode": "2933", - "ZipCodeAddOn": "02", - "MunicipalityName": "Damphreux", - "Canton": "JU", - "DateOfValidity": "1996-09-13T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6789, "ZipCode": "2916", @@ -47276,15 +45899,6 @@ "DateOfValidity": "1996-09-13T00:00:00", "LanguageCode": 0 }, - { - "BfsCode": 6793, - "ZipCode": "2933", - "ZipCodeAddOn": "03", - "MunicipalityName": "Lugnez", - "Canton": "JU", - "DateOfValidity": "1998-08-01T00:00:00", - "LanguageCode": 0 - }, { "BfsCode": 6800, "ZipCode": "2900", @@ -47528,6 +46142,24 @@ "DateOfValidity": "1996-09-13T00:00:00", "LanguageCode": 0 }, + { + "BfsCode": 6811, + "ZipCode": "2933", + "ZipCodeAddOn": "02", + "MunicipalityName": "Damphreux", + "Canton": "JU", + "DateOfValidity": "1996-09-13T00:00:00", + "LanguageCode": 0 + }, + { + "BfsCode": 6811, + "ZipCode": "2933", + "ZipCodeAddOn": "03", + "MunicipalityName": "Lugnez", + "Canton": "JU", + "DateOfValidity": "1998-08-01T00:00:00", + "LanguageCode": 0 + }, { "BfsCode": 7001, "ZipCode": "9490", @@ -47595,7 +46227,7 @@ "BfsCode": 7005, "ZipCode": "9494", "ZipCodeAddOn": "68", - "MunicipalityName": "Schaan BZ GKS", + "MunicipalityName": "Schaan BZ Spezialkunden", "Canton": "FL", "DateOfValidity": "2008-03-01T00:00:00", "LanguageCode": 0 diff --git a/src/Tax.Data.Integration.Tests/__snapshots__/MunicipalityDataIntegrationTests.ShouldCalculateSuccessfully.snap b/src/Tax.Data.Integration.Tests/__snapshots__/MunicipalityDataIntegrationTests.ShouldCalculateSuccessfully.snap index 305e8937..7dc32421 100644 --- a/src/Tax.Data.Integration.Tests/__snapshots__/MunicipalityDataIntegrationTests.ShouldCalculateSuccessfully.snap +++ b/src/Tax.Data.Integration.Tests/__snapshots__/MunicipalityDataIntegrationTests.ShouldCalculateSuccessfully.snap @@ -5,7 +5,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1000, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": null }, { "BfsNumber": 71, @@ -13,7 +14,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1000, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 819600000 }, { "BfsNumber": 121, @@ -21,7 +23,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1000, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 862000000 }, { "BfsNumber": 231, @@ -29,7 +32,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1000, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 848700000 }, { "BfsNumber": 160, @@ -37,7 +41,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1000, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 812600000 }, { "BfsNumber": 120, @@ -45,7 +50,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1000, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 863600000 }, { "BfsNumber": 83, @@ -53,7 +59,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1000, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 810700000 }, { "BfsNumber": 22, @@ -61,7 +68,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1000, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 846300000 }, { "BfsNumber": 115, @@ -69,7 +77,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1000, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 862500000 }, { "BfsNumber": 151, @@ -77,7 +86,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1000, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 870300000 }, { "BfsNumber": 154, @@ -85,7 +95,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1000, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 870000000 }, { "BfsNumber": 135, @@ -93,7 +104,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1000, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 880200000 }, { "BfsNumber": 118, @@ -101,7 +113,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1000, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 863000000 }, { "BfsNumber": 225, @@ -109,7 +122,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1000, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 854500000 }, { "BfsNumber": 161, @@ -117,7 +131,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1493, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 870200000 }, { "BfsNumber": 261, @@ -125,7 +140,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1498, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 800000000 }, { "BfsNumber": 242, @@ -133,7 +149,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1500, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 890300000 }, { "BfsNumber": 251, @@ -141,7 +158,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1509, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 810400000 }, { "BfsNumber": 226, @@ -149,7 +167,8 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 1920, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 841800000 }, { "BfsNumber": 241, @@ -157,6 +176,7 @@ "Canton": "ZH", "DateOfMutation": null, "MutationId": 2165, - "SuccessorId": 0 + "SuccessorId": 0, + "EstvTaxLocationId": 890400000 } ] diff --git a/src/Tax.Data.Tests/MunicipalityDbContextTests.cs b/src/Tax.Data.Tests/MunicipalityDbContextTests.cs index ec788b98..51ad3340 100644 --- a/src/Tax.Data.Tests/MunicipalityDbContextTests.cs +++ b/src/Tax.Data.Tests/MunicipalityDbContextTests.cs @@ -1,74 +1,77 @@ using System.Collections.Generic; using System.Linq; +using Domain.Models.Municipality; +using Domain.Models.Tax; using FluentAssertions; +using Infrastructure.Tax.Data; using Microsoft.Extensions.DependencyInjection; -using Tax.Data.Abstractions.Models; using Xunit; -namespace Tax.Data.Tests; - -[Trait("Data", "Municipality DB Context")] -public class MunicipalityDbContextTests : IClassFixture +namespace Tax.Data.Tests { - private readonly TaxDataFixture _fixture; - - public MunicipalityDbContextTests(TaxDataFixture fixture) + [Trait("Data", "Municipality DB Context")] + public class MunicipalityDbContextTests : IClassFixture { - _fixture = fixture; - } + private readonly TaxDataFixture _fixture; - [Fact(DisplayName = "Get Zürich")] - public void ShouldLoadSingleMunicipalityByBfsNumber() - { - // given - int bfsNumber = 261; + public MunicipalityDbContextTests(TaxDataFixture fixture) + { + _fixture = fixture; + } - // when - var result = GetMunicipalityEntities(); + [Fact(DisplayName = "Get Zürich")] + public void ShouldLoadSingleMunicipalityByBfsNumber() + { + // given + int bfsNumber = 261; - // then - result.Should().NotBeNullOrEmpty(); + // when + var result = GetMunicipalityEntities(); - IEnumerable GetMunicipalityEntities() - { - using var dbContext = _fixture.Provider.GetService(); - return dbContext.MunicipalityEntities - .Where(item => item.BfsNumber == bfsNumber) - .ToList(); + // then + result.Should().NotBeNullOrEmpty(); + + IEnumerable GetMunicipalityEntities() + { + using var dbContext = _fixture.Provider.GetService(); + return dbContext.MunicipalityEntities + .Where(item => item.BfsNumber == bfsNumber) + .ToList(); + } } - } - [Fact(DisplayName = "Load Staged Zip Data")] - public void LoadStagesZipData() - { - // given - int bfsNumber = 261; + [Fact(DisplayName = "Load Staged Zip Data")] + public void LoadStagesZipData() + { + // given + int bfsNumber = 261; - // when - var result = GetZipEntities(); + // when + var result = GetZipEntities(); - // then - result.Should().NotBeNullOrEmpty(); + // then + result.Should().NotBeNullOrEmpty(); - IEnumerable GetZipEntities() - { - using var dbContext = _fixture.Provider.GetService(); - return dbContext.TaxMunicipalityEntities - .Where(item => item.BfsNumber == bfsNumber) - .ToList(); + IEnumerable GetZipEntities() + { + using var dbContext = _fixture.Provider.GetService(); + return dbContext.TaxMunicipalityEntities + .Where(item => item.BfsNumber == bfsNumber) + .ToList(); + } } - } - [Fact(DisplayName = "Truncate Stage Zip Data Table", Skip = "Preserve date")] - public void TruncateTaxMunicipalityTable() - { - // given + [Fact(DisplayName = "Truncate Stage Zip Data Table", Skip = "Preserve date")] + public void TruncateTaxMunicipalityTable() + { + // given - // when - using var dbContext = _fixture.Provider.GetService(); - var result = dbContext.TruncateTaxMunicipalityTable(); + // when + using var dbContext = _fixture.Provider.GetService(); + var result = dbContext.TruncateTaxMunicipalityTable(); - // then - result.Should().BeGreaterThan(0); + // then + result.Should().BeGreaterThan(0); + } } } diff --git a/src/Tax.Data.Tests/Tax.Data.Tests.csproj b/src/Tax.Data.Tests/Tax.Data.Tests.csproj index fdde8589..344c8e31 100644 --- a/src/Tax.Data.Tests/Tax.Data.Tests.csproj +++ b/src/Tax.Data.Tests/Tax.Data.Tests.csproj @@ -1,28 +1,28 @@  - net7.0 + net8.0 false - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Tax.Data.Tests/TaxDataFixture.cs b/src/Tax.Data.Tests/TaxDataFixture.cs index 3f16edbe..1b74cb68 100644 --- a/src/Tax.Data.Tests/TaxDataFixture.cs +++ b/src/Tax.Data.Tests/TaxDataFixture.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using Infrastructure.Tax.Data; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -38,4 +39,4 @@ public TaxDataFixture() Provider = coll.BuildServiceProvider(); } } -} \ No newline at end of file +} diff --git a/src/Tax.Data.Tests/TaxDbContextTests.cs b/src/Tax.Data.Tests/TaxDbContextTests.cs index afbdd78e..59288ad0 100644 --- a/src/Tax.Data.Tests/TaxDbContextTests.cs +++ b/src/Tax.Data.Tests/TaxDbContextTests.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using Domain.Models.Tax; using FluentAssertions; +using Infrastructure.Tax.Data; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Tax.Data.Abstractions.Models; using Xunit; namespace Tax.Data.Tests diff --git a/src/Tax.Data/StaticTaxDataPopulateService.cs b/src/Tax.Data/StaticTaxDataPopulateService.cs deleted file mode 100644 index e38a5d67..00000000 --- a/src/Tax.Data/StaticTaxDataPopulateService.cs +++ /dev/null @@ -1,365 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; -using Tax.Data.Abstractions; -using Tax.Data.Abstractions.Models; -using Tax.Data.Abstractions.Models.Populate; - -namespace Tax.Data; - -public class StaticTaxDataPopulateService : ITaxDataPopulateService -{ - private readonly MunicipalityDbContext municipalityDbContext; - private readonly IEstvTaxCalculatorClient estvTaxCalculatorClient; - - public StaticTaxDataPopulateService( - MunicipalityDbContext municipalityDbContext, IEstvTaxCalculatorClient estvTaxCalculatorClient) - { - this.municipalityDbContext = municipalityDbContext; - this.estvTaxCalculatorClient = estvTaxCalculatorClient; - } - - public async Task PopulateWithTaxLocationAsync(bool doClear) - { - const int numberOfReaders = 5; - const int numberOfFetchLevels = 6; - - if (doClear) - { - foreach (var municipalityEntity in municipalityDbContext.MunicipalityEntities.AsNoTracking()) - { - municipalityEntity.TaxLocationId = null; - municipalityEntity.Remark = null; - municipalityDbContext.Update(municipalityEntity); - } - - await municipalityDbContext.SaveChangesAsync(CancellationToken.None); - - municipalityDbContext.ChangeTracker.Clear(); - } - - var fetchLevelChannels = new Channel[numberOfFetchLevels]; - - for (int i = 0; i < fetchLevelChannels.Length; i++) - { - fetchLevelChannels[i] = Channel.CreateUnbounded(); - } - - Channel dbStoreChannel = Channel.CreateUnbounded(); - - Dictionary>> clientApiFetcher = - new Dictionary>> - { - { 0, entity => SearchApi(new SearchMunicipalityRequest(entity.ZipCode, entity.CleanName)) }, - { 1, entity => SearchApi(new SearchMunicipalityRequest(string.Empty, entity.CleanName)) }, - { 2, entity => SearchApi(new SearchMunicipalityRequest(entity.ZipCode, GetSplitName(entity))) }, - { 3, entity => SearchApi(new SearchMunicipalityRequest("", GetSplitName(entity))) }, - { 4, entity => SearchApi(new SearchMunicipalityRequest(entity.ZipCode, string.Empty)) }, - { 5, entity => SearchApi(new SearchMunicipalityRequest("", entity.OverruledName)) }, - }; - - Task[] fetchConsumers0 = Enumerable - .Range(1, numberOfReaders) - .Select(_ => FetchConsumerAsync(fetchLevelChannels[0], fetchLevelChannels[1], clientApiFetcher[0])) - .ToArray(); - - Task[] fetchConsumers1 = Enumerable - .Range(1, numberOfReaders) - .Select(_ => FetchConsumerAsync(fetchLevelChannels[1], fetchLevelChannels[2], clientApiFetcher[1])) - .ToArray(); - - Task[] fetchConsumers2 = Enumerable - .Range(1, numberOfReaders) - .Select(_ => FetchConsumerAsync(fetchLevelChannels[2], fetchLevelChannels[3], clientApiFetcher[2])) - .ToArray(); - - Task[] fetchConsumers3 = Enumerable - .Range(1, numberOfReaders) - .Select(_ => FetchConsumerAsync(fetchLevelChannels[3], fetchLevelChannels[4], clientApiFetcher[3])) - .ToArray(); - - Task[] fetchConsumers4 = Enumerable - .Range(1, numberOfReaders) - .Select(_ => FetchConsumerAsync(fetchLevelChannels[4], fetchLevelChannels[5], clientApiFetcher[4])) - .ToArray(); - - Task[] fetchConsumers5 = Enumerable - .Range(1, numberOfReaders) - .Select(_ => FetchConsumerAsync(fetchLevelChannels[5], dbStoreChannel, clientApiFetcher[5])) - .ToArray(); - - await Task.Run(async () => await FillInitialStage(fetchLevelChannels[0])); - - fetchLevelChannels[0].Writer.Complete(); - await Task.WhenAll(fetchConsumers0); - - fetchLevelChannels[1].Writer.Complete(); - await Task.WhenAll(fetchConsumers1); - - fetchLevelChannels[2].Writer.Complete(); - await Task.WhenAll(fetchConsumers2); - - fetchLevelChannels[3].Writer.Complete(); - await Task.WhenAll(fetchConsumers3); - - fetchLevelChannels[4].Writer.Complete(); - await Task.WhenAll(fetchConsumers4); - - fetchLevelChannels[5].Writer.Complete(); - await Task.WhenAll(fetchConsumers5); - - dbStoreChannel.Writer.Complete(); - - return await StoreResults(dbStoreChannel); - - async Task FetchConsumerAsync( - Channel fetchChannel, - Channel successorChannel, - Func> searcher) - { - while (await fetchChannel.Reader.WaitToReadAsync()) - { - if (!fetchChannel.Reader.TryRead(out TaxLocationSearchHolder data)) - { - continue; - } - - if (data.SearchResultType == SearchResultType.Match) - { - await successorChannel.Writer.WriteAsync(data); - } - else - { - TaxLocation[] locations = await searcher(data.MunicipalityEntity); - - TaxLocationSearchHolder searchResult = Dispatch(data.SearchLevel, locations, data.MunicipalityEntity); - - await successorChannel.Writer.WriteAsync(searchResult); - } - } - } - } - - private TaxLocationSearchHolder Dispatch(int searchLevel, TaxLocation[] taxLocations, MunicipalityEntity municipalityEntity) - { - TaxLocationSearchHolder searchHolder = CreateSearchHolder(taxLocations, municipalityEntity); - searchHolder.SearchLevel = searchLevel; - searchHolder.SearchResultType = SearchResultType.NotSet; - - (bool match, TaxLocation[] locations) = ApplyCheckers(searchHolder); - - if (match && locations.Length == 1) - { - return searchHolder with - { - SearchResultType = SearchResultType.Match, - TaxLocations = locations - }; - } - - return searchHolder; - } - - private TaxLocationSearchHolder CreateSearchHolder(TaxLocation[] locations, MunicipalityEntity entity) - { - if (locations.Length > 1) - { - return new TaxLocationSearchHolder - { - MunicipalityEntity = entity, - TaxLocations = locations, - SearchResultType = SearchResultType.NotUnique, - SearchLevel = 1, - }; - } - - if (locations.Length == 0) - { - return new TaxLocationSearchHolder - { - MunicipalityEntity = entity, - SearchResultType = SearchResultType.None, - SearchLevel = 1, - }; - } - - return new TaxLocationSearchHolder - { - MunicipalityEntity = entity, - SearchResultType = SearchResultType.Match, - TaxLocations = locations, - }; - } - - private async Task FillInitialStage(Channel fetchChannel) - { - foreach (MunicipalityEntity model in municipalityDbContext.MunicipalityEntities - .Where(item => item.SuccessorId == 0 && - item.MutationType == 11 && - item.TaxLocationId == null)) - { - await fetchChannel.Writer.WriteAsync( - new TaxLocationSearchHolder - { - SearchLevel = 0, - MunicipalityEntity = model, - TaxLocations = Array.Empty() - }); - } - } - - private async Task SearchApi(SearchMunicipalityRequest searchRequest) - { - if (searchRequest is null || (searchRequest.Zipcode == "" && searchRequest.Name == "")) - { - return Array.Empty(); - } - - return await estvTaxCalculatorClient.GetTaxLocationsAsync(searchRequest.Zipcode, searchRequest.Name) switch - { - null => Array.Empty(), - { } a => a - }; - } - - private (bool, TaxLocation[]) ApplyCheckers(TaxLocationSearchHolder searchHolder) - { - Func[] checkers = - { - MatchZipAndName, - MatchNameOnly, - MatchCanton, - MatchContainsNameAndCanton, - MatchByZipAsTaxId, - MatchByNamePart - }; - - if (searchHolder.TaxLocations == null) - { - return (false, null); - } - - foreach (var checker in checkers) - { - TaxLocation[] filteredLocations = checker(searchHolder.TaxLocations, searchHolder.MunicipalityEntity); - - if (filteredLocations.Length == 1) - { - return (true, filteredLocations); - } - } - - return (false, searchHolder.TaxLocations); - } - - private async Task StoreResults(Channel dbStoreChannel) - { - int count = 0; - await foreach (TaxLocationSearchHolder searchHolder in dbStoreChannel.Reader.ReadAllAsync(CancellationToken.None)) - { - // not found - if (searchHolder.SearchResultType is SearchResultType.None or - SearchResultType.NotUnique or - SearchResultType.NotSet) - { - searchHolder.MunicipalityEntity.Remark = searchHolder.SearchResultType.ToString(); - municipalityDbContext.Update(searchHolder.MunicipalityEntity); - continue; - } - - searchHolder.MunicipalityEntity.TaxLocationId = searchHolder.TaxLocations[0].Id; - if (searchHolder.MunicipalityEntity.ZipCode is null && searchHolder.TaxLocations[0].ZipCode is not null) - { - searchHolder.MunicipalityEntity.ZipCode = searchHolder.TaxLocations[0].ZipCode; - } - - municipalityDbContext.Update(searchHolder.MunicipalityEntity); - - count++; - } - - await municipalityDbContext.SaveChangesAsync(CancellationToken.None); - - return count; - } - - private TaxLocation[] MatchZipAndName(TaxLocation[] sourceLocations, MunicipalityEntity entity) - { - if (entity is null) - { - return Array.Empty(); - } - - return (sourceLocations ?? Array.Empty()) - .Where(item => item.ZipCode == entity.ZipCode && item.City == entity.CleanName) - .ToArray(); - } - - private TaxLocation[] MatchNameOnly(TaxLocation[] sourceLocations, MunicipalityEntity entity) - { - if (entity is null) - { - return Array.Empty(); - } - - return (sourceLocations ?? Array.Empty()) - .Where(item => item.City == entity.CleanName) - .ToArray(); - } - - private TaxLocation[] MatchCanton(TaxLocation[] sourceLocations, MunicipalityEntity entity) - { - return (sourceLocations ?? Array.Empty()) - .Where(item => item.Canton == entity.Canton) - .ToArray(); - } - - private TaxLocation[] MatchContainsNameAndCanton(TaxLocation[] sourceLocations, MunicipalityEntity entity) - { - return (sourceLocations ?? Array.Empty()) - .Where(item => item.City.Contains(entity.CleanName) && item.Canton == entity.Canton) - .ToArray(); - } - - private TaxLocation[] MatchByZipAsTaxId(TaxLocation[] sourceLocations, MunicipalityEntity entity) - { - int taxIdFromZipCode = Convert.ToInt32(entity.ZipCode) * 100000; - - return (sourceLocations ?? Array.Empty()) - .Where(item => item.Id == taxIdFromZipCode) - .ToArray(); - } - - private TaxLocation[] MatchByNamePart(TaxLocation[] sourceLocations, MunicipalityEntity entity) - { - // just try part of name before a hypen (ie. Illnau-Effretikon) - return GetSplitName(entity) switch - { - { } p => Filter(p), - _ => Array.Empty() - }; - - TaxLocation[] Filter(string part) - { - return (sourceLocations ?? Array.Empty()) - .Where(item => item.City == part) - .ToArray(); - } - } - - // just try part of name before a hypen (ie. Illnau-Effretikon) - private string GetSplitName(MunicipalityEntity entity) - { - return entity.CleanName.Split('-', ' ', '/') switch - { - { Length: 2 } p => p[0], - _ => null - }; - } -} diff --git a/src/Tax.Data/Tax.Data.csproj b/src/Tax.Data/Tax.Data.csproj deleted file mode 100644 index be63d634..00000000 --- a/src/Tax.Data/Tax.Data.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - net7.0 - - - - - - - - - PreserveNewest - - - - - - - - - - - - - - diff --git a/src/Tax.Tools.Comparison.Domain/IncomeAndWealthComparerRequest.cs b/src/Tax.Tools.Comparison.Domain/IncomeAndWealthComparerRequest.cs deleted file mode 100644 index 0f435c0b..00000000 --- a/src/Tax.Tools.Comparison.Domain/IncomeAndWealthComparerRequest.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using PensionCoach.Tools.CommonTypes; - -namespace PensionCoach.Tools.TaxComparison; - -public class IncomeAndWealthComparerRequest -{ - [MaxLength(50)] - public string Name { get; set; } - - public CivilStatus CivilStatus { get; set; } - - public ReligiousGroupType ReligiousGroup { get; set; } - - public ReligiousGroupType? PartnerReligiousGroup { get; set; } - - /// - /// List of BFS number defines for which municipalites a comparison is calculated. - /// - public int[] BfsNumberList { get; set; } - - [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] - public decimal TaxableIncome { get; set; } - - [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] - public decimal TaxableFederalIncome { get; set; } - - [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] - public decimal TaxableWealth { get; set; } -} diff --git a/src/Tax.Tools.Comparison.Domain/IncomeAndWealthTaxComparerResult.cs b/src/Tax.Tools.Comparison.Domain/IncomeAndWealthTaxComparerResult.cs deleted file mode 100644 index ed0213e4..00000000 --- a/src/Tax.Tools.Comparison.Domain/IncomeAndWealthTaxComparerResult.cs +++ /dev/null @@ -1,19 +0,0 @@ -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; - -namespace PensionCoach.Tools.TaxComparison; - -public class IncomeAndWealthTaxComparerResult -{ - public int MunicipalityId { get; set; } - - public string MunicipalityName { get; set; } - - public Canton Canton { get; set; } - - public int MaxSupportedTaxYear { get; set; } - - public FullTaxResult TaxResult { get; set; } - - public int TotalCount { get; set; } -} diff --git a/src/Tax.Tools.Comparison.Domain/SingleTransferInModel.cs b/src/Tax.Tools.Comparison.Domain/SingleTransferInModel.cs deleted file mode 100644 index fe164fe8..00000000 --- a/src/Tax.Tools.Comparison.Domain/SingleTransferInModel.cs +++ /dev/null @@ -1,5 +0,0 @@ -using System; - -namespace PensionCoach.Tools.TaxComparison; - -public record SingleTransferInModel(decimal Amount, DateTime DateOfTransferIn); diff --git a/src/Tax.Tools.Comparison.Domain/Tax.Tools.Comparison.Domain.csproj b/src/Tax.Tools.Comparison.Domain/Tax.Tools.Comparison.Domain.csproj deleted file mode 100644 index 2fd532b2..00000000 --- a/src/Tax.Tools.Comparison.Domain/Tax.Tools.Comparison.Domain.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - net7.0 - PensionCoach.Tools.TaxComparison.Domain - PensionCoach.Tools.TaxComparison - latest - - - - - - - - diff --git a/src/Tax.Tools.Comparison.Domain/TaxComparerResponse.cs b/src/Tax.Tools.Comparison.Domain/TaxComparerResponse.cs deleted file mode 100644 index 11cc8558..00000000 --- a/src/Tax.Tools.Comparison.Domain/TaxComparerResponse.cs +++ /dev/null @@ -1,26 +0,0 @@ -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; - -namespace PensionCoach.Tools.TaxComparison -{ - public class TaxComparerResponse - { - public string Name { get; set; } - - public int MunicipalityId { get; set; } - - public string MunicipalityName { get; set; } - - public Canton Canton { get; set; } - - public int MaxSupportedTaxYear { get; set; } - - public decimal TotalTaxAmount { get; set; } - - public TaxAmountDetail TaxDetails { get; set; } - - public int TotalCount { get; set; } - - public int CountComputed { get; set; } - } -} diff --git a/src/Tax.Tools.Comparison.Tests/CapitalBenefitTaxComparerTests.cs b/src/Tax.Tools.Comparison.Tests/CapitalBenefitTaxComparerTests.cs new file mode 100644 index 00000000..4fb6128e --- /dev/null +++ b/src/Tax.Tools.Comparison.Tests/CapitalBenefitTaxComparerTests.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Application.Features.TaxComparison; +using Domain.Enums; +using Domain.Models.Tax; +using Domain.Models.TaxComparison; +using LanguageExt; +using Snapshooter.Xunit; +using Xunit; + +namespace Tax.Tools.Comparison.Tests; + +[Trait("Comparer", "Capital Benefits")] +public class CapitalBenefitTaxComparerTests + : IClassFixture> +{ + private readonly TaxComparerFixture fixture; + + public CapitalBenefitTaxComparerTests(TaxComparerFixture fixture) + { + this.fixture = fixture; + } + + [Fact(DisplayName = "Compare Capital Benefit Tax (streamed)")] + public async Task CalculateComparisonByStreaming() + { + // given + int[] bfsNumbers = { 261, 3, 1344 }; + + string name = "Burli"; + CivilStatus status = CivilStatus.Single; + ReligiousGroupType religiousGroup = ReligiousGroupType.Protestant; + + var taxPerson = new CapitalBenefitTaxPerson + { + Name = name, + CivilStatus = status, + ReligiousGroupType = religiousGroup, + TaxableCapitalBenefits = 2000_000, + }; + + // when + List> results = new List>(); + await foreach (var compareResult in fixture.Calculator.CompareCapitalBenefitTaxAsync(taxPerson, bfsNumbers)) + { + results.Add(compareResult); + } + + Assert.All(results, m => Assert.True(m.IsRight)); + + List orderedResults = new(); + results.Iter(m => m.IfRight(r => orderedResults.Add(r))); + + // then + Snapshot.Match(orderedResults.OrderBy(item => item.MunicipalityName)); + } + + + [Fact(DisplayName = "Compare Income and Wealth Tax")] + public async Task CompareIncomeAndWealthTax() + { + // given + int[] bfsNumbers = { 261, 3, 1344 }; + + string name = "Burli"; + CivilStatus status = CivilStatus.Single; + ReligiousGroupType religiousGroup = ReligiousGroupType.Protestant; + + var taxPerson = new TaxPerson + { + Name = name, + CivilStatus = status, + ReligiousGroupType = religiousGroup, + TaxableFederalIncome = 100_000, + TaxableIncome = 100_000, + TaxableWealth = 500_000, + }; + + // when + List> results = new (); + await foreach (var compareResult in fixture.Calculator.CompareIncomeAndWealthTaxAsync(taxPerson, bfsNumbers)) + { + results.Add(compareResult); + } + + Assert.All(results, m => Assert.True(m.IsRight)); + + List orderedResults = new(); + results.Iter(m => m.IfRight(r => orderedResults.Add(r))); + + // then + Snapshot.Match(orderedResults.OrderBy(item => item.MunicipalityName)); + } +} diff --git a/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/Tax.Tools.Comparison.Tests.csproj b/src/Tax.Tools.Comparison.Tests/Tax.Tools.Comparison.Tests.csproj similarity index 54% rename from src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/Tax.Tools.Comparison.Tests.csproj rename to src/Tax.Tools.Comparison.Tests/Tax.Tools.Comparison.Tests.csproj index 965d3058..c7c523b1 100644 --- a/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/Tax.Tools.Comparison.Tests.csproj +++ b/src/Tax.Tools.Comparison.Tests/Tax.Tools.Comparison.Tests.csproj @@ -1,34 +1,33 @@  - net7.0 + net8.0 false - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + - - + diff --git a/src/Tax.Tools.Comparison.Tests/TaxComparerFixture.cs b/src/Tax.Tools.Comparison.Tests/TaxComparerFixture.cs new file mode 100644 index 00000000..c6fd405b --- /dev/null +++ b/src/Tax.Tools.Comparison.Tests/TaxComparerFixture.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Application.Features.FullTaxCalculation; +using Application.Features.TaxComparison; +using Infrastructure.Configuration; +using Infrastructure.Tax.Data; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Tax.Tools.Comparison.Tests; + +public class TaxComparerFixture + where T : class +{ + public ServiceProvider Provider { get; } + + public T Calculator { get; } + + public T Service { get; } + + public TaxComparerFixture() + { + var projectPath = Assembly.GetExecutingAssembly() + .Location.Split("src", StringSplitOptions.RemoveEmptyEntries) + .First(); + + var dbFile = Path.Combine(projectPath, @"src\Infrastructure\files\TaxDb.db"); + + var configurationDict = new Dictionary + { + {"ConnectionStrings:TaxDb", dbFile}, + }; + + IConfiguration configuration = new ConfigurationBuilder() + .AddInMemoryCollection(configurationDict) + .Build(); + + ServiceCollection coll = new ServiceCollection(); + + coll.AddLogging(); + coll.AddTaxCalculators(configuration.GetApplicationMode()); + coll.AddTaxData(configuration); + coll.AddTaxComparers(); + + Provider = coll.BuildServiceProvider(); + + Calculator = Provider.GetRequiredService(); + + Service = Provider.GetRequiredService(); + } +} diff --git a/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/__snapshots__/CapitalBenefitTaxComparerTests.CalculateComparisonByStreaming.snap b/src/Tax.Tools.Comparison.Tests/__snapshots__/CapitalBenefitTaxComparerTests.CalculateComparisonByStreaming.snap similarity index 86% rename from src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/__snapshots__/CapitalBenefitTaxComparerTests.CalculateComparisonByStreaming.snap rename to src/Tax.Tools.Comparison.Tests/__snapshots__/CapitalBenefitTaxComparerTests.CalculateComparisonByStreaming.snap index ab02bcfa..93edccc5 100644 --- a/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/__snapshots__/CapitalBenefitTaxComparerTests.CalculateComparisonByStreaming.snap +++ b/src/Tax.Tools.Comparison.Tests/__snapshots__/CapitalBenefitTaxComparerTests.CalculateComparisonByStreaming.snap @@ -11,12 +11,8 @@ "TaxAmount": 169770.0 }, "ChurchTax": { - "TaxAmount": [ - 22070.100 - ], - "TaxAmountPartner": [ - 0.0 - ], + "TaxAmount": 22070.100, + "TaxAmountPartner": 0.0, "TaxRate": 0.0 }, "CantonRate": 100.0, @@ -46,12 +42,8 @@ "TaxAmount": 169770.0 }, "ChurchTax": { - "TaxAmount": [ - 16977.00 - ], - "TaxAmountPartner": [ - 0.0 - ], + "TaxAmount": 16977.00, + "TaxAmountPartner": 0.0, "TaxRate": 0.0 }, "CantonRate": 100.0, diff --git a/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/__snapshots__/CapitalBenefitTaxComparerTests.CompareIncomeAndWealthTax.snap b/src/Tax.Tools.Comparison.Tests/__snapshots__/CapitalBenefitTaxComparerTests.CompareIncomeAndWealthTax.snap similarity index 84% rename from src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/__snapshots__/CapitalBenefitTaxComparerTests.CompareIncomeAndWealthTax.snap rename to src/Tax.Tools.Comparison.Tests/__snapshots__/CapitalBenefitTaxComparerTests.CompareIncomeAndWealthTax.snap index b9a82277..1f411846 100644 --- a/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/__snapshots__/CapitalBenefitTaxComparerTests.CompareIncomeAndWealthTax.snap +++ b/src/Tax.Tools.Comparison.Tests/__snapshots__/CapitalBenefitTaxComparerTests.CompareIncomeAndWealthTax.snap @@ -15,12 +15,8 @@ "TaxAmount": 307.5 }, "ChurchTax": { - "TaxAmount": [ - 858.4550 - ], - "TaxAmountPartner": [ - 0.00 - ], + "TaxAmount": 858.4550, + "TaxAmountPartner": 0.00, "TaxRate": 13.0 }, "CantonRate": 100.0, @@ -31,9 +27,7 @@ "TotalWealthTax": 642.675, "TotalIncomeTax": 14017.0950, "TotalTaxAmount": 14683.7700, - "PollTaxAmount": [ - 24.0 - ] + "PollTaxAmount": 24.0 }, "FederalTaxResult": { "DeterminingFactorTaxableAmount": 100000.0, @@ -59,12 +53,8 @@ "TaxAmount": 307.5 }, "ChurchTax": { - "TaxAmount": [ - 660.350 - ], - "TaxAmountPartner": [ - 0.00 - ], + "TaxAmount": 660.350, + "TaxAmountPartner": 0.00, "TaxRate": 10.0 }, "CantonRate": 100.0, @@ -75,9 +65,7 @@ "TotalWealthTax": 673.425, "TotalIncomeTax": 14448.590, "TotalTaxAmount": 15146.015, - "PollTaxAmount": [ - 24.0 - ] + "PollTaxAmount": 24.0 }, "FederalTaxResult": { "DeterminingFactorTaxableAmount": 100000.0, diff --git a/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Abstractions/Tax.Tools.Comparison.Abstractions.csproj b/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Abstractions/Tax.Tools.Comparison.Abstractions.csproj deleted file mode 100644 index fd7fefd3..00000000 --- a/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Abstractions/Tax.Tools.Comparison.Abstractions.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - net7.0 - - - - - - - - diff --git a/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/CapitalBenefitTaxComparerTests.cs b/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/CapitalBenefitTaxComparerTests.cs deleted file mode 100644 index 7b35b4d0..00000000 --- a/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/CapitalBenefitTaxComparerTests.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxComparison; -using Snapshooter.Xunit; -using Tax.Tools.Comparison.Abstractions; -using Xunit; - -namespace Tax.Tools.Comparison.Tests -{ - [Trait("Comparer", "Capital Benefits")] - public class CapitalBenefitTaxComparerTests - : IClassFixture> - { - private readonly TaxComparerFixture fixture; - - public CapitalBenefitTaxComparerTests(TaxComparerFixture fixture) - { - this.fixture = fixture; - } - - [Fact(DisplayName = "Compare Capital Benefit Tax (streamed)")] - public async Task CalculateComparisonByStreaming() - { - // given - int[] bfsNumbers = { 261, 3, 1344 }; - - string name = "Burli"; - CivilStatus status = CivilStatus.Single; - ReligiousGroupType religiousGroup = ReligiousGroupType.Protestant; - - var taxPerson = new CapitalBenefitTaxPerson - { - Name = name, - CivilStatus = status, - ReligiousGroupType = religiousGroup, - TaxableCapitalBenefits = 2000_000, - }; - - // when - List> results = new List>(); - await foreach (var compareResult in fixture.Calculator.CompareCapitalBenefitTaxAsync(taxPerson, bfsNumbers)) - { - results.Add(compareResult); - } - - Assert.All(results, m => Assert.True(m.IsRight)); - - List orderedResults = new(); - results.Iter(m => m.IfRight(r => orderedResults.Add(r))); - - // then - Snapshot.Match(orderedResults.OrderBy(item => item.MunicipalityName)); - } - - - [Fact(DisplayName = "Compare Income and Wealth Tax")] - public async Task CompareIncomeAndWealthTax() - { - // given - int[] bfsNumbers = { 261, 3, 1344 }; - - string name = "Burli"; - CivilStatus status = CivilStatus.Single; - ReligiousGroupType religiousGroup = ReligiousGroupType.Protestant; - - var taxPerson = new TaxPerson - { - Name = name, - CivilStatus = status, - ReligiousGroupType = religiousGroup, - TaxableFederalIncome = 100_000, - TaxableIncome = 100_000, - TaxableWealth = 500_000, - }; - - // when - List> results = new (); - await foreach (var compareResult in fixture.Calculator.CompareIncomeAndWealthTaxAsync(taxPerson, bfsNumbers)) - { - results.Add(compareResult); - } - - Assert.All(results, m => Assert.True(m.IsRight)); - - List orderedResults = new(); - results.Iter(m => m.IfRight(r => orderedResults.Add(r))); - - // then - Snapshot.Match(orderedResults.OrderBy(item => item.MunicipalityName)); - } - } -} diff --git a/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/TaxComparerFixture.cs b/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/TaxComparerFixture.cs deleted file mode 100644 index 0b1002cb..00000000 --- a/src/Tax.Tools.Comparison/Tax.Tools.Comparison.Tests/TaxComparerFixture.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using PensionCoach.Tools.TaxCalculator; -using Tax.Data; - - -namespace Tax.Tools.Comparison.Tests -{ - public class TaxComparerFixture - where T : class - { - public ServiceProvider Provider { get; } - - public T Calculator { get; } - - public T Service { get; } - - public TaxComparerFixture() - { - var projectPath = Assembly.GetExecutingAssembly() - .Location.Split("src", StringSplitOptions.RemoveEmptyEntries) - .First(); - - var dbFile = Path.Combine(projectPath, @"src\Tax.Data\files\TaxDb.db"); - - var configurationDict = new Dictionary - { - {"ConnectionStrings:TaxDb", dbFile}, - }; - - IConfiguration configuration = new ConfigurationBuilder() - .AddInMemoryCollection(configurationDict) - .Build(); - - ServiceCollection coll = new ServiceCollection(); - - coll.AddLogging(); - coll.AddTaxCalculators(configuration); - coll.AddTaxData(configuration); - coll.AddTaxComparers(); - - Provider = coll.BuildServiceProvider(); - - Calculator = Provider.GetRequiredService(); - - Service = Provider.GetRequiredService(); - } - } -} diff --git a/src/Tax.Tools.Comparison/Tax.Tools.Comparison/Tax.Tools.Comparison.csproj b/src/Tax.Tools.Comparison/Tax.Tools.Comparison/Tax.Tools.Comparison.csproj deleted file mode 100644 index d00a82ed..00000000 --- a/src/Tax.Tools.Comparison/Tax.Tools.Comparison/Tax.Tools.Comparison.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - net7.0 - - - - - - - - - - - - - - diff --git a/src/Tax.Tools.Comparison/Tax.Tools.Comparison/TaxComparerServiceCollectionExtensions.cs b/src/Tax.Tools.Comparison/Tax.Tools.Comparison/TaxComparerServiceCollectionExtensions.cs deleted file mode 100644 index 85a10786..00000000 --- a/src/Tax.Tools.Comparison/Tax.Tools.Comparison/TaxComparerServiceCollectionExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Tax.Tools.Comparison.Abstractions; - -namespace Tax.Tools.Comparison; - -public static class TaxComparerServiceCollectionExtensions -{ - public static void AddTaxComparers(this IServiceCollection collection) - { - collection.AddTransient(); - } -} diff --git a/src/TaxCalculator.Abstractions/IAdminConnector.cs b/src/TaxCalculator.Abstractions/IAdminConnector.cs deleted file mode 100644 index d7ec4e23..00000000 --- a/src/TaxCalculator.Abstractions/IAdminConnector.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using PensionCoach.Tools.PostOpenApi.Models; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions; - -public interface IAdminConnector -{ - /// - /// Copy zip code data from stage table to data store. - /// - /// - Task PopulateWithZipCodeAsync(); - - /// - /// Populate data store containing tax data with ESTV tax location id. - /// This is one one time step which may repeated every time a municipality change is available. - /// - /// - /// - Task PopulateWithTaxLocationAsync(bool doClear); - - /// - /// Stage table with zip codes from Post OpenData API. - /// - /// Number of cases processed. - Task StagePlzTableAsync(); - - /// - /// Get all current zip (PLZ) information for Switzerland supplied by the Swiss Post. - /// - /// - IAsyncEnumerable GetAllZipCodesAsync(int limit); - - /// - /// Clean municipality name. E.g. removing canton postfix to avoid ambiguity. - /// - /// Number of cases processed. - Task CleanMunicipalityName(); -} diff --git a/src/TaxCalculator.Abstractions/IAggregatedBasisTaxCalculator.cs b/src/TaxCalculator.Abstractions/IAggregatedBasisTaxCalculator.cs deleted file mode 100644 index 2702fa43..00000000 --- a/src/TaxCalculator.Abstractions/IAggregatedBasisTaxCalculator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions -{ - public interface IAggregatedBasisTaxCalculator - { - Task> CalculateAsync( - int calculationYear, Canton canton, TaxPerson person); - } -} diff --git a/src/TaxCalculator.Abstractions/IBasisIncomeTaxCalculator.cs b/src/TaxCalculator.Abstractions/IBasisIncomeTaxCalculator.cs deleted file mode 100644 index 54f3b4c8..00000000 --- a/src/TaxCalculator.Abstractions/IBasisIncomeTaxCalculator.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions; - -public interface IBasisIncomeTaxCalculator -{ - Task> CalculateAsync( - int calculationYear, Canton canton, BasisTaxPerson person); -} diff --git a/src/TaxCalculator.Abstractions/IBasisWealthTaxCalculator.cs b/src/TaxCalculator.Abstractions/IBasisWealthTaxCalculator.cs deleted file mode 100644 index 2c39b357..00000000 --- a/src/TaxCalculator.Abstractions/IBasisWealthTaxCalculator.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace PensionCoach.Tools.TaxCalculator.Abstractions -{ - public interface IBasisWealthTaxCalculator : IBasisIncomeTaxCalculator - { - } -} diff --git a/src/TaxCalculator.Abstractions/ICapitalBenefitTaxCalculator.cs b/src/TaxCalculator.Abstractions/ICapitalBenefitTaxCalculator.cs deleted file mode 100644 index f6e9756b..00000000 --- a/src/TaxCalculator.Abstractions/ICapitalBenefitTaxCalculator.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; - - -namespace PensionCoach.Tools.TaxCalculator.Abstractions -{ - public interface ICapitalBenefitTaxCalculator - { - Task> CalculateAsync( - int calculationYear, - int taxId, - Canton canton, - CapitalBenefitTaxPerson person); - } -} diff --git a/src/TaxCalculator.Abstractions/ICheckSettingsConnector.cs b/src/TaxCalculator.Abstractions/ICheckSettingsConnector.cs deleted file mode 100644 index b29663d7..00000000 --- a/src/TaxCalculator.Abstractions/ICheckSettingsConnector.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions; - -public interface ICheckSettingsConnector -{ - Task> GetAsync(); -} diff --git a/src/TaxCalculator.Abstractions/IChurchTaxCalculator.cs b/src/TaxCalculator.Abstractions/IChurchTaxCalculator.cs deleted file mode 100644 index 26231447..00000000 --- a/src/TaxCalculator.Abstractions/IChurchTaxCalculator.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -using Tax.Data.Abstractions.Models; - - -namespace PensionCoach.Tools.TaxCalculator.Abstractions -{ - public interface IChurchTaxCalculator - { - /// - /// Calculates the church tax for given year and municipality. - /// - /// The calculation year. - /// The municipality. - /// The person. - /// The tax result. - /// - Task> CalculateAsync( - int calculationYear, - int municipality, - ChurchTaxPerson person, - AggregatedBasisTaxResult taxResult); - - /// - /// Calculates the church tax with a given tax rate record. - /// - /// The person. - /// The tax rate entity. - /// The tax result. - /// - Task> CalculateAsync( - ChurchTaxPerson person, - TaxRateEntity taxRateEntity, - AggregatedBasisTaxResult taxResult); - } -} diff --git a/src/TaxCalculator.Abstractions/IFederalCapitalBenefitTaxCalculator.cs b/src/TaxCalculator.Abstractions/IFederalCapitalBenefitTaxCalculator.cs deleted file mode 100644 index ddc82337..00000000 --- a/src/TaxCalculator.Abstractions/IFederalCapitalBenefitTaxCalculator.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - - -namespace PensionCoach.Tools.TaxCalculator.Abstractions -{ - public interface IFederalCapitalBenefitTaxCalculator - { - Task> CalculateAsync( - int calculationYear, FederalTaxPerson person); - } -} diff --git a/src/TaxCalculator.Abstractions/IFederalTaxCalculator.cs b/src/TaxCalculator.Abstractions/IFederalTaxCalculator.cs deleted file mode 100644 index a6b3a28d..00000000 --- a/src/TaxCalculator.Abstractions/IFederalTaxCalculator.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - - -namespace PensionCoach.Tools.TaxCalculator.Abstractions -{ - public interface IFederalTaxCalculator - { - Task> CalculateAsync(int calculationYear, FederalTaxPerson person); - } -} diff --git a/src/TaxCalculator.Abstractions/IFullCapitalBenefitTaxCalculator.cs b/src/TaxCalculator.Abstractions/IFullCapitalBenefitTaxCalculator.cs deleted file mode 100644 index 69fcd371..00000000 --- a/src/TaxCalculator.Abstractions/IFullCapitalBenefitTaxCalculator.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions -{ - public interface IFullCapitalBenefitTaxCalculator - { - Task> CalculateAsync( - int calculationYear, - MunicipalityModel municipality, - CapitalBenefitTaxPerson person, - bool withMaxAvailableCalculationYear = false); - } -} diff --git a/src/TaxCalculator.Abstractions/IFullWealthAndIncomeTaxCalculator.cs b/src/TaxCalculator.Abstractions/IFullWealthAndIncomeTaxCalculator.cs deleted file mode 100644 index 872ce817..00000000 --- a/src/TaxCalculator.Abstractions/IFullWealthAndIncomeTaxCalculator.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions; - -public interface IFullWealthAndIncomeTaxCalculator -{ - Task> CalculateAsync( - int calculationYear, MunicipalityModel municipality, TaxPerson person, bool withMaxAvailableCalculationYear = false); -} diff --git a/src/TaxCalculator.Abstractions/IIncomeTaxCalculator.cs b/src/TaxCalculator.Abstractions/IIncomeTaxCalculator.cs deleted file mode 100644 index a08fe177..00000000 --- a/src/TaxCalculator.Abstractions/IIncomeTaxCalculator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions -{ - public interface IIncomeTaxCalculator - { - Task> CalculateAsync( - int calculationYear, int municipalityId, Canton canton, TaxPerson person); - } -} diff --git a/src/TaxCalculator.Abstractions/IPollTaxCalculator.cs b/src/TaxCalculator.Abstractions/IPollTaxCalculator.cs deleted file mode 100644 index fbb82f5e..00000000 --- a/src/TaxCalculator.Abstractions/IPollTaxCalculator.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -using Tax.Data.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions -{ - public interface IPollTaxCalculator - { - Task> CalculateAsync( - int calculationYear, - int municipalityId, - Canton canton, - PollTaxPerson person); - - Task> CalculateAsync( - int calculationYear, - Canton canton, - PollTaxPerson person, - TaxRateEntity taxRateEntity); - } -} diff --git a/src/TaxCalculator.Abstractions/IStateTaxCalculator.cs b/src/TaxCalculator.Abstractions/IStateTaxCalculator.cs deleted file mode 100644 index f27dda8d..00000000 --- a/src/TaxCalculator.Abstractions/IStateTaxCalculator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - - -namespace PensionCoach.Tools.TaxCalculator.Abstractions -{ - public interface IStateTaxCalculator - { - /// - /// Calculates state (canton) tax asynchronously. - /// - /// The calculation year. - /// The BFS municipality identifier. - /// The canton. - /// The person. - /// - Task> CalculateAsync( - int calculationYear, - int municipalityId, - Canton canton, - TaxPerson person); - } -} diff --git a/src/TaxCalculator.Abstractions/ITaxCalculatorConnector.cs b/src/TaxCalculator.Abstractions/ITaxCalculatorConnector.cs deleted file mode 100644 index a1131528..00000000 --- a/src/TaxCalculator.Abstractions/ITaxCalculatorConnector.cs +++ /dev/null @@ -1,17 +0,0 @@ -using LanguageExt; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using System.Threading.Tasks; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions; - -public interface ITaxCalculatorConnector -{ - Task> CalculateAsync( - int calculationYear, int bfsMunicipalityId, TaxPerson person, bool withMaxAvailableCalculationYear = false); - - Task> CalculateAsync( - int calculationYear, int bfsMunicipalityId, CapitalBenefitTaxPerson person, bool withMaxAvailableCalculationYear = false); - - Task GetSupportedTaxYears(); -} diff --git a/src/TaxCalculator.Abstractions/ITaxSupportedYearProvider.cs b/src/TaxCalculator.Abstractions/ITaxSupportedYearProvider.cs deleted file mode 100644 index f3a5d50b..00000000 --- a/src/TaxCalculator.Abstractions/ITaxSupportedYearProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace PensionCoach.Tools.TaxCalculator.Abstractions; - -public interface ITaxSupportedYearProvider -{ - int[] GetSupportedTaxYears(); - - int MapToSupportedYear(int taxYear); -} diff --git a/src/TaxCalculator.Abstractions/IWealthTaxCalculator.cs b/src/TaxCalculator.Abstractions/IWealthTaxCalculator.cs deleted file mode 100644 index 3a14ad00..00000000 --- a/src/TaxCalculator.Abstractions/IWealthTaxCalculator.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions -{ - public interface IWealthTaxCalculator - { - Task> CalculateAsync( - int calculationYear, int municipalityId, Canton canton, TaxPerson person); - } -} diff --git a/src/TaxCalculator.Abstractions/Models/AggregatedBasisTaxResult.cs b/src/TaxCalculator.Abstractions/Models/AggregatedBasisTaxResult.cs deleted file mode 100644 index 49a0c3ca..00000000 --- a/src/TaxCalculator.Abstractions/Models/AggregatedBasisTaxResult.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models -{ - public class AggregatedBasisTaxResult - { - public BasisTaxResult IncomeTax { get; set; } - public BasisTaxResult WealthTax { get; set; } - public decimal Total => IncomeTax.TaxAmount + WealthTax.TaxAmount; - } -} \ No newline at end of file diff --git a/src/TaxCalculator.Abstractions/Models/ChurchTaxResult.cs b/src/TaxCalculator.Abstractions/Models/ChurchTaxResult.cs deleted file mode 100644 index e1a58bfd..00000000 --- a/src/TaxCalculator.Abstractions/Models/ChurchTaxResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -using LanguageExt; - - -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models -{ - public class ChurchTaxResult - { - public Option TaxAmount { get; set; } - - public Option TaxAmountPartner { get; set; } - - public decimal TaxRate { get; set; } - } -} \ No newline at end of file diff --git a/src/TaxCalculator.Abstractions/Models/FullTaxResult.cs b/src/TaxCalculator.Abstractions/Models/FullTaxResult.cs deleted file mode 100644 index 3c55ac73..00000000 --- a/src/TaxCalculator.Abstractions/Models/FullTaxResult.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models -{ - public class FullTaxResult - { - public StateTaxResult StateTaxResult { get; set; } - public BasisTaxResult FederalTaxResult { get; set; } - public decimal TotalTaxAmount => StateTaxResult.TotalTaxAmount + FederalTaxResult.TaxAmount; - } -} \ No newline at end of file diff --git a/src/TaxCalculator.Abstractions/Models/Person/BasisTaxPerson.cs b/src/TaxCalculator.Abstractions/Models/Person/BasisTaxPerson.cs deleted file mode 100644 index bc2d3b35..00000000 --- a/src/TaxCalculator.Abstractions/Models/Person/BasisTaxPerson.cs +++ /dev/null @@ -1,8 +0,0 @@ -using PensionCoach.Tools.CommonTypes.Tax; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - -public record BasisTaxPerson : TaxPersonBasic -{ - public decimal TaxableAmount { get; set; } -} diff --git a/src/TaxCalculator.Abstractions/Models/Person/ChurchTaxPerson.cs b/src/TaxCalculator.Abstractions/Models/Person/ChurchTaxPerson.cs deleted file mode 100644 index ad4c82c1..00000000 --- a/src/TaxCalculator.Abstractions/Models/Person/ChurchTaxPerson.cs +++ /dev/null @@ -1,5 +0,0 @@ -using PensionCoach.Tools.CommonTypes.Tax; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - -public record ChurchTaxPerson : TaxPersonBasic; diff --git a/src/TaxCalculator.Abstractions/Models/Person/FederalTaxPerson.cs b/src/TaxCalculator.Abstractions/Models/Person/FederalTaxPerson.cs deleted file mode 100644 index eab948fb..00000000 --- a/src/TaxCalculator.Abstractions/Models/Person/FederalTaxPerson.cs +++ /dev/null @@ -1,9 +0,0 @@ -using PensionCoach.Tools.CommonTypes.Tax; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person -{ - public record FederalTaxPerson : TaxPersonBasic - { - public decimal TaxableAmount { get; set; } - } -} diff --git a/src/TaxCalculator.Abstractions/Models/Person/PollTaxPerson.cs b/src/TaxCalculator.Abstractions/Models/Person/PollTaxPerson.cs deleted file mode 100644 index d7140167..00000000 --- a/src/TaxCalculator.Abstractions/Models/Person/PollTaxPerson.cs +++ /dev/null @@ -1,7 +0,0 @@ -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person -{ - public record PollTaxPerson : TaxPersonBasic; -} diff --git a/src/TaxCalculator.Abstractions/Models/PollTaxResult.cs b/src/TaxCalculator.Abstractions/Models/PollTaxResult.cs deleted file mode 100644 index 26e47867..00000000 --- a/src/TaxCalculator.Abstractions/Models/PollTaxResult.cs +++ /dev/null @@ -1,11 +0,0 @@ -using LanguageExt; - -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models -{ - public class PollTaxResult - { - public Option CantonTaxAmount { get; set; } - - public Option MunicipalityTaxAmount { get; set; } - } -} diff --git a/src/TaxCalculator.Abstractions/Models/SingleTaxResult.cs b/src/TaxCalculator.Abstractions/Models/SingleTaxResult.cs deleted file mode 100644 index f38fc372..00000000 --- a/src/TaxCalculator.Abstractions/Models/SingleTaxResult.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models -{ - public class SingleTaxResult - { - public BasisTaxResult BasisTaxAmount { get; set; } - public decimal CantonRate { get; set; } - public decimal MunicipalityRate { get; set; } - public decimal MunicipalityTaxAmount => MunicipalityRate / 100M * BasisTaxAmount.TaxAmount; - public decimal CantonTaxAmount => CantonRate / 100M * BasisTaxAmount.TaxAmount; - public decimal TotalTaxAmount => MunicipalityTaxAmount + CantonTaxAmount; - } -} \ No newline at end of file diff --git a/src/TaxCalculator.Abstractions/Models/TariffType.cs b/src/TaxCalculator.Abstractions/Models/TariffType.cs deleted file mode 100644 index d4b03f75..00000000 --- a/src/TaxCalculator.Abstractions/Models/TariffType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace PensionCoach.Tools.TaxCalculator.Abstractions.Models -{ - public enum TariffType - { - Undefined = 0, - Base = 1, - Married = 2 - } -} \ No newline at end of file diff --git a/src/TaxCalculator.Abstractions/TaxCalculator.Abstractions.csproj b/src/TaxCalculator.Abstractions/TaxCalculator.Abstractions.csproj deleted file mode 100644 index a5af3db7..00000000 --- a/src/TaxCalculator.Abstractions/TaxCalculator.Abstractions.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - net7.0 - PensionCoach.Tools.TaxCalculator.Abstractions - PensionCoach.Tools.TaxCalculator.Abstractions - - - - - - - - - - - - - - - - - - diff --git a/src/TaxCalculator.Integration.Tests/TaxCalculator.Integration.Tests.csproj b/src/TaxCalculator.Integration.Tests/TaxCalculator.Integration.Tests.csproj index e34c6bd2..5cc5e10d 100644 --- a/src/TaxCalculator.Integration.Tests/TaxCalculator.Integration.Tests.csproj +++ b/src/TaxCalculator.Integration.Tests/TaxCalculator.Integration.Tests.csproj @@ -1,22 +1,22 @@  - net7.0 + net8.0 false - - - - - - - + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/TaxCalculator.Integration.Tests/TaxCalculatorIntegrationTests.cs b/src/TaxCalculator.Integration.Tests/TaxCalculatorIntegrationTests.cs index affe8b17..ea7af59f 100644 --- a/src/TaxCalculator.Integration.Tests/TaxCalculatorIntegrationTests.cs +++ b/src/TaxCalculator.Integration.Tests/TaxCalculatorIntegrationTests.cs @@ -3,6 +3,9 @@ using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; +using Application.Tax.Proprietary.Models; +using Domain.Enums; +using Domain.Models.Tax; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; @@ -25,7 +28,13 @@ public TaxCalculatorIntegrationTests() new WebHostBuilder() .ConfigureAppConfiguration((_, builder) => { - builder.AddJsonFile("appsettings.integration.json"); + // add memory collection + builder.AddInMemoryCollection(new Dictionary + { + {"ApplicationMode", "Estv"}, + }); + + builder.AddJsonFile("appsettings.integration.json", false); }) .UseStartup()); @@ -44,4 +53,33 @@ public async Task Get_Full_Tax_Supported_Municipalities() // then Snapshot.Match(result); } + + [Fact(DisplayName = "Marginal Tax Curve for Capital Benefits")] + public async Task Get_Marginal_Tax_Curve_For_Capital_Benefits() + { + // given + var request = new + { + Name = "Marginal Tax Curve", + CalculationYear = 2023, + CivilStatus = CivilStatus.Married, + ReligiousGroup = ReligiousGroupType.Protestant, + PartnerReligiousGroup = ReligiousGroupType.Protestant, + BfsMunicipalityId = 2526, + TaxableAmount = 810_000, + LowerSalaryLimit = 0, + UpperSalaryLimit = 1_000_000, + numberOfSamples = 25 + }; + + // when + var response = await client.PostAsJsonAsync("marginaltaxcurve/capitalbenefits", request); + + // then + var result = response.EnsureSuccessStatusCode(); + + var content = await result.Content.ReadFromJsonAsync(); + + Snapshot.Match(content); + } } diff --git a/src/TaxCalculator.Integration.Tests/__snapshots__/TaxCalculatorIntegrationTests.Get_Marginal_Tax_Curve_For_Capital_Benefits.snap b/src/TaxCalculator.Integration.Tests/__snapshots__/TaxCalculatorIntegrationTests.Get_Marginal_Tax_Curve_For_Capital_Benefits.snap new file mode 100644 index 00000000..31f94fe7 --- /dev/null +++ b/src/TaxCalculator.Integration.Tests/__snapshots__/TaxCalculatorIntegrationTests.Get_Marginal_Tax_Curve_For_Capital_Benefits.snap @@ -0,0 +1,139 @@ +{ + "CurrentMarginalTaxRate": { + "Salary": 810000.0, + "Rate": 0.089000000000000000000000002, + "TotalTaxAmount": 64355.999999999999999999999999 + }, + "MarginalTaxCurve": [ + { + "Salary": 0.0, + "Rate": 0.0, + "TotalTaxAmount": 0.0 + }, + { + "Salary": 41666.0, + "Rate": 0.038, + "TotalTaxAmount": 546.000 + }, + { + "Salary": 83333.0, + "Rate": 0.0630000000000000000000000, + "TotalTaxAmount": 2841.0000000000000000000000000 + }, + { + "Salary": 125000.0, + "Rate": 0.0710000000000000000000000, + "TotalTaxAmount": 5612.0000000000000000000000001 + }, + { + "Salary": 166666.0, + "Rate": 0.082000000000000000000000, + "TotalTaxAmount": 8886.000000000000000000000000 + }, + { + "Salary": 208333.0, + "Rate": 0.088000000000000000000000, + "TotalTaxAmount": 12458.000000000000000000000000 + }, + { + "Salary": 250000.0, + "Rate": 0.088000000000000000000000, + "TotalTaxAmount": 16136.000000000000000000000000 + }, + { + "Salary": 291666.0, + "Rate": 0.088000000000000000000000, + "TotalTaxAmount": 19810.000000000000000000000000 + }, + { + "Salary": 333333.0, + "Rate": 0.089000000000000000000000, + "TotalTaxAmount": 23487.000000000000000000000000 + }, + { + "Salary": 375000.0, + "Rate": 0.089000000000000000000000, + "TotalTaxAmount": 27165.000000000000000000000000 + }, + { + "Salary": 416666.0, + "Rate": 0.089000000000000000000000, + "TotalTaxAmount": 30841.000000000000000000000000 + }, + { + "Salary": 458333.0, + "Rate": 0.089000000000000000000000, + "TotalTaxAmount": 34518.000000000000000000000000 + }, + { + "Salary": 500000.0, + "Rate": 0.089000000000000000000000, + "TotalTaxAmount": 38196.000000000000000000000000 + }, + { + "Salary": 541666.0, + "Rate": 0.089000000000000000000000, + "TotalTaxAmount": 41871.000000000000000000000000 + }, + { + "Salary": 583333.0, + "Rate": 0.089000000000000000000000002, + "TotalTaxAmount": 45547.999999999999999999999999 + }, + { + "Salary": 625000.0, + "Rate": 0.089000000000000000000000002, + "TotalTaxAmount": 49032.000000000000000000000000 + }, + { + "Salary": 666666.0, + "Rate": 0.089000000000000000000000002, + "TotalTaxAmount": 52481.000000000000000000000000 + }, + { + "Salary": 708333.0, + "Rate": 0.089000000000000000000000002, + "TotalTaxAmount": 55933.000000000000000000000000 + }, + { + "Salary": 750000.0, + "Rate": 0.089000000000000000000000002, + "TotalTaxAmount": 59386.000000000000000000000000 + }, + { + "Salary": 791666.0, + "Rate": 0.089000000000000000000000002, + "TotalTaxAmount": 62835.000000000000000000000000 + }, + { + "Salary": 810000.0, + "Rate": 0.089000000000000000000000002, + "TotalTaxAmount": 64355.999999999999999999999999 + }, + { + "Salary": 833333.0, + "Rate": 0.089000000000000000000000002, + "TotalTaxAmount": 66288.000000000000000000000000 + }, + { + "Salary": 875000.0, + "Rate": 0.089000000000000000000000002, + "TotalTaxAmount": 69740.000000000000000000000000 + }, + { + "Salary": 916666.0, + "Rate": 0.089000000000000000000000002, + "TotalTaxAmount": 73178.000000000000000000000000 + }, + { + "Salary": 958333.0, + "Rate": 0.089000000000000000000000002, + "TotalTaxAmount": 76504.000000000000000000000001 + }, + { + "Salary": 1000000.0, + "Rate": 0.089000000000000000000000002, + "TotalTaxAmount": 79832.00000000000000000000000 + } + ] +} diff --git a/src/TaxCalculator.Tests/AggregatedBasisTaxCalculatorTests.cs b/src/TaxCalculator.Tests/AggregatedBasisTaxCalculatorTests.cs index c1858dd1..7d802369 100644 --- a/src/TaxCalculator.Tests/AggregatedBasisTaxCalculatorTests.cs +++ b/src/TaxCalculator.Tests/AggregatedBasisTaxCalculatorTests.cs @@ -1,8 +1,8 @@ using System.Threading.Tasks; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax; using FluentAssertions; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.Tests/BasisWealthTaxCalculatorTests.cs b/src/TaxCalculator.Tests/BasisWealthTaxCalculatorTests.cs index 2653c608..6fbd4b43 100644 --- a/src/TaxCalculator.Tests/BasisWealthTaxCalculatorTests.cs +++ b/src/TaxCalculator.Tests/BasisWealthTaxCalculatorTests.cs @@ -1,9 +1,9 @@ using System; using System.Threading.Tasks; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax.Person; using FluentAssertions; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.Tests/CantonBasisIncomeTaxCalculatorTests.cs b/src/TaxCalculator.Tests/CantonBasisIncomeTaxCalculatorTests.cs index 7ca438cf..7254cb56 100644 --- a/src/TaxCalculator.Tests/CantonBasisIncomeTaxCalculatorTests.cs +++ b/src/TaxCalculator.Tests/CantonBasisIncomeTaxCalculatorTests.cs @@ -1,10 +1,9 @@ using System; using System.Threading.Tasks; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax.Person; using FluentAssertions; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.Tests/CapitalBenefitTaxCalculatorTests.cs b/src/TaxCalculator.Tests/CapitalBenefitTaxCalculatorTests.cs index 6774cf41..21a4848d 100644 --- a/src/TaxCalculator.Tests/CapitalBenefitTaxCalculatorTests.cs +++ b/src/TaxCalculator.Tests/CapitalBenefitTaxCalculatorTests.cs @@ -1,8 +1,8 @@ using System; using System.Threading.Tasks; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.Tests/ChurchTaxCalculatorTests.cs b/src/TaxCalculator.Tests/ChurchTaxCalculatorTests.cs index d0c7ae61..47d444be 100644 --- a/src/TaxCalculator.Tests/ChurchTaxCalculatorTests.cs +++ b/src/TaxCalculator.Tests/ChurchTaxCalculatorTests.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Threading.Tasks; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; +using Application.Tax.Proprietary.Abstractions.Models; +using Application.Tax.Proprietary.Contracts; +using Application.Tax.Proprietary.Models; +using Domain.Enums; +using Domain.Models.Tax.Person; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.Tests/DefaultBasisIncomeTaxCalculatorTests.cs b/src/TaxCalculator.Tests/DefaultBasisIncomeTaxCalculatorTests.cs index 453a73a7..a1374f28 100644 --- a/src/TaxCalculator.Tests/DefaultBasisIncomeTaxCalculatorTests.cs +++ b/src/TaxCalculator.Tests/DefaultBasisIncomeTaxCalculatorTests.cs @@ -1,9 +1,9 @@ using System; using System.Threading.Tasks; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax.Person; using FluentAssertions; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.Tests/FederalCapitalBenefitTaxCalculatorTests.cs b/src/TaxCalculator.Tests/FederalCapitalBenefitTaxCalculatorTests.cs index b5ce40d8..301e4bba 100644 --- a/src/TaxCalculator.Tests/FederalCapitalBenefitTaxCalculatorTests.cs +++ b/src/TaxCalculator.Tests/FederalCapitalBenefitTaxCalculatorTests.cs @@ -1,8 +1,8 @@ -using System; +using System; using System.Threading.Tasks; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax.Person; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.Tests/FederalTaxCalculatorTests.cs b/src/TaxCalculator.Tests/FederalTaxCalculatorTests.cs index bf322a87..5125c1f2 100644 --- a/src/TaxCalculator.Tests/FederalTaxCalculatorTests.cs +++ b/src/TaxCalculator.Tests/FederalTaxCalculatorTests.cs @@ -1,9 +1,9 @@ using System; using System.Threading.Tasks; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax.Person; using FluentAssertions; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.Tests/FullCapitalBenefitTaxCalculatorTests.cs b/src/TaxCalculator.Tests/FullCapitalBenefitTaxCalculatorTests.cs index a4023882..794e1ce8 100644 --- a/src/TaxCalculator.Tests/FullCapitalBenefitTaxCalculatorTests.cs +++ b/src/TaxCalculator.Tests/FullCapitalBenefitTaxCalculatorTests.cs @@ -1,9 +1,9 @@ using System; using System.Threading.Tasks; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; +using Application.Tax.Contracts; +using Domain.Enums; +using Domain.Models.Municipality; +using Domain.Models.Tax; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.Tests/FullTaxCalculatorTests.cs b/src/TaxCalculator.Tests/FullTaxCalculatorTests.cs index 97d834b7..fa5d4713 100644 --- a/src/TaxCalculator.Tests/FullTaxCalculatorTests.cs +++ b/src/TaxCalculator.Tests/FullTaxCalculatorTests.cs @@ -1,10 +1,10 @@ using System; using System.Threading.Tasks; +using Application.Tax.Contracts; +using Domain.Enums; +using Domain.Models.Municipality; +using Domain.Models.Tax; using FluentAssertions; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.Tests/IncomeTaxCalculatorTests.cs b/src/TaxCalculator.Tests/IncomeTaxCalculatorTests.cs index 44bc2572..b4720133 100644 --- a/src/TaxCalculator.Tests/IncomeTaxCalculatorTests.cs +++ b/src/TaxCalculator.Tests/IncomeTaxCalculatorTests.cs @@ -1,8 +1,8 @@ using System.Threading.Tasks; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax; using FluentAssertions; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.Tests/MarginalTax/MarginalTaxCurveCalculatorTests.cs b/src/TaxCalculator.Tests/MarginalTax/MarginalTaxCurveCalculatorTests.cs index 45e84a9f..7cb70529 100644 --- a/src/TaxCalculator.Tests/MarginalTax/MarginalTaxCurveCalculatorTests.cs +++ b/src/TaxCalculator.Tests/MarginalTax/MarginalTaxCurveCalculatorTests.cs @@ -1,9 +1,9 @@ using System; using System.Threading.Tasks; +using Application.Features.MarginalTaxCurve; +using Domain.Enums; +using Domain.Models.Tax; using FluentAssertions; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.Tests/MunicipalityConnectorTests.cs b/src/TaxCalculator.Tests/MunicipalityConnectorTests.cs index 04d81063..8930e711 100644 --- a/src/TaxCalculator.Tests/MunicipalityConnectorTests.cs +++ b/src/TaxCalculator.Tests/MunicipalityConnectorTests.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.TaxCalculator.Abstractions; +using Application.Municipality; +using Domain.Enums; +using Domain.Models.Municipality; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.Tests/PollTaxCalculatorTests.cs b/src/TaxCalculator.Tests/PollTaxCalculatorTests.cs index 20211014..48979a80 100644 --- a/src/TaxCalculator.Tests/PollTaxCalculatorTests.cs +++ b/src/TaxCalculator.Tests/PollTaxCalculatorTests.cs @@ -1,10 +1,9 @@ -using System; +using System; using System.Threading.Tasks; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax.Person; using FluentAssertions; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; using Snapshooter.Xunit; using Xunit; @@ -49,4 +48,4 @@ public async Task ShouldCalculatePollTax( Snapshot.Match(result,$"Theory Poll Tax {calculationYear}{civilStatusCode}{canton}"); } } -} \ No newline at end of file +} diff --git a/src/TaxCalculator.Tests/StateTaxCalculatorTests.cs b/src/TaxCalculator.Tests/StateTaxCalculatorTests.cs index 525c17b0..7087a9cf 100644 --- a/src/TaxCalculator.Tests/StateTaxCalculatorTests.cs +++ b/src/TaxCalculator.Tests/StateTaxCalculatorTests.cs @@ -1,9 +1,9 @@ using System; using System.Threading.Tasks; +using Application.Tax.Proprietary.Contracts; +using Domain.Enums; +using Domain.Models.Tax; using FluentAssertions; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.Tests/TaxCalculator.Tests.csproj b/src/TaxCalculator.Tests/TaxCalculator.Tests.csproj index 3d4ae82b..21e91f57 100644 --- a/src/TaxCalculator.Tests/TaxCalculator.Tests.csproj +++ b/src/TaxCalculator.Tests/TaxCalculator.Tests.csproj @@ -1,32 +1,32 @@  - net7.0 + net8.0 false - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + diff --git a/src/TaxCalculator.Tests/TaxCalculatorFixture.cs b/src/TaxCalculator.Tests/TaxCalculatorFixture.cs index c72fec79..3d707d68 100644 --- a/src/TaxCalculator.Tests/TaxCalculatorFixture.cs +++ b/src/TaxCalculator.Tests/TaxCalculatorFixture.cs @@ -3,10 +3,11 @@ using System.IO; using System.Linq; using System.Reflection; +using Application.Features.FullTaxCalculation; +using Infrastructure.Configuration; +using Infrastructure.Tax.Data; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using PensionCoach.Tools.TaxCalculator; -using Tax.Data; namespace TaxCalculator.Tests { @@ -40,7 +41,7 @@ public TaxCalculatorFixture() coll.AddLogging(); coll.AddTaxData(configuration); - coll.AddTaxCalculators(configuration); + coll.AddTaxCalculators(configuration.GetApplicationMode()); Provider = coll.BuildServiceProvider(); diff --git a/src/TaxCalculator.Tests/WealthTaxCalculatorTests.cs b/src/TaxCalculator.Tests/WealthTaxCalculatorTests.cs index f270d1ec..cc4cdb4f 100644 --- a/src/TaxCalculator.Tests/WealthTaxCalculatorTests.cs +++ b/src/TaxCalculator.Tests/WealthTaxCalculatorTests.cs @@ -1,12 +1,11 @@ using System; using System.Threading.Tasks; +using Application.Tax.Proprietary.Contracts; +using Application.Tax.Proprietary.Models; +using Domain.Enums; +using Domain.Models.Tax; using FluentAssertions; using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; using Snapshooter.Xunit; using Xunit; diff --git a/src/TaxCalculator.WebApi/Controllers/AdminController.cs b/src/TaxCalculator.WebApi/Controllers/AdminController.cs index a4509f0c..ae0f5578 100644 --- a/src/TaxCalculator.WebApi/Controllers/AdminController.cs +++ b/src/TaxCalculator.WebApi/Controllers/AdminController.cs @@ -1,100 +1,100 @@ -using System.Collections.Generic; -using PensionCoach.Tools.PostOpenApi.Models; - -namespace TaxCalculator.WebApi.Controllers; - -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using PensionCoach.Tools.TaxCalculator.Abstractions; +using System.Collections.Generic; +using Domain.Models.Municipality; +using Infrastructure.DataStaging; -[Produces("application/json")] -[ApiController] -[Route("api/admin")] -public class AdminController : ControllerBase +namespace TaxCalculator.WebApi.Controllers { - private readonly IAdminConnector adminConnector; - - public AdminController(IAdminConnector adminConnector) + [Produces("application/json")] + [ApiController] + [Route("api/admin")] + public class AdminController : ControllerBase { - this.adminConnector = adminConnector; - } + private readonly IDataStagingConnector adminConnector; - /// - /// Populate internal data store with ESTV tax location id. - /// - /// - /// Gemeindeverzeichnis mit interner ESTV Steuer-Id anreichern. - /// - [HttpPost] - [Route("tax/populate/{clear:bool}")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> PopulateTaxLocation(bool clear) - { - int count = await adminConnector.PopulateWithTaxLocationAsync(clear); - - return Ok(count); - } + public AdminController(IDataStagingConnector adminConnector) + { + this.adminConnector = adminConnector; + } - /// - /// Populate internal data store with all Swiss zip codes supplied by the Swiss Post Open Api. - /// - /// - /// Vollständiges PLZ-Verzeichnis bereitgestellt von der Post abspeichern. - /// - [HttpPost] - [Route("zip/populate")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> PopulateZipCodes() - { - int count = await adminConnector.PopulateWithZipCodeAsync(); + /// + /// Populate internal data store with ESTV tax location id. + /// + /// + /// Gemeindeverzeichnis mit interner ESTV Steuer-Id anreichern. + /// + [HttpPost] + [Route("tax/populate/{clear:bool}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> PopulateTaxLocation(bool clear) + { + int count = await adminConnector.PopulateWithTaxLocationAsync(clear); - return Ok(count); - } + return Ok(count); + } - /// - /// All Swiss zip codes supplied by the Swiss Post Open Api. - /// - /// - /// Vollständiges PLZ-Verzeichnis bereitgestellt von der Post. - /// - [HttpGet] - [Route("zip")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetZipCodes() - { - List result = new List(); - await foreach (var model in adminConnector.GetAllZipCodesAsync(int.MaxValue)) + /// + /// Populate internal data store with all Swiss zip codes supplied by the Swiss Post Open Api. + /// + /// + /// Vollständiges PLZ-Verzeichnis bereitgestellt von der Post abspeichern. + /// + [HttpPost] + [Route("zip/populate")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> PopulateZipCodes() { - result.Add(model); + int count = await adminConnector.PopulateWithZipCodeAsync(); + + return Ok(count); } - return Ok(result); - } + /// + /// All Swiss zip codes supplied by the Swiss Post Open Api. + /// + /// + /// Vollständiges PLZ-Verzeichnis bereitgestellt von der Post. + /// + [HttpGet] + [Route("zip")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> GetZipCodes() + { + List result = new List(); + await foreach (var model in adminConnector.GetAllZipCodesAsync(int.MaxValue)) + { + result.Add(model); + } - /// - /// Populate internal data store with all Swiss zip codes supplied by the Swiss Post Open Api. - /// - /// - /// Vollständiges PLZ-Verzeichnis bereitgestellt von der Post abspeichern. - /// - [HttpPost] - [Route("zip/stage")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> PopulateStageMunicipalityTable() - { - int count = await adminConnector.StagePlzTableAsync(); + return Ok(result); + } - return Ok(count); - } + /// + /// Populate internal data store with all Swiss zip codes supplied by the Swiss Post Open Api. + /// + /// + /// Vollständiges PLZ-Verzeichnis bereitgestellt von der Post abspeichern. + /// + [HttpPost] + [Route("zip/stage")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> PopulateStageMunicipalityTable() + { + int count = await adminConnector.StagePlzTableAsync(); - [HttpPost] - [Route("tax/clean")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> CleanMunicipalityNames() - { - int count = await adminConnector.CleanMunicipalityName(); + return Ok(count); + } - return Ok(count); + [HttpPost] + [Route("tax/clean")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> CleanMunicipalityNames() + { + int count = await adminConnector.CleanMunicipalityName(); + + return Ok(count); + } } } diff --git a/src/TaxCalculator.WebApi/Controllers/BvgCalculatorController.cs b/src/TaxCalculator.WebApi/Controllers/BvgCalculatorController.cs index 31a7d078..305716b3 100644 --- a/src/TaxCalculator.WebApi/Controllers/BvgCalculatorController.cs +++ b/src/TaxCalculator.WebApi/Controllers/BvgCalculatorController.cs @@ -1,80 +1,58 @@ -using System.Threading.Tasks; +using Application.Bvg; +using Domain.Models.Bvg; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using PensionCoach.Tools.BvgCalculator; -using PensionCoach.Tools.BvgCalculator.Models; -using TaxCalculator.WebApi.Models.Bvg; +using PensionCoach.Tools.CommonTypes.Bvg; -namespace TaxCalculator.WebApi.Controllers +namespace TaxCalculator.WebApi.Controllers; + +[Produces("application/json")] +[ApiController] +[Route("api/calculator/bvg")] +public class BvgCalculatorController(IBvgCalculator bvgCalculator) : ControllerBase { - [Produces("application/json")] - [ApiController] - [Route("api/calculator/bvg")] - public class BvgCalculatorController : ControllerBase + /// + /// Calculates the BVG benefits at retirement date of the person. + /// + /// The request includes details about the person. + /// Calculation results. + /// If calculation is successful. + /// + /// If request is incomplete or cannot be validated. + /// + /// + /// Berechnet die BVG Leistungen zum ordentlichen Pensionierungsalter. + /// + [HttpPost] + [Route("benefits")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public ActionResult Calculate(BvgCalculationRequest request) { - private readonly IBvgCalculator bvgCalculator; - private readonly ILogger logger; - - public BvgCalculatorController( - IBvgCalculator bvgCalculator, - ILogger logger) + if (request == null) { - this.bvgCalculator = bvgCalculator; - this.logger = logger; + return BadRequest(nameof(request)); } - /// - /// Calculates the BVG benefits at retirement date of the person. - /// - /// The request includes details about the person. - /// Calculation results. - /// If calculation is successful. - /// - /// If request is incomplete or cannot be validated. - /// - /// - /// Berechnet die BVG Leistungen zum ordentlichen Pensionierungsalter. - /// - [HttpPost] - [Route("benefits")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task> Calculate(BvgCalculationRequest request) - { - if (request == null) - { - return BadRequest(nameof(request)); - } + var bvgPerson = MapBvgPerson(); - var bvgPerson = MapBvgPerson(); - var capital = new PredecessorRetirementCapital - { - BeginOfYearAmount = request.RetirementCapitalBeginOfYear, - DateOfProcess = request.DateOfCalculation, - CurrentAmount = request.RetirementCapitalBeginOfYear, - EndOfYearAmount = request.RetirementCapitalEndOfYear, - }; - - var result = - await bvgCalculator.CalculateAsync(capital, request.DateOfCalculation, bvgPerson); + var result = bvgCalculator.Calculate(request.CalculationYear, request.RetirementCapitalEndOfYear, bvgPerson); - return result - .Match( - Right: Ok, - Left: BadRequest); + return result + .Match( + Right: Ok, + Left: BadRequest); - // local methods - BvgPerson MapBvgPerson() + // local methods + BvgPerson MapBvgPerson() + { + return new BvgPerson { - return new BvgPerson - { - DateOfBirth = request.DateOfBirth, - ReportedSalary = request.Salary, - Gender = request.Gender, - PartTimeDegree = decimal.One, - }; - } + DateOfBirth = request.DateOfBirth, + ReportedSalary = request.Salary, + Gender = request.Gender, + PartTimeDegree = decimal.One, + }; } } } diff --git a/src/TaxCalculator.WebApi/Controllers/CheckSettingsController.cs b/src/TaxCalculator.WebApi/Controllers/CheckSettingsController.cs index d77fcc51..6f16ebec 100644 --- a/src/TaxCalculator.WebApi/Controllers/CheckSettingsController.cs +++ b/src/TaxCalculator.WebApi/Controllers/CheckSettingsController.cs @@ -1,28 +1,29 @@ using System.Threading.Tasks; +using Application.Features.CheckSettings; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using PensionCoach.Tools.TaxCalculator.Abstractions; -namespace TaxCalculator.WebApi.Controllers; - -[Produces("application/json")] -[ApiController] -[Route("api/check")] -public class CheckSettingsController : Controller +namespace TaxCalculator.WebApi.Controllers { - private readonly ICheckSettingsConnector checkSettingsConnector; - - public CheckSettingsController(ICheckSettingsConnector checkSettingsConnector) + [Produces("application/json")] + [ApiController] + [Route("api/check")] + public class CheckSettingsController : Controller { - this.checkSettingsConnector = checkSettingsConnector; - } + private readonly ICheckSettingsConnector checkSettingsConnector; - [HttpGet("settings")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task Get() - { - var settings = await checkSettingsConnector.GetAsync(); + public CheckSettingsController(ICheckSettingsConnector checkSettingsConnector) + { + this.checkSettingsConnector = checkSettingsConnector; + } + + [HttpGet("settings")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task Get() + { + var settings = await checkSettingsConnector.GetAsync(); - return Ok(settings); + return Ok(settings); + } } } diff --git a/src/TaxCalculator.WebApi/Controllers/MultiPeriodCalculatorController.cs b/src/TaxCalculator.WebApi/Controllers/MultiPeriodCalculatorController.cs index 923312bf..8beba4d9 100644 --- a/src/TaxCalculator.WebApi/Controllers/MultiPeriodCalculatorController.cs +++ b/src/TaxCalculator.WebApi/Controllers/MultiPeriodCalculatorController.cs @@ -2,125 +2,127 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Calculators.CashFlow; -using Calculators.CashFlow.Models; +using Application.MultiPeriodCalculator; +using Application.Municipality; +using Domain.Contracts; +using Domain.Enums; +using Domain.Models.MultiPeriod; +using Domain.Models.Municipality; using LanguageExt; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using PensionCoach.Tools.CommonTypes; using PensionCoach.Tools.CommonTypes.MultiPeriod; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.TaxCalculator.Abstractions; -namespace TaxCalculator.WebApi.Controllers; - -[Produces("application/json")] -[ApiController] -[Route("api/calculator")] -public class MultiPeriodCalculatorController : ControllerBase +namespace TaxCalculator.WebApi.Controllers { - private readonly IMultiPeriodCashFlowCalculator multiPeriodCashFlowCalculator; - private readonly IMunicipalityConnector municipalityResolver; - - public MultiPeriodCalculatorController( - IMultiPeriodCashFlowCalculator multiPeriodCashFlowCalculator, - IMunicipalityConnector municipalityResolver) + [Produces("application/json")] + [ApiController] + [Route("api/calculator")] + public class MultiPeriodCalculatorController : ControllerBase { - this.multiPeriodCashFlowCalculator = multiPeriodCashFlowCalculator; - this.municipalityResolver = municipalityResolver; - } + private readonly IMultiPeriodCashFlowCalculator multiPeriodCashFlowCalculator; + private readonly IMunicipalityConnector municipalityResolver; - /// - /// Calculates for a pre-defined set of accounts, cash-flows and clear-actions a tax-person's wealth development - /// with respect to her tax situation. - /// - /// The request includes definitions for accounts, cash-flows and clear-actions for a the tax person. - /// Calculation results. - /// If calculation is successful. - /// - /// If request is incomplete or cannot be validated. The calculator may also not support all cantons. - /// - /// - /// Definitionen von Konten, Cash-Flows und Clear-Actions für eine steuerbare Person bestimmen wie sich deren Vermögen über die Zeit - /// entwickelt. Die Berechnung berücksichtigt insbesondere Steuereffekte auf den einzelnen Konti. - /// - [HttpPost] - [Route("multiperiod")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task> CalculateMultiPeriodCashFlows(MultiPeriodRequest request) - { - if (request == null) + public MultiPeriodCalculatorController( + IMultiPeriodCashFlowCalculator multiPeriodCashFlowCalculator, + IMunicipalityConnector municipalityResolver) { - return BadRequest(nameof(request)); + this.multiPeriodCashFlowCalculator = multiPeriodCashFlowCalculator; + this.municipalityResolver = municipalityResolver; } - MultiPeriodCalculatorPerson person = MapPerson(); + /// + /// Calculates for a pre-defined set of accounts, cash-flows and clear-actions a tax-person's wealth development + /// with respect to her tax situation. + /// + /// The request includes definitions for accounts, cash-flows and clear-actions for a the tax person. + /// Calculation results. + /// If calculation is successful. + /// + /// If request is incomplete or cannot be validated. The calculator may also not support all cantons. + /// + /// + /// Definitionen von Konten, Cash-Flows und Clear-Actions für eine steuerbare Person bestimmen wie sich deren Vermögen über die Zeit + /// entwickelt. Die Berechnung berücksichtigt insbesondere Steuereffekte auf den einzelnen Konti. + /// + [HttpPost] + [Route("multiperiod")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> CalculateMultiPeriodCashFlows(MultiPeriodRequest request) + { + if (request == null) + { + return BadRequest(nameof(request)); + } - Either municipalityData = - await municipalityResolver.GetAsync(request.BfsMunicipalityId, request.StartingYear); + MultiPeriodCalculatorPerson person = MapPerson(); - var composites = new List - { - request.CashFlowDefinitionRequest.SetupAccountDefinition, - request.CashFlowDefinitionRequest.SalaryPaymentsDefinition, - request.CashFlowDefinitionRequest.RelativeTransferAmountDefinition, - request.CashFlowDefinitionRequest.FixedTransferAmountDefinition, - request.CashFlowDefinitionRequest.ThirdPillarPaymentsDefinition, - request.CashFlowDefinitionRequest.PurchaseInsuranceYearsPaymentsDefinition, - }; + Either municipalityData = + await municipalityResolver.GetAsync(request.BfsMunicipalityId, request.StartingYear); - CashFlowDefinitionHolder cashFlowDefinitionHolder = new() - { - Composites = composites.Where(c => c is not null).ToList(), - StaticGenericCashFlowDefinitions = request.CashFlowDefinitionRequest.StaticGenericCashFlowDefinitions.ToList() - }; + var composites = new List + { + request.CashFlowDefinitionRequest.SetupAccountDefinition, + request.CashFlowDefinitionRequest.SalaryPaymentsDefinition, + request.CashFlowDefinitionRequest.RelativeTransferAmountDefinition, + request.CashFlowDefinitionRequest.FixedTransferAmountDefinition, + request.CashFlowDefinitionRequest.ThirdPillarPaymentsDefinition, + request.CashFlowDefinitionRequest.PurchaseInsuranceYearsPaymentsDefinition, + }; - Either result = await municipalityData - .BindAsync(m => - multiPeriodCashFlowCalculator.CalculateAsync( - request.StartingYear, - request.NumberOfPeriods, - person with { Canton = m.Canton }, - cashFlowDefinitionHolder, - request.Options)); + CashFlowDefinitionHolder cashFlowDefinitionHolder = new() + { + Composites = composites.Where(c => c is not null).ToList(), + StaticGenericCashFlowDefinitions = request.CashFlowDefinitionRequest.StaticGenericCashFlowDefinitions.ToList() + }; - return result - .Match( - Right: r => Ok(MapResponse(r)), - Left: BadRequest); + Either result = await municipalityData + .BindAsync(m => + multiPeriodCashFlowCalculator.CalculateAsync( + request.StartingYear, + request.NumberOfPeriods, + person with { Canton = m.Canton }, + cashFlowDefinitionHolder, + request.Options)); - // local methods - MultiPeriodCalculatorPerson MapPerson() - { - var name = string.IsNullOrEmpty(request.Name) - ? Guid.NewGuid().ToString().Substring(0, 6) - : request.Name; + return result + .Match( + Right: r => Ok(MapResponse(r)), + Left: BadRequest); - return new MultiPeriodCalculatorPerson + // local methods + MultiPeriodCalculatorPerson MapPerson() { - Name = name, - Gender = request.Gender, - DateOfBirth = request.DateOfBirth, - MunicipalityId = request.BfsMunicipalityId, - CivilStatus = request.CivilStatus, - ReligiousGroupType = request.ReligiousGroupType, - PartnerReligiousGroupType = request.PartnerReligiousGroupType ?? ReligiousGroupType.Other, - NumberOfChildren = 0, - Income = request.Income, - Wealth = request.Wealth, - CapitalBenefits = (request.CapitalBenefitsPillar3A, request.CapitalBenefitsPension), - }; - } + var name = string.IsNullOrEmpty(request.Name) + ? Guid.NewGuid().ToString().Substring(0, 6) + : request.Name; - MultiPeriodResponse MapResponse(MultiPeriodCalculationResult calculationResult) - { - return new MultiPeriodResponse + return new MultiPeriodCalculatorPerson + { + Name = name, + Gender = request.Gender, + DateOfBirth = request.DateOfBirth, + MunicipalityId = request.BfsMunicipalityId, + CivilStatus = request.CivilStatus, + ReligiousGroupType = request.ReligiousGroupType, + PartnerReligiousGroupType = request.PartnerReligiousGroupType ?? ReligiousGroupType.Other, + NumberOfChildren = 0, + Income = request.Income, + Wealth = request.Wealth, + CapitalBenefits = (request.CapitalBenefitsPillar3A, request.CapitalBenefitsPension), + }; + } + + MultiPeriodResponse MapResponse(MultiPeriodCalculationResult calculationResult) { - StartingYear = calculationResult.StartingYear, - NumberOfPeriods = calculationResult.NumberOfPeriods, - Accounts = calculationResult.Accounts, - }; + return new MultiPeriodResponse + { + StartingYear = calculationResult.StartingYear, + NumberOfPeriods = calculationResult.NumberOfPeriods, + Accounts = calculationResult.Accounts, + }; + } } } } diff --git a/src/TaxCalculator.WebApi/Controllers/MunicipalityDataController.cs b/src/TaxCalculator.WebApi/Controllers/MunicipalityDataController.cs index 38840699..6fbe863a 100644 --- a/src/TaxCalculator.WebApi/Controllers/MunicipalityDataController.cs +++ b/src/TaxCalculator.WebApi/Controllers/MunicipalityDataController.cs @@ -1,87 +1,88 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Application.Municipality; +using Domain.Models.Municipality; using LanguageExt; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.TaxCalculator.Abstractions; -namespace TaxCalculator.WebApi.Controllers; - -[Produces("application/json")] -[ApiController] -[Route("api/data/municipality")] -public class MunicipalityDataController : ControllerBase +namespace TaxCalculator.WebApi.Controllers { - private readonly IMunicipalityConnector municipalityConnector; - - public MunicipalityDataController( - IMunicipalityConnector municipalityConnector) + [Produces("application/json")] + [ApiController] + [Route("api/data/municipality")] + public class MunicipalityDataController : ControllerBase { - this.municipalityConnector = municipalityConnector; - } + private readonly IMunicipalityConnector municipalityConnector; - /// - /// Gets municipality data supported by the calculators. - /// - /// A list of municipalities. - /// - /// Holt die vollständige Gemeindeliste. - /// - [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> Get() - { - return Ok(await municipalityConnector.GetAllAsync()); - } - - /// - /// Gets a municipality by its BFS id. - /// - /// Year of validity. - /// BFS municipality number, e.g. 261 for city of Zurich. - /// Detailed municipality data. - /// - /// Holt Steuerdetails der Gemeinde. Als Schlüssel dient - /// die BFS-Gemeindenummer. Diese Nummer kann für eine Gemeinde - /// über die Zeit ändern. - /// - [HttpGet("{year}/{id}", Name = "Get")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> Get(int year, int id) - { - Either result = - await municipalityConnector.GetAsync(id, year); + public MunicipalityDataController( + IMunicipalityConnector municipalityConnector) + { + this.municipalityConnector = municipalityConnector; + } - return result - .Match( - Right: Ok, - Left: NotFound); - } + /// + /// Gets municipality data supported by the calculators. + /// + /// A list of municipalities. + /// + /// Holt die vollständige Gemeindeliste. + /// + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> Get() + { + return Ok(await municipalityConnector.GetAllAsync()); + } - /// - /// Search the municipality directory including its history - /// matching filter values. - /// - /// Search municipalities with respect to given filter. - /// A list of municipalities. - /// - /// Durchsucht das Gemeindeverzeichnis gemäss Filterwerte. - /// - [HttpPost] - [Route("search")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public ActionResult> Search(MunicipalitySearchFilter filter) - { - if (filter == null) + /// + /// Gets a municipality by its BFS id. + /// + /// Year of validity. + /// BFS municipality number, e.g. 261 for city of Zurich. + /// Detailed municipality data. + /// + /// Holt Steuerdetails der Gemeinde. Als Schlüssel dient + /// die BFS-Gemeindenummer. Diese Nummer kann für eine Gemeinde + /// über die Zeit ändern. + /// + [HttpGet("{year}/{id}", Name = "Get")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> Get(int year, int id) { - return BadRequest("Search filter is null"); + Either result = + await municipalityConnector.GetAsync(id, year); + + return result + .Match( + Right: Ok, + Left: NotFound); } - IEnumerable result = - municipalityConnector.Search(filter); + /// + /// Search the municipality directory including its history + /// matching filter values. + /// + /// Search municipalities with respect to given filter. + /// A list of municipalities. + /// + /// Durchsucht das Gemeindeverzeichnis gemäss Filterwerte. + /// + [HttpPost] + [Route("search")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public ActionResult> Search(MunicipalitySearchFilter filter) + { + if (filter == null) + { + return BadRequest("Search filter is null"); + } + + IEnumerable result = + municipalityConnector.Search(filter); - return Ok(result); + return Ok(result); + } } } diff --git a/src/TaxCalculator.WebApi/Controllers/TaxCalculatorController.cs b/src/TaxCalculator.WebApi/Controllers/TaxCalculatorController.cs index e6d1fca4..e46222ab 100644 --- a/src/TaxCalculator.WebApi/Controllers/TaxCalculatorController.cs +++ b/src/TaxCalculator.WebApi/Controllers/TaxCalculatorController.cs @@ -1,322 +1,325 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Application.Features.FullTaxCalculation; +using Application.Features.MarginalTaxCurve; +using Application.Municipality; +using Application.Tax.Proprietary.Models; +using Domain.Enums; +using Domain.Models.Tax; using LanguageExt; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using PensionCoach.Tools.CommonTypes; using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using TaxCalculator.WebApi.Models; -namespace TaxCalculator.WebApi.Controllers; - -[Produces("application/json")] -[ApiController] -[Route("api/calculators/tax")] -public class TaxCalculatorController : ControllerBase +namespace TaxCalculator.WebApi.Controllers { - private readonly ITaxCalculatorConnector taxCalculatorConnector; - private readonly IMarginalTaxCurveCalculatorConnector marginalTaxCurveCalculatorConnector; - private readonly IMunicipalityConnector municipalityResolver; - private readonly ITaxSupportedYearProvider taxSupportedYearProvider; - - public TaxCalculatorController( - ITaxCalculatorConnector taxCalculatorConnector, - IMarginalTaxCurveCalculatorConnector marginalTaxCurveCalculatorConnector, - IMunicipalityConnector municipalityResolver, - ITaxSupportedYearProvider taxSupportedYearProvider) - { - this.taxCalculatorConnector = taxCalculatorConnector; - this.marginalTaxCurveCalculatorConnector = marginalTaxCurveCalculatorConnector; - this.municipalityResolver = municipalityResolver; - this.taxSupportedYearProvider = taxSupportedYearProvider; - } - - /// - /// Calculates the full tax. Result comprises municipality, state and federal income tax amounts. - /// And, wealth capital tax is calculated for municipality and state only. Wealth is not taxed on federal level. - /// - /// The request. - /// Calculation results. - /// - /// Einkommens- und Vermögenssteuerrechner für Gemeinde- und Staatsteuern. Vermögenssteuern werden - /// auf Bundesebene keine erhoben. - /// - [HttpPost] - [Route("full/incomeandwealth")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task> CalculateFullTax( - FullTaxRequest request) + [Produces("application/json")] + [ApiController] + [Route("api/calculators/tax")] + public class TaxCalculatorController : ControllerBase { - if (request == null) + private readonly ITaxCalculatorConnector taxCalculatorConnector; + private readonly IMarginalTaxCurveCalculatorConnector marginalTaxCurveCalculatorConnector; + private readonly IMunicipalityConnector municipalityResolver; + private readonly ITaxSupportedYearProvider taxSupportedYearProvider; + + public TaxCalculatorController( + ITaxCalculatorConnector taxCalculatorConnector, + IMarginalTaxCurveCalculatorConnector marginalTaxCurveCalculatorConnector, + IMunicipalityConnector municipalityResolver, + ITaxSupportedYearProvider taxSupportedYearProvider) { - return BadRequest(nameof(request)); + this.taxCalculatorConnector = taxCalculatorConnector; + this.marginalTaxCurveCalculatorConnector = marginalTaxCurveCalculatorConnector; + this.municipalityResolver = municipalityResolver; + this.taxSupportedYearProvider = taxSupportedYearProvider; } - var taxPerson = MapRequest(); + /// + /// Calculates the full tax. Result comprises municipality, state and federal income tax amounts. + /// And, wealth capital tax is calculated for municipality and state only. Wealth is not taxed on federal level. + /// + /// The request. + /// Calculation results. + /// + /// Einkommens- und Vermögenssteuerrechner für Gemeinde- und Staatsteuern. Vermögenssteuern werden + /// auf Bundesebene keine erhoben. + /// + [HttpPost] + [Route("full/incomeandwealth")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> CalculateFullTax( + FullTaxRequest request) + { + if (request == null) + { + return BadRequest(nameof(request)); + } + + var taxPerson = MapRequest(); - Either result = await taxCalculatorConnector.CalculateAsync( + Either result = await taxCalculatorConnector.CalculateAsync( request.CalculationYear, request.BfsMunicipalityId, taxPerson); - return result - .Match( - Right: r => Ok(MapResponse(r)), - Left: BadRequest); + return result + .Match( + Right: r => Ok(MapResponse(r)), + Left: BadRequest); - FullTaxResponse MapResponse(FullTaxResult r) - { - return new FullTaxResponse + FullTaxResponse MapResponse(FullTaxResult r) { - Name = taxPerson.Name, - CalculationYear = request.CalculationYear, - TotalTaxAmount = r.TotalTaxAmount, - FederalTaxAmount = r.FederalTaxResult.TaxAmount, - WealthTaxAmount = r.StateTaxResult.BasisWealthTax.TaxAmount, - CantonTaxAmount = r.StateTaxResult.CantonTaxAmount, - MunicipalityTaxAmount = r.StateTaxResult.MunicipalityTaxAmount, - ChurchTaxAmount = r.StateTaxResult.ChurchTaxAmount, - PollTaxAmount = r.StateTaxResult.PollTaxAmount.IfNone(0), - TaxRateDetails = new TaxRateDetails + return new FullTaxResponse { - CantonRate = r.StateTaxResult.CantonRate, - MunicipalityRate = r.StateTaxResult.MunicipalityRate, - ChurchTaxRate = r.StateTaxResult.ChurchTax.TaxRate, - }, - }; - } - - TaxPerson MapRequest() - { - var name = string.IsNullOrEmpty(request.Name) - ? Guid.NewGuid().ToString().Substring(0, 6) - : request.Name; - - return new TaxPerson + Name = taxPerson.Name, + CalculationYear = request.CalculationYear, + TotalTaxAmount = r.TotalTaxAmount, + FederalTaxAmount = r.FederalTaxResult.TaxAmount, + WealthTaxAmount = r.StateTaxResult.BasisWealthTax.TaxAmount, + CantonTaxAmount = r.StateTaxResult.CantonTaxAmount, + MunicipalityTaxAmount = r.StateTaxResult.MunicipalityTaxAmount, + ChurchTaxAmount = r.StateTaxResult.ChurchTaxAmount, + PollTaxAmount = r.StateTaxResult.PollTaxAmount.IfNone(0), + TaxRateDetails = new TaxRateDetails + { + CantonRate = r.StateTaxResult.CantonRate, + MunicipalityRate = r.StateTaxResult.MunicipalityRate, + ChurchTaxRate = r.StateTaxResult.ChurchTax.TaxRate, + }, + }; + } + + TaxPerson MapRequest() { - Name = name, - CivilStatus = request.CivilStatus, - ReligiousGroupType = request.ReligiousGroup, - PartnerReligiousGroupType = request.PartnerReligiousGroup ?? ReligiousGroupType.Other, - NumberOfChildren = 0, - TaxableIncome = request.TaxableIncome, - TaxableWealth = request.TaxableWealth, - TaxableFederalIncome = request.TaxableFederalIncome, - }; - } - } + var name = string.IsNullOrEmpty(request.Name) + ? Guid.NewGuid().ToString().Substring(0, 6) + : request.Name; - /// - /// Calculates marginal income tax rate curve. - /// - /// The request. - /// Calculation results. - /// - /// Berechnet die Grenzsteuersatzkurve für die Einkommenssteuer. Ein Punkt auf der Kurve sagt aus, wie hoch der Steueranteil - /// für einen zusätzlichen Einkommensfranken ist. - /// - [HttpPost] - [Route("marginaltaxcurve/income")] - [Produces("application/json")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task> CalculateMarginalIncomeTaxCurve(MarginalTaxRequest request) - { - if (request == null) - { - return BadRequest(nameof(request)); + return new TaxPerson + { + Name = name, + CivilStatus = request.CivilStatus, + ReligiousGroupType = request.ReligiousGroup, + PartnerReligiousGroupType = request.PartnerReligiousGroup ?? ReligiousGroupType.Other, + NumberOfChildren = 0, + TaxableIncome = request.TaxableIncome, + TaxableWealth = request.TaxableWealth, + TaxableFederalIncome = request.TaxableFederalIncome, + }; + } } - var taxPerson = MapRequest(); + /// + /// Calculates marginal income tax rate curve. + /// + /// The request. + /// Calculation results. + /// + /// Berechnet die Grenzsteuersatzkurve für die Einkommenssteuer. Ein Punkt auf der Kurve sagt aus, wie hoch der Steueranteil + /// für einen zusätzlichen Einkommensfranken ist. + /// + [HttpPost] + [Route("marginaltaxcurve/income")] + [Produces("application/json")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> CalculateMarginalIncomeTaxCurve(MarginalTaxRequest request) + { + if (request == null) + { + return BadRequest(nameof(request)); + } - Either result = - await marginalTaxCurveCalculatorConnector.CalculateIncomeTaxCurveAsync( - request.CalculationYear, request.BfsMunicipalityId, taxPerson, request.LowerSalaryLimit, request.UpperSalaryLimit, request.NumberOfSamples); + var taxPerson = MapRequest(); - return result - .Match( - Right: r => Ok(MapResponse(r)), - Left: BadRequest); + Either result = + await marginalTaxCurveCalculatorConnector.CalculateIncomeTaxCurveAsync( + request.CalculationYear, request.BfsMunicipalityId, taxPerson, request.LowerSalaryLimit, request.UpperSalaryLimit, request.NumberOfSamples); - MarginalTaxCurveResult MapResponse(MarginalTaxCurveResult r) - { - return r; - } + return result + .Match( + Right: r => Ok(MapResponse(r)), + Left: BadRequest); - TaxPerson MapRequest() - { - var name = string.IsNullOrEmpty(request.Name) - ? Guid.NewGuid().ToString().Substring(0, 6) - : request.Name; + MarginalTaxCurveResult MapResponse(MarginalTaxCurveResult r) + { + return r; + } - return new TaxPerson + TaxPerson MapRequest() { - Name = name, - CivilStatus = request.CivilStatus, - ReligiousGroupType = request.ReligiousGroup, - PartnerReligiousGroupType = request.PartnerReligiousGroup ?? ReligiousGroupType.Other, - NumberOfChildren = 0, - TaxableIncome = request.TaxableAmount, - TaxableFederalIncome = request.TaxableAmount - }; + var name = string.IsNullOrEmpty(request.Name) + ? Guid.NewGuid().ToString().Substring(0, 6) + : request.Name; + + return new TaxPerson + { + Name = name, + CivilStatus = request.CivilStatus, + ReligiousGroupType = request.ReligiousGroup, + PartnerReligiousGroupType = request.PartnerReligiousGroup ?? ReligiousGroupType.Other, + NumberOfChildren = 0, + TaxableIncome = request.TaxableAmount, + TaxableFederalIncome = request.TaxableAmount + }; + } } - } - /// - /// Calculates the overall capital benefit tax including state, municipality and - /// federal tax amounts. - /// - /// The request includes details about the tax person and the taxable amount. - /// Calculation results. - /// If calculation is successful. - /// - /// If request is incomplete or cannot be validated. The calculator may also not support all cantons. - /// - /// - /// Steuerrechner für Bundes-, Staats- und Gemeindesteuern auf Kapitalleistungen (Kapitalbezugssteuer). - /// - [HttpPost] - [Route("full/capitalbenefit")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task> CalculateFullCapitalBenefitTax( - CapitalBenefitTaxRequest request) - { - if (request == null) + /// + /// Calculates the overall capital benefit tax including state, municipality and + /// federal tax amounts. + /// + /// The request includes details about the tax person and the taxable amount. + /// Calculation results. + /// If calculation is successful. + /// + /// If request is incomplete or cannot be validated. The calculator may also not support all cantons. + /// + /// + /// Steuerrechner für Bundes-, Staats- und Gemeindesteuern auf Kapitalleistungen (Kapitalbezugssteuer). + /// + [HttpPost] + [Route("full/capitalbenefit")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> CalculateFullCapitalBenefitTax( + CapitalBenefitTaxRequest request) { - return BadRequest(nameof(request)); - } + if (request == null) + { + return BadRequest(nameof(request)); + } - var taxPerson = MapRequest(); + var taxPerson = MapRequest(); - var result = await taxCalculatorConnector.CalculateAsync( - request.CalculationYear, request.BfsMunicipalityId, taxPerson); + var result = await taxCalculatorConnector.CalculateAsync( + request.CalculationYear, request.BfsMunicipalityId, taxPerson); - return result - .Match( - Right: r => Ok(MapResponse(r)), - Left: BadRequest); + return result + .Match( + Right: r => Ok(MapResponse(r)), + Left: BadRequest); - // local methods - CapitalBenefitTaxResponse MapResponse(FullCapitalBenefitTaxResult r) - { - return new CapitalBenefitTaxResponse + // local methods + CapitalBenefitTaxResponse MapResponse(FullCapitalBenefitTaxResult r) + { + return new CapitalBenefitTaxResponse + { + Name = taxPerson.Name, + CalculationYear = request.CalculationYear, + TotalTaxAmount = r.TotalTaxAmount, + TaxDetails = new TaxAmountDetail + { + ChurchTaxAmount = r.StateResult.ChurchTaxAmount, + FederalTaxAmount = r.FederalResult.TaxAmount, + MunicipalityTaxAmount = r.StateResult.MunicipalityTaxAmount, + CantonTaxAmount = r.StateResult.CantonTaxAmount, + }, + }; + } + + CapitalBenefitTaxPerson MapRequest() { - Name = taxPerson.Name, - CalculationYear = request.CalculationYear, - TotalTaxAmount = r.TotalTaxAmount, - TaxDetails = new TaxAmountDetail + var name = string.IsNullOrEmpty(request.Name) + ? Guid.NewGuid().ToString().Substring(0, 6) + : request.Name; + + return new CapitalBenefitTaxPerson { - ChurchTaxAmount = r.StateResult.ChurchTaxAmount, - FederalTaxAmount = r.FederalResult.TaxAmount, - MunicipalityTaxAmount = r.StateResult.MunicipalityTaxAmount, - CantonTaxAmount = r.StateResult.CantonTaxAmount, - }, - }; + Name = name, + CivilStatus = request.CivilStatus, + ReligiousGroupType = request.ReligiousGroup, + PartnerReligiousGroupType = request.PartnerReligiousGroup ?? ReligiousGroupType.Other, + NumberOfChildren = 0, + TaxableCapitalBenefits = request.TaxableBenefits, + }; + } } - CapitalBenefitTaxPerson MapRequest() + /// + /// Calculates marginal income tax rate curve. + /// + /// The request. + /// Calculation results. + /// + /// Berechnet die Grenzsteuersatzkurve für die Kapitalbezugssteuer (z.B. Bezüge aus 3a- oder PK-Konten). + /// Ein Punkt auf der Kurve sagt aus, wie hoch der Steueranteil für einen Bezug eines zusätzlichen Frankens ist. + /// + [HttpPost] + [Route("marginaltaxcurve/capitalbenefits")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> CalculateMarginalCapitalBenefitsTaxCurve(MarginalTaxRequest request) { - var name = string.IsNullOrEmpty(request.Name) - ? Guid.NewGuid().ToString().Substring(0, 6) - : request.Name; + if (request == null) + { + return BadRequest(nameof(request)); + } + + var taxPerson = MapRequest(); + + Either result = + await marginalTaxCurveCalculatorConnector.CalculateCapitalBenefitTaxCurveAsync( + request.CalculationYear, + request.BfsMunicipalityId, + taxPerson, + request.LowerSalaryLimit, + request.UpperSalaryLimit, + request.NumberOfSamples); + + return result + .Match( + Right: r => Ok(MapResponse(r)), + Left: BadRequest); + + MarginalTaxCurveResult MapResponse(MarginalTaxCurveResult r) + { + return r; + } - return new CapitalBenefitTaxPerson + CapitalBenefitTaxPerson MapRequest() { - Name = name, - CivilStatus = request.CivilStatus, - ReligiousGroupType = request.ReligiousGroup, - PartnerReligiousGroupType = request.PartnerReligiousGroup ?? ReligiousGroupType.Other, - NumberOfChildren = 0, - TaxableCapitalBenefits = request.TaxableBenefits, - }; - } - } + var name = string.IsNullOrEmpty(request.Name) + ? Guid.NewGuid().ToString()[..6] + : request.Name; - /// - /// Calculates marginal income tax rate curve. - /// - /// The request. - /// Calculation results. - /// - /// Berechnet die Grenzsteuersatzkurve für die Kapitalbezugssteuer (z.B. Bezüge aus 3a- oder PK-Konten). - /// Ein Punkt auf der Kurve sagt aus, wie hoch der Steueranteil für einen Bezug eines zusätzlichen Frankens ist. - /// - [HttpPost] - [Route("marginaltaxcurve/capitalbenefits")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task> CalculateMarginalCapitalBenefitsTaxCurve(MarginalTaxRequest request) - { - if (request == null) - { - return BadRequest(nameof(request)); + return new CapitalBenefitTaxPerson + { + Name = name, + CivilStatus = request.CivilStatus, + ReligiousGroupType = request.ReligiousGroup, + PartnerReligiousGroupType = request.PartnerReligiousGroup ?? ReligiousGroupType.Other, + NumberOfChildren = 0, + TaxableCapitalBenefits = request.TaxableAmount, + }; + } } - var taxPerson = MapRequest(); - - Either result = - await marginalTaxCurveCalculatorConnector.CalculateCapitalBenefitTaxCurveAsync( - request.CalculationYear, - request.BfsMunicipalityId, - taxPerson, - request.LowerSalaryLimit, - request.UpperSalaryLimit, - request.NumberOfSamples); - - return result - .Match( - Right: r => Ok(MapResponse(r)), - Left: BadRequest); - - MarginalTaxCurveResult MapResponse(MarginalTaxCurveResult r) + [HttpGet] + [Route("municipality")] + public async Task>> GetSupportedMunicipalities() { - return r; + IReadOnlyCollection list = await municipalityResolver.GetAllSupportTaxCalculationAsync(); + + return Ok(list); } - CapitalBenefitTaxPerson MapRequest() + /// + /// Returns the supported tax years. List depends on the tax calculator implementation. + /// + /// Tax calculation years. + /// If calculation is successful. + /// + /// Unterstütze Steuerjahre - hängt von der konkreten Steuerrechner-Implementation ab. + /// + [HttpGet] + [Route("years")] + public async Task> GetSupportedTaxYears() { - var name = string.IsNullOrEmpty(request.Name) - ? Guid.NewGuid().ToString()[..6] - : request.Name; + var years = await taxSupportedYearProvider.GetSupportedTaxYears().AsTask(); - return new CapitalBenefitTaxPerson - { - Name = name, - CivilStatus = request.CivilStatus, - ReligiousGroupType = request.ReligiousGroup, - PartnerReligiousGroupType = request.PartnerReligiousGroup ?? ReligiousGroupType.Other, - NumberOfChildren = 0, - TaxableCapitalBenefits = request.TaxableAmount, - }; + return Ok(years); } } - - [HttpGet] - [Route("municipality")] - public async Task>> GetSupportedMunicipalities() - { - IReadOnlyCollection list = await municipalityResolver.GetAllSupportTaxCalculationAsync(); - - return Ok(list); - } - - /// - /// Returns the supported tax years. List depends on the tax calculator implementation. - /// - /// Tax calculation years. - /// If calculation is successful. - /// - /// Unterstütze Steuerjahre - hängt von der konkreten Steuerrechner-Implementation ab. - /// - [HttpGet] - [Route("years")] - public async Task> GetSupportedTaxYears() - { - var years = await taxSupportedYearProvider.GetSupportedTaxYears().AsTask(); - - return Ok(years); - } } diff --git a/src/TaxCalculator.WebApi/Controllers/TaxComparerController.cs b/src/TaxCalculator.WebApi/Controllers/TaxComparerController.cs index d9c689e2..f3747d80 100644 --- a/src/TaxCalculator.WebApi/Controllers/TaxComparerController.cs +++ b/src/TaxCalculator.WebApi/Controllers/TaxComparerController.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; +using Application.Features.TaxComparison; +using Application.Features.TaxComparison.Models; +using Domain.Enums; +using Domain.Models.Tax; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxComparison; -using Tax.Tools.Comparison.Abstractions; namespace TaxCalculator.WebApi.Controllers { diff --git a/src/TaxCalculator.WebApi/Controllers/TaxScenarioController.cs b/src/TaxCalculator.WebApi/Controllers/TaxScenarioController.cs index 2921f0b3..e67a77c0 100644 --- a/src/TaxCalculator.WebApi/Controllers/TaxScenarioController.cs +++ b/src/TaxCalculator.WebApi/Controllers/TaxScenarioController.cs @@ -1,12 +1,14 @@ using System; using System.Threading.Tasks; -using Calculators.CashFlow; +using Application.Features.TaxScenarios; +using Application.Features.TaxScenarios.Models; +using Domain.Enums; +using Domain.Models.Scenarios; +using Domain.Models.Tax; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using PensionCoach.Tools.CommonTypes; +using PensionCoach.Tools.CommonTypes.Features.PensionVersusCapital; using PensionCoach.Tools.CommonTypes.MultiPeriod; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxComparison; namespace TaxCalculator.WebApi.Controllers; @@ -32,15 +34,15 @@ public TaxScenarioController(ITaxScenarioCalculator scenarioCalculator) /// If request is incomplete or cannot be validated. /// /// - /// Berechnet die Wertentwicklung von ein oder mehreren Kapitaleinlagen in die Pensionkasse oder auf ein 3a Konto. + /// Berechnet die Wertentwicklung von ein oder mehreren Kapitaleinlagen in die Pensionskasse oder auf ein 3a Konto. /// Dabei werden steuerliche Aspekte bei der Einlage (Abzugsmöglichkeit) sowie die anfallende Kapitalbezugssteuer beim Bezug der /// Kapitalleistungen berücksichtigt. /// [HttpPost] - [Route(nameof(CalculateTransferInCapitalBenefits))] + [Route(nameof(CalculateCapitalBenefitTransferInsYears))] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task> CalculateTransferInCapitalBenefits(CapitalBenefitTransferInComparerRequest request) + public async Task> CalculateCapitalBenefitTransferInsYears(CapitalBenefitTransferInComparerRequest request) { if (request == null) { @@ -50,10 +52,10 @@ public async Task> CalculateTran var taxPerson = MapPerson(); var scenarioModel = MapScenarioModel(); - var response = new CapitalBenefitsTransferInResponse(); + var response = new ScenarioCalculationResponse(); - await scenarioCalculator.TransferInCapitalBenefitsAsync( - request.CalculationYear, request.BfsMunicipalityId, taxPerson, scenarioModel) + await scenarioCalculator.CapitalBenefitTransferInsAsync( + request.CalculationYear, request.BfsMunicipalityId, taxPerson, scenarioModel) .IterAsync(r => { response.NumberOfPeriods = r.NumberOfPeriods; @@ -65,9 +67,9 @@ await scenarioCalculator.TransferInCapitalBenefitsAsync( return response; - TransferInCapitalBenefitsScenarioModel MapScenarioModel() + CapitalBenefitTransferInsScenarioModel MapScenarioModel() { - return new TransferInCapitalBenefitsScenarioModel + return new CapitalBenefitTransferInsScenarioModel { TransferIns = request.TransferIns, NetReturnCapitalBenefits = request.NetPensionCapitalReturn, @@ -96,4 +98,99 @@ TaxPerson MapPerson() }; } } + + [HttpPost] + [Route(nameof(CalculateThirdPillarVersusSelfInvestmentComparison))] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> CalculateThirdPillarVersusSelfInvestmentComparison(ThirdPillarVersusSelfInvestmentComparerRequest request) + { + if (request == null) + { + return BadRequest(nameof(request)); + } + + var taxPerson = MapPerson(); + var scenarioModel = MapScenarioModel(); + + var response = new ScenarioCalculationResponse(); + + await scenarioCalculator.ThirdPillarVersusSelfInvestmentAsync( + request.CalculationYear, request.BfsMunicipalityId, taxPerson, scenarioModel) + .IterAsync(r => + { + response.NumberOfPeriods = r.NumberOfPeriods; + response.StartingYear = r.StartingYear; + response.DeltaSeries = r.DeltaSeries; + response.BenchmarkSeries = r.BenchmarkSeries; + response.ScenarioSeries = r.ScenarioSeries; + }); + + return response; + + ThirdPillarVersusSelfInvestmentScenarioModel MapScenarioModel() + { + return new ThirdPillarVersusSelfInvestmentScenarioModel + { + FinalYear = request.FinalYear, + InvestmentAmount = request.InvestmentAmount, + InvestmentNetGrowthRate = request.InvestmentNetGrowthRate, + InvestmentNetIncomeYield = request.InvestmentNetIncomeRate, + ThirdPillarNetGrowthRate = request.ThirdPillarNetGrowthRate, + }; + } + + TaxPerson MapPerson() + { + var name = string.IsNullOrEmpty(request.Name) + ? Guid.NewGuid().ToString()[..6] + : request.Name; + + return new TaxPerson + { + Name = name, + CivilStatus = request.CivilStatus, + ReligiousGroupType = request.ReligiousGroup, + PartnerReligiousGroupType = request.PartnerReligiousGroup ?? ReligiousGroupType.Other, + NumberOfChildren = 0, + TaxableFederalIncome = request.TaxableFederalIncome, + TaxableIncome = request.TaxableIncome, + TaxableWealth = request.TaxableWealth, + }; + } + } + + [HttpPost] + [Route(nameof(CalculatePensionVersusCapitalComparison))] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> CalculatePensionVersusCapitalComparison(PensionVersusCapitalRequest request) + { + if (request == null) + { + return BadRequest(nameof(request)); + } + + var response = new ScenarioCalculationResponse(); + + var result = await scenarioCalculator.PensionVersusCapitalComparisonAsync( + request.CalculationYear, + request.MunicipalityId, + request.YearlyConsumptionAmount, + request.RetirementPension, + request.RetirementCapital, + request.NetWealthReturn, + request.TaxPerson); + + result.Iter(r => + { + response.NumberOfPeriods = r.NumberOfPeriods; + response.StartingYear = r.StartingYear; + response.DeltaSeries = r.DeltaSeries; + response.BenchmarkSeries = r.BenchmarkSeries; + response.ScenarioSeries = r.ScenarioSeries; + }); + + return response; + } } diff --git a/src/TaxCalculator.WebApi/Dockerfile b/src/TaxCalculator.WebApi/Dockerfile index e26e45be..697e09e1 100644 --- a/src/TaxCalculator.WebApi/Dockerfile +++ b/src/TaxCalculator.WebApi/Dockerfile @@ -14,7 +14,7 @@ RUN dotnet publish "TaxCalculator.WebApi.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app -COPY ["Tax.Data/files/TaxDb.db", "/data/TaxDb.db"] +COPY ["Infrastructure/files/TaxDb.db", "/data/TaxDb.db"] COPY --from=publish /app/publish . # Do not run as root user diff --git a/src/TaxCalculator.WebApi/Examples/BvgCalculationRequestExample.cs b/src/TaxCalculator.WebApi/Examples/BvgCalculationRequestExample.cs new file mode 100644 index 00000000..ea26555e --- /dev/null +++ b/src/TaxCalculator.WebApi/Examples/BvgCalculationRequestExample.cs @@ -0,0 +1,22 @@ +using System; +using Domain.Enums; +using PensionCoach.Tools.CommonTypes.Bvg; +using Swashbuckle.AspNetCore.Filters; + +namespace TaxCalculator.WebApi.Examples; + +public class BvgCalculationRequestExample : IExamplesProvider +{ + public BvgCalculationRequest GetExamples() + { + return new BvgCalculationRequest + { + Name = "Test", + DateOfBirth = new DateTime(2000, 4, 19), + CalculationYear = 2019, + Gender = Gender.Male, + Salary = 100_000, + RetirementCapitalEndOfYear = 0, + }; + } +} diff --git a/src/TaxCalculator.WebApi/Examples/CapitalBenefitTaxComparerRequestExample.cs b/src/TaxCalculator.WebApi/Examples/CapitalBenefitTaxComparerRequestExample.cs index 1b68a196..3b20aa99 100644 --- a/src/TaxCalculator.WebApi/Examples/CapitalBenefitTaxComparerRequestExample.cs +++ b/src/TaxCalculator.WebApi/Examples/CapitalBenefitTaxComparerRequestExample.cs @@ -1,5 +1,5 @@ -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxComparison; +using Application.Features.TaxComparison.Models; +using Domain.Enums; using Swashbuckle.AspNetCore.Filters; namespace TaxCalculator.WebApi.Examples; diff --git a/src/TaxCalculator.WebApi/Examples/CapitalBenefitTaxRequestExample.cs b/src/TaxCalculator.WebApi/Examples/CapitalBenefitTaxRequestExample.cs index d3afd660..4759f439 100644 --- a/src/TaxCalculator.WebApi/Examples/CapitalBenefitTaxRequestExample.cs +++ b/src/TaxCalculator.WebApi/Examples/CapitalBenefitTaxRequestExample.cs @@ -1,6 +1,6 @@ -using PensionCoach.Tools.CommonTypes; +using Domain.Enums; +using PensionCoach.Tools.CommonTypes.Tax; using Swashbuckle.AspNetCore.Filters; -using TaxCalculator.WebApi.Models; namespace TaxCalculator.WebApi.Examples; diff --git a/src/TaxCalculator.WebApi/Examples/CapitalBenefitTransferInRequestExample.cs b/src/TaxCalculator.WebApi/Examples/CapitalBenefitTransferInRequestExample.cs index 49ba51e4..fac85a7a 100644 --- a/src/TaxCalculator.WebApi/Examples/CapitalBenefitTransferInRequestExample.cs +++ b/src/TaxCalculator.WebApi/Examples/CapitalBenefitTransferInRequestExample.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxComparison; +using Application.Features.TaxScenarios.Models; +using Domain.Enums; +using Domain.Models.Tax; using Swashbuckle.AspNetCore.Filters; namespace TaxCalculator.WebApi.Examples; diff --git a/src/TaxCalculator.WebApi/Examples/FullTaxRequestExample.cs b/src/TaxCalculator.WebApi/Examples/FullTaxRequestExample.cs index 4d4d6c8d..926745d2 100644 --- a/src/TaxCalculator.WebApi/Examples/FullTaxRequestExample.cs +++ b/src/TaxCalculator.WebApi/Examples/FullTaxRequestExample.cs @@ -1,25 +1,24 @@ -using PensionCoach.Tools.CommonTypes; +using Domain.Enums; using PensionCoach.Tools.CommonTypes.Tax; using Swashbuckle.AspNetCore.Filters; -namespace TaxCalculator.WebApi.Examples +namespace TaxCalculator.WebApi.Examples; + +public class FullTaxRequestExample : IExamplesProvider { - public class FullTaxRequestExample : IExamplesProvider + public FullTaxRequest GetExamples() { - public FullTaxRequest GetExamples() + return new FullTaxRequest { - return new FullTaxRequest - { - Name = "Test", - CalculationYear = 2019, - CivilStatus = CivilStatus.Married, - BfsMunicipalityId = 261, - ReligiousGroup = ReligiousGroupType.Other, - PartnerReligiousGroup = ReligiousGroupType.Other, - TaxableIncome = 100_000M, - TaxableFederalIncome = 100_000M, - TaxableWealth = 500_000, - }; - } + Name = "Test", + CalculationYear = 2019, + CivilStatus = CivilStatus.Married, + BfsMunicipalityId = 261, + ReligiousGroup = ReligiousGroupType.Other, + PartnerReligiousGroup = ReligiousGroupType.Other, + TaxableIncome = 100_000M, + TaxableFederalIncome = 100_000M, + TaxableWealth = 500_000, + }; } } diff --git a/src/TaxCalculator.WebApi/Examples/MarginalTaxRateCurveRequestExample.cs b/src/TaxCalculator.WebApi/Examples/MarginalTaxRateCurveRequestExample.cs index 56b41a73..361d4a9e 100644 --- a/src/TaxCalculator.WebApi/Examples/MarginalTaxRateCurveRequestExample.cs +++ b/src/TaxCalculator.WebApi/Examples/MarginalTaxRateCurveRequestExample.cs @@ -1,26 +1,25 @@ -using PensionCoach.Tools.CommonTypes; +using Domain.Enums; using PensionCoach.Tools.CommonTypes.Tax; using Swashbuckle.AspNetCore.Filters; -namespace TaxCalculator.WebApi.Examples +namespace TaxCalculator.WebApi.Examples; + +public class MarginalTaxRateCurveRequestExample : IExamplesProvider { - public class MarginalTaxRateCurveRequestExample : IExamplesProvider + public MarginalTaxRequest GetExamples() { - public MarginalTaxRequest GetExamples() + return new MarginalTaxRequest { - return new MarginalTaxRequest - { - Name = "Test Marginal Tax Rate Curve", - CalculationYear = 2019, - CivilStatus = CivilStatus.Married, - BfsMunicipalityId = 261, - ReligiousGroup = ReligiousGroupType.Other, - PartnerReligiousGroup = ReligiousGroupType.Other, - TaxableAmount = 100_000M, - LowerSalaryLimit = 0, - UpperSalaryLimit = 200_000, - NumberOfSamples = 10 - }; - } + Name = "Test Marginal Tax Rate Curve", + CalculationYear = 2019, + CivilStatus = CivilStatus.Married, + BfsMunicipalityId = 261, + ReligiousGroup = ReligiousGroupType.Other, + PartnerReligiousGroup = ReligiousGroupType.Other, + TaxableAmount = 100_000M, + LowerSalaryLimit = 0, + UpperSalaryLimit = 200_000, + NumberOfSamples = 10 + }; } } diff --git a/src/TaxCalculator.WebApi/Examples/MultiPeriodRequestExample.cs b/src/TaxCalculator.WebApi/Examples/MultiPeriodRequestExample.cs index 542e166b..afa8c1f3 100644 --- a/src/TaxCalculator.WebApi/Examples/MultiPeriodRequestExample.cs +++ b/src/TaxCalculator.WebApi/Examples/MultiPeriodRequestExample.cs @@ -1,122 +1,122 @@ using System; using System.Collections.Generic; -using PensionCoach.Tools.CommonTypes; +using Domain.Enums; +using Domain.Models.MultiPeriod; +using Domain.Models.MultiPeriod.Actions; +using Domain.Models.MultiPeriod.Definitions; using PensionCoach.Tools.CommonTypes.MultiPeriod; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Actions; -using PensionCoach.Tools.CommonTypes.MultiPeriod.Definitions; using PensionCoach.Tools.CommonTypes.Tax; using Swashbuckle.AspNetCore.Filters; -namespace TaxCalculator.WebApi.Examples +namespace TaxCalculator.WebApi.Examples; + +public class MultiPeriodRequestExample : IExamplesProvider { - public class MultiPeriodRequestExample : IExamplesProvider + public MultiPeriodRequest GetExamples() { - public MultiPeriodRequest GetExamples() - { - string personName = "Test Person"; + string personName = "Test Person"; - int startingYear = 2021; - int finalYear = 2030; - int numberOfPeriods = finalYear - startingYear + 1; + int startingYear = 2021; + int finalYear = 2030; + int numberOfPeriods = finalYear - startingYear + 1; - return new MultiPeriodRequest + return new MultiPeriodRequest + { + Name = "Swagger Sample", + StartingYear = startingYear, + NumberOfPeriods = numberOfPeriods, + BfsMunicipalityId = 261, + Gender = Gender.Male, + CivilStatus = CivilStatus.Married, + Income = 100_000, + Wealth = 500_000, + CapitalBenefitsPillar3A = 0, + CapitalBenefitsPension = 0, + ReligiousGroupType = ReligiousGroupType.Other, + PartnerReligiousGroupType = ReligiousGroupType.Other, + CashFlowDefinitionRequest = new CashFlowDefinitionRequest { - Name = "Swagger Sample", - StartingYear = startingYear, - NumberOfPeriods = numberOfPeriods, - BfsMunicipalityId = 261, - Gender = Gender.Male, - CivilStatus = CivilStatus.Married, - Income = 100_000, - Wealth = 500_000, - CapitalBenefitsPillar3A = 0, - CapitalBenefitsPension = 0, - ReligiousGroupType = ReligiousGroupType.Other, - PartnerReligiousGroupType = ReligiousGroupType.Other, - CashFlowDefinitionRequest = new CashFlowDefinitionRequest + RelativeTransferAmountDefinition = new RelativeTransferAmountDefinition() { - RelativeTransferAmountDefinition = new RelativeTransferAmountDefinition() + Header = new CashFlowHeader { - Header = new CashFlowHeader - { - Id = "ClearCapitalBenefitAction", - Name = $"{personName} - Clear Capital Benefit Action" - }, - DateOfProcess = new DateTime(finalYear, 1, 1), - TransferRatio = 1.0M, - Flow = new FlowPair(AccountType.OccupationalPension, AccountType.Wealth), - IsTaxable = true, - TaxType = TaxType.CapitalBenefits, + Id = "ClearCapitalBenefitAction", + Name = $"{personName} - Clear Capital Benefit Action" }, + DateOfProcess = new DateTime(finalYear, 1, 1), + TransferRatio = 1.0M, + Flow = new FlowPair(AccountType.OccupationalPension, AccountType.Wealth), + IsTaxable = true, + TaxType = TaxType.CapitalBenefits, + }, - ChangeResidenceActions = new List + ChangeResidenceActions = new List + { + new () { - new () + Header = new CashFlowHeader { - Header = new CashFlowHeader - { - Id = "ChangeResidence", - Name = $"{personName} - Change Residence" - }, - DestinationMunicipalityId = 3426, - DestinationCanton = Canton.SG, - ChangeCost = 5_000, - DateOfProcess = new DateTime(2029, 7, 1) + Id = "ChangeResidence", + Name = $"{personName} - Change Residence" }, + DestinationMunicipalityId = 3426, + DestinationCanton = Canton.SG, + ChangeCost = 5_000, + DateOfProcess = new DateTime(2029, 7, 1) }, + }, - StaticGenericCashFlowDefinitions = new List + StaticGenericCashFlowDefinitions = new List + { + new () { - new () + Header = new CashFlowHeader { - Header = new CashFlowHeader - { - Id = "my 3a account", - Name = $"{personName} - 3a Pillar" - }, + Id = "my 3a account", + Name = $"{personName} - 3a Pillar" + }, - InitialAmount = 100_000, - RecurringInvestment = new RecurringInvestment - { - Amount = 6883, - Frequency = FrequencyType.Yearly, - }, - Flow = new FlowPair(AccountType.Income, AccountType.ThirdPillar), - InvestmentPeriod = new InvestmentPeriod - { - Year = startingYear, - NumberOfPeriods = numberOfPeriods, - }, - IsTaxable = false, - TaxType = TaxType.Undefined, + InitialAmount = 100_000, + RecurringInvestment = new RecurringInvestment + { + Amount = 6883, + Frequency = FrequencyType.Yearly, }, - new () + Flow = new FlowPair(AccountType.Income, AccountType.ThirdPillar), + InvestmentPeriod = new InvestmentPeriod { - Header = new CashFlowHeader - { - Id = "my PK account", - Name = "PK-Einkauf", - }, + Year = startingYear, + NumberOfPeriods = numberOfPeriods, + }, + IsTaxable = false, + TaxType = TaxType.Undefined, + }, + new () + { + Header = new CashFlowHeader + { + Id = "my PK account", + Name = "PK-Einkauf", + }, - NetGrowthRate = 0, - InitialAmount = 400_000, - RecurringInvestment = new RecurringInvestment - { - Amount = 10000, - Frequency = FrequencyType.Yearly, - }, - Flow = new FlowPair(AccountType.Income, AccountType.OccupationalPension), - InvestmentPeriod = new InvestmentPeriod - { - Year = startingYear, - NumberOfPeriods = 5, - }, - IsTaxable = false, - TaxType = TaxType.Undefined + NetGrowthRate = 0, + InitialAmount = 400_000, + RecurringInvestment = new RecurringInvestment + { + Amount = 10000, + Frequency = FrequencyType.Yearly, + }, + Flow = new FlowPair(AccountType.Income, AccountType.OccupationalPension), + InvestmentPeriod = new InvestmentPeriod + { + Year = startingYear, + NumberOfPeriods = 5, }, + IsTaxable = false, + TaxType = TaxType.Undefined }, }, - }; - } + }, + }; } } diff --git a/src/TaxCalculator.WebApi/Examples/MunicipalitySearchRequestExample.cs b/src/TaxCalculator.WebApi/Examples/MunicipalitySearchRequestExample.cs index 230bc6be..0905e17e 100644 --- a/src/TaxCalculator.WebApi/Examples/MunicipalitySearchRequestExample.cs +++ b/src/TaxCalculator.WebApi/Examples/MunicipalitySearchRequestExample.cs @@ -1,19 +1,18 @@ -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Municipality; +using Domain.Enums; +using Domain.Models.Municipality; using Swashbuckle.AspNetCore.Filters; -namespace TaxCalculator.WebApi.Examples +namespace TaxCalculator.WebApi.Examples; + +public class MunicipalitySearchRequestExample : IExamplesProvider { - public class MunicipalitySearchRequestExample : IExamplesProvider + public MunicipalitySearchFilter GetExamples() { - public MunicipalitySearchFilter GetExamples() + return new MunicipalitySearchFilter { - return new MunicipalitySearchFilter - { - Name = "R", - Canton = Canton.ZH, - YearOfValidity = 2019, - }; - } + Name = "R", + Canton = Canton.ZH, + YearOfValidity = 2019, + }; } } diff --git a/src/TaxCalculator.WebApi/Examples/PensionVersusCapitalRequestExample.cs b/src/TaxCalculator.WebApi/Examples/PensionVersusCapitalRequestExample.cs new file mode 100644 index 00000000..6111246f --- /dev/null +++ b/src/TaxCalculator.WebApi/Examples/PensionVersusCapitalRequestExample.cs @@ -0,0 +1,31 @@ +using Domain.Enums; +using Domain.Models.Tax; +using PensionCoach.Tools.CommonTypes.Features.PensionVersusCapital; +using Swashbuckle.AspNetCore.Filters; + +namespace TaxCalculator.WebApi.Examples; + +public class PensionVersusCapitalRequestExample : IExamplesProvider +{ + public PensionVersusCapitalRequest GetExamples() + { + return new PensionVersusCapitalRequest + { + CalculationYear = 2019, + MunicipalityId = 261, + RetirementPension = 50_000, + RetirementCapital = 500_000, + YearlyConsumptionAmount = 78_800, + TaxPerson = new TaxPerson + { + Name = "Test", + CivilStatus = CivilStatus.Married, + ReligiousGroupType = ReligiousGroupType.Other, + PartnerReligiousGroupType = ReligiousGroupType.Other, + TaxableIncome = 28_800, + TaxableFederalIncome = 28_800, + TaxableWealth = 500_000, + } + }; + } +} diff --git a/src/TaxCalculator.WebApi/HealthChecks/TaxCalculatorHealthCheck.cs b/src/TaxCalculator.WebApi/HealthChecks/TaxCalculatorHealthCheck.cs deleted file mode 100644 index 3ec5470f..00000000 --- a/src/TaxCalculator.WebApi/HealthChecks/TaxCalculatorHealthCheck.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using LanguageExt; -using Microsoft.Extensions.Diagnostics.HealthChecks; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; - -namespace TaxCalculator.WebApi.HealthChecks; - -public class TaxCalculatorHealthCheck : IHealthCheck -{ - private readonly ITaxCalculatorConnector taxCalculatorConnector; - - public TaxCalculatorHealthCheck(ITaxCalculatorConnector taxCalculatorConnector) - { - this.taxCalculatorConnector = taxCalculatorConnector; - } - - public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken) - { - try - { - Either result = await taxCalculatorConnector.CalculateAsync(2023, 261, - new TaxPerson() - { - CivilStatus = CivilStatus.Single, - Name = "HealthCheck", - NumberOfChildren = 0, - TaxableIncome = 100_000, - TaxableWealth = 0, - TaxableFederalIncome = 100_000, - ReligiousGroupType = ReligiousGroupType.Other, - }); - - return result.Match( - Right: _ => HealthCheckResult.Healthy("Tax calculator ok"), - Left: error => HealthCheckResult.Unhealthy($"Tax calculator nok {error}")); - } - catch (Exception ex) - { - return HealthCheckResult.Unhealthy($"An unhealthy result: {ex.Message}"); - } - } -} diff --git a/src/TaxCalculator.WebApi/Models/Bvg/BvgCalculationRequest.cs b/src/TaxCalculator.WebApi/Models/Bvg/BvgCalculationRequest.cs deleted file mode 100644 index a600ed5a..00000000 --- a/src/TaxCalculator.WebApi/Models/Bvg/BvgCalculationRequest.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using PensionCoach.Tools.CommonTypes; - -namespace TaxCalculator.WebApi.Models.Bvg -{ - public class BvgCalculationRequest - { - [MaxLength(50)] - public string Name { get; set; } - - [Range(typeof(DateTime), "1/1/1970", "1/1/2099", ErrorMessage = "Valid calculation years start from 1970")] - public DateTime DateOfCalculation { get; set; } - - [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] - public decimal RetirementCapitalBeginOfYear { get; set; } - - [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] - public decimal RetirementCapitalEndOfYear { get; set; } - - [Range(typeof(DateTime), "1/1/1900", "1/1/2099", ErrorMessage = "No valid birthdate")] - public DateTime DateOfBirth { get; set; } - - [Range(typeof(decimal), "0", "1000000000", ErrorMessage = "No negative values allowed")] - public decimal Salary { get; set; } - - public Gender Gender { get; set; } - } -} diff --git a/src/TaxCalculator.WebApi/Models/CapitalBenefitTaxResponse.cs b/src/TaxCalculator.WebApi/Models/CapitalBenefitTaxResponse.cs deleted file mode 100644 index 4cd14098..00000000 --- a/src/TaxCalculator.WebApi/Models/CapitalBenefitTaxResponse.cs +++ /dev/null @@ -1,15 +0,0 @@ -using PensionCoach.Tools.CommonTypes.Tax; - -namespace TaxCalculator.WebApi.Models -{ - public class CapitalBenefitTaxResponse - { - public string Name { get; set; } - - public int CalculationYear { get; set; } - - public decimal TotalTaxAmount { get; set; } - - public TaxAmountDetail TaxDetails { get; set; } - } -} diff --git a/src/TaxCalculator.WebApi/Program.cs b/src/TaxCalculator.WebApi/Program.cs index 1e6a31ef..85fa5d7c 100644 --- a/src/TaxCalculator.WebApi/Program.cs +++ b/src/TaxCalculator.WebApi/Program.cs @@ -1,31 +1,124 @@ -using Microsoft.AspNetCore.Hosting; +using System.Reflection; +using System; +using System.IO; +using System.Text.Json.Serialization; +using Application.Extensions; +using Application.Features.FullTaxCalculation; +using Application.Features.PensionVersusCapital; +using Application.Features.TaxComparison; +using Application.MultiPeriodCalculator; +using Aspire; +using Infrastructure.Configuration; +using Infrastructure.DataStaging; +using Infrastructure.EstvTaxCalculator; +using Infrastructure.HealthChecks; +using Infrastructure.PostOpenApi; +using Infrastructure.Tax.Data; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.Filters; +using TaxCalculator.WebApi.Examples; -namespace TaxCalculator.WebApi +string myAllowSpecificOrigins = "_myAllowSpecificOrigins"; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddProblemDetails(); + +builder.Configuration.AddJsonFile("appsettings.json") + .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", true) + .AddEnvironmentVariables(); + +builder.AddServiceDefaults(); + +builder.Services.AddCors(options => + options.AddPolicy(myAllowSpecificOrigins, corsBuilder => + { + corsBuilder.WithOrigins( + "http://localhost:5167", + "http://localhost:8080", + "https://localhost:8080", + "https://localhost:5001", + "https://localhost:44309", + "http://localhost:44331", + "https://localhost:44331", + "http://localhost:44353", + "https://localhost:44353", + "http://localhost:57094", + "https://localhost:57094", + "http://localhost:55276", + "https://localhost:57276", + "https://x14qsqjz-44331.euw.devtunnels.ms", + "https://relaxed-bose-eb5bc2.netlify.com", + "https://pensiontoolsblazor.z6.web.core.windows.net") + .AllowAnyHeader() + .AllowAnyMethod(); + })); + +builder.Services.AddHealthChecks() + .AddCheck("Tax calculator check") + .AddDbContextCheck() + .AddDbContextCheck() + .AddDbContextCheck() + .AddDbContextCheck(); + +builder.Services.AddControllersWithViews() + .AddJsonOptions(options => + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())); + +builder.Services.AddTaxData(builder.Configuration); +builder.Services.AddTaxCalculators(builder.Configuration.GetApplicationMode()); +builder.Services.AddTaxComparers(); +builder.Services.AddDataStagingServices(); +builder.Services.AddBvgCalculators(); +builder.Services.AddCashFlowCalculators(); +builder.Services.AddToolsCalculators(); +builder.Services.AddEstvTaxCalculatorClient(builder.Configuration); +builder.Services.AddPostOpenApiClient(builder.Configuration); +builder.Services.AddSwaggerExamplesFromAssemblyOf(); +builder.Services.AddSwaggerGen(opt => { - public class Program + opt.SwaggerDoc("v1", new OpenApiInfo { - public static void Main(string[] args) + Title = "Tax Calculators", + Contact = new OpenApiContact { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup() - .ConfigureAppConfiguration((context, builder) => - { - builder - .AddJsonFile("appsettings.json") - .AddJsonFile( - $"appsettings.{context.HostingEnvironment.EnvironmentName}.json", - true) - .AddEnvironmentVariables() - .Build(); - }); - }); - } + Name = "Dieter Niggeler", + Email = "dnig69+2@gmail.com", + }, + Version = "v1", + }); + opt.ExampleFilters(); + + // Set the comments path for the Swagger JSON and UI. + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + opt.IncludeXmlComments(xmlPath); +}); + +var app = builder.Build(); +app.UseExceptionHandler(); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); } + +app.UseCors(myAllowSpecificOrigins); +app.UseRouting(); +app.UseAuthorization(); +app.UseHealthChecks("/"); +app.UseSwagger(); +app.UseSwaggerUI(opt => +{ + opt.SwaggerEndpoint("/swagger/v1/swagger.json", "Tax Calculators Api V1"); + opt.RoutePrefix = "swagger"; +}); + +app.MapControllers(); +app.MapDefaultEndpoints(); + +app.Run(); + diff --git a/src/TaxCalculator.WebApi/Startup.cs b/src/TaxCalculator.WebApi/Startup.cs index 27d92e3c..7e62d4a7 100644 --- a/src/TaxCalculator.WebApi/Startup.cs +++ b/src/TaxCalculator.WebApi/Startup.cs @@ -2,21 +2,24 @@ using System.IO; using System.Reflection; using System.Text.Json.Serialization; -using Calculators.CashFlow; +using Application.Extensions; +using Application.Features.FullTaxCalculation; +using Application.Features.PensionVersusCapital; +using Application.Features.TaxComparison; +using Application.MultiPeriodCalculator; +using Infrastructure.Configuration; +using Infrastructure.DataStaging; +using Infrastructure.EstvTaxCalculator; +using Infrastructure.HealthChecks; +using Infrastructure.PostOpenApi; +using Infrastructure.Tax.Data; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; -using PensionCoach.Tools.BvgCalculator; -using PensionCoach.Tools.EstvTaxCalculators; -using PensionCoach.Tools.PostOpenApi; -using PensionCoach.Tools.TaxCalculator; using Swashbuckle.AspNetCore.Filters; -using Tax.Data; -using Tax.Tools.Comparison; -using TaxCalculator.WebApi.HealthChecks; namespace TaxCalculator.WebApi { @@ -43,6 +46,8 @@ public void ConfigureServices(IServiceCollection services) "https://localhost:5001", "http://localhost:44331", "https://localhost:44331", + "http://localhost:44353", + "https://localhost:44353", "http://localhost:57094", "https://localhost:57094", "https://x14qsqjz-44331.euw.devtunnels.ms", @@ -63,10 +68,12 @@ public void ConfigureServices(IServiceCollection services) .AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())); services.AddTaxData(Configuration); - services.AddTaxCalculators(Configuration); + services.AddTaxCalculators(Configuration.GetApplicationMode()); services.AddTaxComparers(); + services.AddDataStagingServices(); services.AddBvgCalculators(); services.AddCashFlowCalculators(); + services.AddToolsCalculators(); services.AddEstvTaxCalculatorClient(Configuration); services.AddPostOpenApiClient(Configuration); services.AddSwaggerExamplesFromAssemblyOf(); diff --git a/src/TaxCalculator.WebApi/TaxCalculator.WebApi.csproj b/src/TaxCalculator.WebApi/TaxCalculator.WebApi.csproj index 97f26308..eca9a913 100644 --- a/src/TaxCalculator.WebApi/TaxCalculator.WebApi.csproj +++ b/src/TaxCalculator.WebApi/TaxCalculator.WebApi.csproj @@ -1,10 +1,7 @@  - - net7.0 - Linux - -v "$(SolutionDir)\..\src\Tax.Data\files:/data:ro" - + Exe + net8.0 true $(NoWarn);1591 latest @@ -15,24 +12,19 @@ - - - + + - - - - - + + + + + - - - - - - + + diff --git a/src/TaxCalculator.WebApi/appsettings.integration.json b/src/TaxCalculator.WebApi/appsettings.integration.json index a1a065c6..1f571bf7 100644 --- a/src/TaxCalculator.WebApi/appsettings.integration.json +++ b/src/TaxCalculator.WebApi/appsettings.integration.json @@ -8,7 +8,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "TaxDb": "../../../../Tax.Data/files/TaxDb.db" + "TaxDb": "../../../../Infrastructure/files/TaxDb.db" }, "TaxCalculatorClient": { "SwissTaxFacadeBaseUrl": "https://steuerrechner-schweiz.com/", diff --git a/src/TaxCalculator.WebApi/appsettings.json b/src/TaxCalculator.WebApi/appsettings.json index 18c886c5..490bc737 100644 --- a/src/TaxCalculator.WebApi/appsettings.json +++ b/src/TaxCalculator.WebApi/appsettings.json @@ -8,7 +8,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "TaxDb": "../Tax.Data/files/TaxDb.db" + "TaxDb": "../Infrastructure/files/TaxDb.db" }, "TaxCalculatorClient": { "SwissTaxFacadeBaseUrl": "https://steuerrechner-schweiz.com/", diff --git a/src/TaxCalculator/AdminConnector.cs b/src/TaxCalculator/AdminConnector.cs deleted file mode 100644 index e93b7f57..00000000 --- a/src/TaxCalculator/AdminConnector.cs +++ /dev/null @@ -1,297 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using LanguageExt; -using Microsoft.Extensions.Logging; -using PensionCoach.Tools.CommonUtils; -using PensionCoach.Tools.PostOpenApi; -using PensionCoach.Tools.PostOpenApi.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using Tax.Data; -using Tax.Data.Abstractions; -using Tax.Data.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator; - -public class AdminConnector : IAdminConnector -{ - private readonly MunicipalityDbContext municipalityDbContext; - private readonly ITaxDataPopulateService populateTaxDataService; - private readonly IPostOpenApiClient postOpenApiClient; - private readonly ILogger logger; - - public AdminConnector( - MunicipalityDbContext municipalityDbContext, - ITaxDataPopulateService populateTaxDataService, - IPostOpenApiClient postOpenApiClient, - ILogger logger) - { - this.municipalityDbContext = municipalityDbContext; - this.populateTaxDataService = populateTaxDataService; - this.postOpenApiClient = postOpenApiClient; - this.logger = logger; - } - - private int TotalCount { get; set; } - - public async Task PopulateWithZipCodeAsync() - { - int count = 0; - foreach (var municipalityEntity in municipalityDbContext.MunicipalityEntities - .Where(item => item.SuccessorId == 0 && string.IsNullOrEmpty(item.ZipCode))) - { - ZipEntity[] zipEntities = municipalityDbContext.TaxMunicipalityEntities - .Where(item => item.BfsNumber == municipalityEntity.BfsNumber && item.Canton == municipalityEntity.Canton) - .ToArray(); - - if (zipEntities.Length == 1) // unique, easy case - { - if (municipalityDbContext.MunicipalityEntities.Local - .Any(item => - item.BfsNumber == municipalityEntity.BfsNumber && - item.MutationId == municipalityEntity.MutationId)) - { - continue; - } - - municipalityEntity.ZipCode = zipEntities[0].ZipCode; - municipalityDbContext.Update(municipalityEntity); - count++; - } - else if (zipEntities.Length > 1) - { - var zipsByNameEntities = zipEntities - .Where(item => item.Name.Contains(municipalityEntity.CleanName)) - .ToArray(); - - if (zipsByNameEntities.Length == 1) - { - if (municipalityDbContext.MunicipalityEntities.Local - .Any(item => - item.BfsNumber == municipalityEntity.BfsNumber && - item.MutationId == municipalityEntity.MutationId)) - { - continue; - } - - municipalityEntity.ZipCode = zipsByNameEntities[0].ZipCode; - municipalityDbContext.Update(municipalityEntity); - count++; - } - else if (zipsByNameEntities.Length > 1) - { - // take smallest zip add on - var zipsByAddOn = zipsByNameEntities - .Where(item => item.ZipCodeAddOn == "0") - .ToArray(); - - if (zipsByAddOn.Length == 1) - { - if (municipalityDbContext.MunicipalityEntities.Local - .Any(item => - item.BfsNumber == municipalityEntity.BfsNumber && - item.MutationId == municipalityEntity.MutationId)) - { - continue; - } - - municipalityEntity.ZipCode = zipsByAddOn[0].ZipCode; - municipalityDbContext.Update(municipalityEntity); - count++; - } - else if (zipsByAddOn.Length > 1) - { - // take smallest zip add on - var zipFinalEntity = zipsByAddOn - .OrderBy(item => item.Name.Length) - .First(); - - if (municipalityDbContext.MunicipalityEntities.Local - .Any(item => - item.BfsNumber == municipalityEntity.BfsNumber && - item.MutationId == municipalityEntity.MutationId)) - { - continue; - } - - municipalityEntity.ZipCode = zipFinalEntity.ZipCode; - municipalityDbContext.Update(municipalityEntity); - count++; - } - } - } - } - - await municipalityDbContext.SaveChangesAsync(); - - return count; - } - - public Task PopulateWithTaxLocationAsync(bool doClear) - { - return populateTaxDataService.PopulateWithTaxLocationAsync(doClear); - } - - public async Task StagePlzTableAsync() - { - municipalityDbContext.TruncateTaxMunicipalityTable(); - - int count = 0; - - await foreach (var model in GetAllZipCodesAsync(int.MaxValue)) - { - municipalityDbContext.Add(new ZipEntity - { - BfsNumber = model.BfsCode, - Canton = model.Canton, - ZipCode = model.ZipCode, - ZipCodeAddOn = model.ZipCodeAddOn, - Name = model.MunicipalityName, - LanguageCode = model.LanguageCode, - DateOfValidity = model.DateOfValidity, - }); - - count++; - } - - await municipalityDbContext.SaveChangesAsync(CancellationToken.None); - - return count; - } - - public async Task CleanMunicipalityName() - { - int count = 0; - foreach (var municipalityEntity in municipalityDbContext.MunicipalityEntities) - { - Prelude.Optional((municipalityEntity.Name, municipalityEntity.Canton)) - .Map(t => RemoveCantonDescription(t.Name, t.Canton)) - .Map(Abbreviate) - .Iter(cleanName => - { - municipalityEntity.CleanName = cleanName; - municipalityDbContext.Update(municipalityEntity); - - count++; - }); - } - - await municipalityDbContext.SaveChangesAsync(CancellationToken.None); - - return count; - } - - /// - /// - public async IAsyncEnumerable GetAllZipCodesAsync(int limit) - { - const int numberOfReaders = 5; - const int limitPerFetch = 100; - - using (new MeasureTime(t => logger.LogDebug($"Execution time to fetch all ZIP: {t}ms"))) - { - logger.LogDebug($"First fetch zip data ({limitPerFetch} items)"); - - OpenApiZipInfo openApiData = await postOpenApiClient.GetZipCodesAsync(limitPerFetch, 0); - - if (openApiData is not { TotalCount: > 0 }) - { - yield break; - } - - TotalCount = openApiData.TotalCount; - - Channel<(int, int)> fetchZipChannel = Channel.CreateBounded<(int, int)>(new BoundedChannelOptions(5)); - Channel resultZipChannel = Channel.CreateUnbounded(); - - Task[] consumers = Enumerable - .Range(1, numberOfReaders) - .Select(counter => ConsumeDataAsync(fetchZipChannel.Reader, resultZipChannel.Writer, counter)) - .ToArray(); - - await WriteOpenApiDataToChannel(resultZipChannel, openApiData); - - await Task.Run(async () => - { - int count = 1; - while (count * limitPerFetch < TotalCount) - { - await fetchZipChannel.Writer.WriteAsync((limitPerFetch, count * limitPerFetch)); - count++; - } - - fetchZipChannel.Writer.Complete(); - }); - - await Task.WhenAll(consumers); - - resultZipChannel.Writer.Complete(); - - await foreach (ZipModel record in resultZipChannel.Reader.ReadAllAsync(CancellationToken.None)) - { - yield return record; - } - } - } - - private async Task ConsumeDataAsync(ChannelReader<(int, int)> channelReader, ChannelWriter channelWriter, int readerId) - { - while (await channelReader.WaitToReadAsync()) - { - if (channelReader.TryRead(out (int limit, int offset) fetch)) - { - logger.LogDebug($"Fetch zip data by reader {readerId}: limit {fetch.limit}, offset {fetch.offset}"); - - OpenApiZipInfo openApiData = await postOpenApiClient.GetZipCodesAsync(fetch.limit, fetch.offset); - - if (openApiData is null || openApiData.Records.Length() == 0) - { - break; - } - - await WriteOpenApiDataToChannel(channelWriter, openApiData); - } - } - - logger.LogDebug($"Reader {readerId} completed"); - } - - private static async Task WriteOpenApiDataToChannel(ChannelWriter channelWriter, OpenApiZipInfo openApiData) - { - foreach (ZipModel model in openApiData.Records.Select(x => new ZipModel - { - BfsCode = x.Record.Fields.BfsCode, - MunicipalityName = x.Record.Fields.MunicipalityName, - Canton = x.Record.Fields.Canton, - ZipCode = x.Record.Fields.ZipCode, - ZipCodeAddOn = x.Record.Fields.ZipCodeAddOn, - LanguageCode = x.Record.Fields.LanguageCode, - DateOfValidity = DateTime.Parse(x.Record.Fields.DateOfValidity, CultureInfo.InvariantCulture), - })) - { - await channelWriter.WriteAsync(model); - } - } - - private string RemoveCantonDescription(string name, string canton) - { - return name - .Replace($" ({canton})", string.Empty) - .Replace($" {canton}", string.Empty) - .Trim(); - } - - private string Abbreviate(string name) - { - return name - .Replace(" bei ", " b. ") - .Replace("Saint-", "St-") - .Replace("Sainte-", "Ste-") - .Replace("ë", "e") - .Trim(); - } -} diff --git a/src/TaxCalculator/Basis/CapitalBenefit/MissingCapitalBenefitTaxCalculator.cs b/src/TaxCalculator/Basis/CapitalBenefit/MissingCapitalBenefitTaxCalculator.cs deleted file mode 100644 index cbe3531c..00000000 --- a/src/TaxCalculator/Basis/CapitalBenefit/MissingCapitalBenefitTaxCalculator.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using Microsoft.Extensions.Logging; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Basis.CapitalBenefit -{ - /// - /// Null calculator for missing capital benefit calculator. - /// - public class MissingCapitalBenefitTaxCalculator : ICapitalBenefitTaxCalculator - { - private readonly ILogger logger; - - public MissingCapitalBenefitTaxCalculator(ILogger logger) - { - this.logger = logger; - } - - public Task> CalculateAsync( - int calculationYear, int municipalityId, Canton canton, CapitalBenefitTaxPerson person) - { - string msg = $"No capital benefit tax calculator for canton {canton} available"; - - Either result = msg; - - logger.LogWarning(msg); - - return Task.FromResult(result); - } - } -} diff --git a/src/TaxCalculator/Basis/CapitalBenefit/SGCapitalBenefitTaxCalculator.cs b/src/TaxCalculator/Basis/CapitalBenefit/SGCapitalBenefitTaxCalculator.cs deleted file mode 100644 index 4d6bf5b1..00000000 --- a/src/TaxCalculator/Basis/CapitalBenefit/SGCapitalBenefitTaxCalculator.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using AutoMapper; -using FluentValidation; -using LanguageExt; -using Microsoft.EntityFrameworkCore; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -using Tax.Data; -using static LanguageExt.Prelude; - -namespace PensionCoach.Tools.TaxCalculator.Basis.CapitalBenefit -{ - public class SGCapitalBenefitTaxCalculator : ICapitalBenefitTaxCalculator - { - private readonly IMapper mapper; - private readonly IValidator validator; - private readonly IChurchTaxCalculator churchTaxCalculator; - private readonly Func dbContext; - - public SGCapitalBenefitTaxCalculator( - IMapper mapper, - IValidator validator, - IChurchTaxCalculator churchTaxCalculator, - Func dbContext) - { - this.mapper = mapper; - this.validator = validator; - this.churchTaxCalculator = churchTaxCalculator; - this.dbContext = dbContext; - } - - /// - public async Task> CalculateAsync( - int calculationYear, - int municipalityId, - Canton canton, - CapitalBenefitTaxPerson capitalBenefitTaxPerson) - { - const decimal taxRateForSingle = 2.2M / 100M; - const decimal taxRateForMarried = 2.0M / 100M; - - var validationResult = validator.Validate(capitalBenefitTaxPerson); - if (!validationResult.IsValid) - { - return - string.Join(";", validationResult.Errors.Select(x => x.ErrorMessage)); - } - - await using var ctxt = dbContext(); - var taxRateEntity = ctxt.Rates.AsNoTracking() - .FirstOrDefault(item => item.BfsId == municipalityId - && item.Year == calculationYear); - - if (taxRateEntity == null) - { - return - $"No tax rate available for municipality {municipalityId} and year {calculationYear}"; - } - - BasisTaxResult basisTaxResult = GetBasisCapitalBenefitTaxAmount(capitalBenefitTaxPerson); - - ChurchTaxPerson churchTaxPerson = mapper.Map(capitalBenefitTaxPerson); - - Either churchTaxResult = - await churchTaxCalculator.CalculateAsync( - churchTaxPerson, - taxRateEntity, - new AggregatedBasisTaxResult - { - IncomeTax = basisTaxResult, - WealthTax = new BasisTaxResult(), - }); - - return churchTaxResult.Map(Update); - - CapitalBenefitTaxResult Update(ChurchTaxResult churchResult) - { - return new CapitalBenefitTaxResult - { - BasisTax = basisTaxResult, - ChurchTax = churchResult, - CantonRate = taxRateEntity.TaxRateCanton, - MunicipalityRate = taxRateEntity.TaxRateMunicipality, - }; - } - - BasisTaxResult GetBasisCapitalBenefitTaxAmount(CapitalBenefitTaxPerson person) - { - var amount = Some(person.CivilStatus) - .Match( - Some: status => status switch - { - CivilStatus.Single => - capitalBenefitTaxPerson.TaxableCapitalBenefits * taxRateForSingle, - CivilStatus.Married => - capitalBenefitTaxPerson.TaxableCapitalBenefits * taxRateForMarried, - _ => 0M, - }, - None: () => 0); - - return new BasisTaxResult - { - DeterminingFactorTaxableAmount = amount, - TaxAmount = amount, - }; - } - } - } -} diff --git a/src/TaxCalculator/Basis/CapitalBenefit/SOCapitalBenefitTaxCalculator.cs b/src/TaxCalculator/Basis/CapitalBenefit/SOCapitalBenefitTaxCalculator.cs deleted file mode 100644 index 425fb0bd..00000000 --- a/src/TaxCalculator/Basis/CapitalBenefit/SOCapitalBenefitTaxCalculator.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using AutoMapper; -using FluentValidation; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using Tax.Data; - -namespace PensionCoach.Tools.TaxCalculator.Basis.CapitalBenefit -{ - public class SOCapitalBenefitTaxCalculator : ICapitalBenefitTaxCalculator - { - private const decimal ScaleFactor = 0.25M; - - private readonly IMapper mapper; - private readonly IValidator validator; - private readonly IStateTaxCalculator stateTaxCalculator; - private readonly IChurchTaxCalculator churchTaxCalculator; - private readonly Func dbContext; - - public SOCapitalBenefitTaxCalculator( - IMapper mapper, - IValidator validator, - IStateTaxCalculator stateTaxCalculator, - IChurchTaxCalculator churchTaxCalculator, - Func dbContext) - { - this.mapper = mapper; - this.validator = validator; - this.stateTaxCalculator = stateTaxCalculator; - this.churchTaxCalculator = churchTaxCalculator; - this.dbContext = dbContext; - } - - /// - public async Task> CalculateAsync( - int calculationYear, - int municipalityId, - Canton canton, - CapitalBenefitTaxPerson capitalBenefitTaxPerson) - { - var validationResult = validator.Validate(capitalBenefitTaxPerson); - if (!validationResult.IsValid) - { - return - string.Join(";", validationResult.Errors.Select(x => x.ErrorMessage)); - } - - var stateTaxPerson = mapper.Map(capitalBenefitTaxPerson); - stateTaxPerson.TaxableIncome = capitalBenefitTaxPerson.TaxableCapitalBenefits; - - var stateTaxResult = await stateTaxCalculator - .CalculateAsync(calculationYear, municipalityId, Canton.SO, stateTaxPerson); - - return stateTaxResult.Map(Scale); - - static CapitalBenefitTaxResult Scale(StateTaxResult intermediateResult) - { - return new CapitalBenefitTaxResult - { - BasisTax = new BasisTaxResult - { - DeterminingFactorTaxableAmount = - intermediateResult.BasisIncomeTax.DeterminingFactorTaxableAmount * ScaleFactor, - TaxAmount = - intermediateResult.BasisIncomeTax.TaxAmount * ScaleFactor, - }, - ChurchTax = new ChurchTaxResult - { - TaxAmount = intermediateResult.ChurchTax.TaxAmount.Match( - Some: r => r * ScaleFactor, - None: () => Option.None), - - TaxAmountPartner = intermediateResult.ChurchTax.TaxAmountPartner.Match( - Some: r => r * ScaleFactor, - None: () => Option.None), - }, - CantonRate = intermediateResult.CantonRate, - MunicipalityRate = intermediateResult.MunicipalityRate, - }; - } - } - } -} diff --git a/src/TaxCalculator/Basis/CapitalBenefit/ZHCapitalBenefitTaxCalculator.cs b/src/TaxCalculator/Basis/CapitalBenefit/ZHCapitalBenefitTaxCalculator.cs deleted file mode 100644 index 70b3ad93..00000000 --- a/src/TaxCalculator/Basis/CapitalBenefit/ZHCapitalBenefitTaxCalculator.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using AutoMapper; -using FluentValidation; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Basis.CapitalBenefit -{ - public class ZHCapitalBenefitTaxCalculator : ICapitalBenefitTaxCalculator - { - private readonly IStateTaxCalculator stateTaxCalculator; - private readonly IValidator validator; - private readonly IMapper mapper; - - public ZHCapitalBenefitTaxCalculator( - IStateTaxCalculator stateTaxCalculator, - IValidator validator, - IMapper mapper) - { - this.stateTaxCalculator = stateTaxCalculator; - this.validator = validator; - this.mapper = mapper; - } - - /// - public async Task> CalculateAsync( - int calculationYear, - int municipalityId, - Canton canton, - CapitalBenefitTaxPerson capitalBenefitTaxPerson) - { - var validationResult = validator.Validate(capitalBenefitTaxPerson); - if (!validationResult.IsValid) - { - var errorMessageLine = - string.Join(";", validationResult.Errors.Select(x => x.ErrorMessage)); - - return errorMessageLine; - } - - const decimal annuitizeFactor = 10; - TaxPerson taxPerson = mapper.Map(capitalBenefitTaxPerson); - - taxPerson.TaxableIncome = capitalBenefitTaxPerson.TaxableCapitalBenefits / annuitizeFactor; - - var stateTaxResult = await stateTaxCalculator.CalculateAsync( - calculationYear, - municipalityId, - canton, - taxPerson); - - return stateTaxResult - .Map(r => Scale(r, annuitizeFactor)); - } - - private CapitalBenefitTaxResult Scale(StateTaxResult intermediateResult, decimal scaleFactor) - { - var result = new CapitalBenefitTaxResult - { - BasisTax = new BasisTaxResult - { - DeterminingFactorTaxableAmount = - intermediateResult.BasisIncomeTax.DeterminingFactorTaxableAmount * scaleFactor, - TaxAmount = - intermediateResult.BasisIncomeTax.TaxAmount * scaleFactor, - }, - ChurchTax = new ChurchTaxResult - { - TaxAmount = intermediateResult.ChurchTax.TaxAmount.Match( - Some: r => r * scaleFactor, - None: () => Option.None), - - TaxAmountPartner = intermediateResult.ChurchTax.TaxAmountPartner.Match( - Some: r => r * scaleFactor, - None: () => Option.None), - }, - CantonRate = intermediateResult.CantonRate, - MunicipalityRate = intermediateResult.MunicipalityRate, - }; - - return result; - } - } -} diff --git a/src/TaxCalculator/Basis/Wealth/MissingBasisWealthTaxCalculator.cs b/src/TaxCalculator/Basis/Wealth/MissingBasisWealthTaxCalculator.cs deleted file mode 100644 index baabb0a4..00000000 --- a/src/TaxCalculator/Basis/Wealth/MissingBasisWealthTaxCalculator.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using Microsoft.Extensions.Logging; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - -namespace PensionCoach.Tools.TaxCalculator.Basis.Wealth -{ - /// - /// Null calculator for missing wealth calculators. - /// - public class MissingBasisWealthTaxCalculator : IBasisWealthTaxCalculator - { - private readonly ILogger logger; - - public MissingBasisWealthTaxCalculator(ILogger logger) - { - this.logger = logger; - } - - public Task> CalculateAsync( - int calculationYear, Canton canton, BasisTaxPerson person) - { - string msg = $"No wealth tax calculator for canton {canton.ToString()} available"; - - Either result = msg; - - logger.LogWarning(msg); - - return Task.FromResult(result); - } - } -} diff --git a/src/TaxCalculator/Basis/Wealth/SGBasisWealthTaxCalculator.cs b/src/TaxCalculator/Basis/Wealth/SGBasisWealthTaxCalculator.cs deleted file mode 100644 index d5602354..00000000 --- a/src/TaxCalculator/Basis/Wealth/SGBasisWealthTaxCalculator.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - -namespace PensionCoach.Tools.TaxCalculator.Basis.Wealth -{ - public class SGBasisWealthTaxCalculator : IBasisWealthTaxCalculator - { - private const decimal TaxRate = 1.7M / 1000M; - private const decimal MinLevel = 1000M; - - public Task> CalculateAsync(int calculationYear, Canton canton, BasisTaxPerson person) - { - Either taxResult = new BasisTaxResult(); - - if (person.TaxableAmount < MinLevel) - { - return taxResult.AsTask(); - } - - taxResult.IfRight(r => - { - r.TaxAmount = person.TaxableAmount * TaxRate; - r.DeterminingFactorTaxableAmount = person.TaxableAmount; - }); - - return taxResult.AsTask(); - } - } -} \ No newline at end of file diff --git a/src/TaxCalculator/Basis/Wealth/SOBasisWealthTaxCalculator.cs b/src/TaxCalculator/Basis/Wealth/SOBasisWealthTaxCalculator.cs deleted file mode 100644 index 336d3f2d..00000000 --- a/src/TaxCalculator/Basis/Wealth/SOBasisWealthTaxCalculator.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using FluentValidation; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -using Tax.Data.Abstractions; -using Tax.Data.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Basis.Wealth -{ - /// - /// Wealth tax calculator for SO. - /// - /// - public class SOBasisWealthTaxCalculator : IBasisWealthTaxCalculator - { - private const int TaxTypeId = (int)TaxType.Wealth; - - private readonly IValidator taxPersonValidator; - private readonly ITaxTariffData tariffData; - - public SOBasisWealthTaxCalculator( - IValidator taxPersonValidator, - ITaxTariffData tariffData) - { - this.taxPersonValidator = taxPersonValidator; - this.tariffData = tariffData; - } - - public Task> CalculateAsync( - int calculationYear, Canton canton, BasisTaxPerson person) - { - var validationResult = taxPersonValidator.Validate(person); - if (!validationResult.IsValid) - { - var errorMessageLine = string.Join(";", validationResult.Errors.Select(x => x.ErrorMessage)); - return Task.FromResult>($"validation failed: {errorMessageLine}"); - } - - var tariffItems = - tariffData.Get(new TaxFilterModel - { - Year = calculationYear, - Canton = canton.ToString(), - }) - .OrderBy(item => item.TaxAmount); - - return Map(person.CivilStatus) - - // get all income level candidate - .Map(typeId => tariffItems - .Where(item => item.TariffType == (int)TariffType.Base) - .Where(item => item.TaxType == TaxTypeId) - .Where(item => item.IncomeLevel <= person.TaxableAmount) - .OrderByDescending(item => item.IncomeLevel) - .DefaultIfEmpty(new TaxTariffModel()) - .First()) - - // calculate result - .Map(tariff => CalculateIncomeTax(person, tariff)) - .Match>( - Some: r => r, - None: () => "Tariff not available") - .AsTask(); - } - - private BasisTaxResult CalculateIncomeTax(BasisTaxPerson person, TaxTariffModel tariff) - { - var referenceTaxableIncome = - person.TaxableAmount - (person.TaxableAmount % tariff.IncomeIncrement); - - var incrementMultiplier = (referenceTaxableIncome - tariff.IncomeLevel) / tariff.IncomeIncrement; - - var baseTaxAmount = (incrementMultiplier * tariff.TaxTariffRatePercent / 1000m) + tariff.TaxAmount; - - return new BasisTaxResult - { - DeterminingFactorTaxableAmount = referenceTaxableIncome, - TaxAmount = baseTaxAmount, - }; - } - - private Option Map(Option status) - { - return status.Match( - Some: s => s switch - { - CivilStatus.Undefined => Option.None, - CivilStatus.Single => TariffType.Base, - CivilStatus.Married => TariffType.Married, - _ => Option.None - }, - None: () => Option.None); - } - } -} diff --git a/src/TaxCalculator/Basis/Wealth/ZHBasisWealthTaxCalculator.cs b/src/TaxCalculator/Basis/Wealth/ZHBasisWealthTaxCalculator.cs deleted file mode 100644 index 2d4f0b76..00000000 --- a/src/TaxCalculator/Basis/Wealth/ZHBasisWealthTaxCalculator.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using FluentValidation; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -using Tax.Data.Abstractions; -using Tax.Data.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Basis.Wealth -{ - /// - /// Wealth tax calculator for ZH. - /// - /// - public class ZHBasisWealthTaxCalculator : IBasisWealthTaxCalculator - { - private const int TaxTypeId = (int)TaxType.Wealth; - - private readonly IValidator taxPersonValidator; - private readonly ITaxTariffData tariffData; - - public ZHBasisWealthTaxCalculator( - IValidator taxPersonValidator, - ITaxTariffData tariffData) - { - this.taxPersonValidator = taxPersonValidator; - this.tariffData = tariffData; - } - - public Task> CalculateAsync( - int calculationYear, Canton canton, BasisTaxPerson person) - { - var validationResult = taxPersonValidator.Validate(person); - if (!validationResult.IsValid) - { - var errorMessageLine = string.Join(";", validationResult.Errors.Select(x => x.ErrorMessage)); - return Task.FromResult>($"validation failed: {errorMessageLine}"); - } - - var tariffItems = - tariffData.Get(new TaxFilterModel - { - Year = calculationYear, - Canton = canton.ToString(), - }) - .OrderBy(item => item.TaxAmount); - - return Map(person.CivilStatus) - - // get all income level candidate - .Map(typeId => tariffItems - .Where(item => item.TariffType == (int)typeId) - .Where(item => item.TaxType == TaxTypeId) - .Where(item => item.IncomeLevel <= person.TaxableAmount) - .OrderByDescending(item => item.IncomeLevel) - .DefaultIfEmpty(new TaxTariffModel()) - .First()) - - // calculate result - .Map(tariff => CalculateIncomeTax(person, tariff)) - .Match>( - Some: r => r, - None: () => "Tariff not available") - .AsTask(); - } - - private BasisTaxResult CalculateIncomeTax(BasisTaxPerson person, TaxTariffModel tariff) - { - var referenceTaxableIncome = - person.TaxableAmount - (person.TaxableAmount % tariff.IncomeIncrement); - - var incrementMultiplier = (referenceTaxableIncome - tariff.IncomeLevel) / tariff.IncomeIncrement; - - var baseTaxAmount = (incrementMultiplier * tariff.TaxTariffRatePercent) + tariff.TaxAmount; - - return new BasisTaxResult - { - DeterminingFactorTaxableAmount = referenceTaxableIncome, - TaxAmount = baseTaxAmount, - }; - } - - private Option Map(Option status) - { - return status.Match( - Some: s => s switch - { - CivilStatus.Undefined => Option.None, - CivilStatus.Single => TariffType.Base, - CivilStatus.Married => TariffType.Married, - _ => Option.None - }, - None: () => Option.None); - } - } -} diff --git a/src/TaxCalculator/CheckSettingsConnector.cs b/src/TaxCalculator/CheckSettingsConnector.cs deleted file mode 100644 index eac66dab..00000000 --- a/src/TaxCalculator/CheckSettingsConnector.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; -using PensionCoach.Tools.CommonUtils; -using PensionCoach.Tools.TaxCalculator.Abstractions; - -namespace PensionCoach.Tools.TaxCalculator; - -public class CheckSettingsConnector : ICheckSettingsConnector -{ - private readonly IConfiguration _configuration; - - public CheckSettingsConnector(IConfiguration configuration) - { - _configuration = configuration; - } - - public Task> GetAsync() - { - _configuration.GetApplicationMode(); - - var settings = new Dictionary { { "Steuerrechner", _configuration.GetApplicationMode().ToString() } }; - - return Task.FromResult(settings); - } -} diff --git a/src/TaxCalculator/Estv/EstvFullCapitalBenefitTaxCalculator.cs b/src/TaxCalculator/Estv/EstvFullCapitalBenefitTaxCalculator.cs deleted file mode 100644 index f8ee97c7..00000000 --- a/src/TaxCalculator/Estv/EstvFullCapitalBenefitTaxCalculator.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Estv; - -/// -/// Facade for the ESTV tax calculation service. -/// -public class EstvFullCapitalBenefitTaxCalculator : IFullCapitalBenefitTaxCalculator -{ - private readonly IEstvTaxCalculatorClient estvTaxCalculatorClient; - private readonly ITaxSupportedYearProvider taxSupportedYearProvider; - - public EstvFullCapitalBenefitTaxCalculator( - IEstvTaxCalculatorClient estvTaxCalculatorClient, - ITaxSupportedYearProvider taxSupportedYearProvider) - { - this.estvTaxCalculatorClient = estvTaxCalculatorClient; - this.taxSupportedYearProvider = taxSupportedYearProvider; - } - - public async Task> CalculateAsync( - int calculationYear, - MunicipalityModel municipality, - CapitalBenefitTaxPerson person, - bool withMaxAvailableCalculationYear = false) - { - if (!municipality.EstvTaxLocationId.HasValue) - { - return "ESTV tax location id is null"; - } - - int supportedTaxYear = taxSupportedYearProvider.MapToSupportedYear(calculationYear); - - SimpleCapitalTaxResult calculationResult = await estvTaxCalculatorClient.CalculateCapitalBenefitTaxAsync( - municipality.EstvTaxLocationId.Value, supportedTaxYear, person); - - decimal municipalityRate = calculationResult.TaxCanton == 0 - ? decimal.Zero - : calculationResult.TaxCity / (decimal)calculationResult.TaxCanton * 100M; - - return new FullCapitalBenefitTaxResult - { - FederalResult = new BasisTaxResult { TaxAmount = calculationResult.TaxFed }, - StateResult = new CapitalBenefitTaxResult - { - MunicipalityRate = municipalityRate, - CantonRate = 100, - ChurchTax = new ChurchTaxResult - { - TaxAmount = calculationResult.TaxChurch, - }, - BasisTax = new BasisTaxResult - { - TaxAmount = calculationResult.TaxCanton, - DeterminingFactorTaxableAmount = municipalityRate - } - } - }; - } -} diff --git a/src/TaxCalculator/Estv/EstvFullTaxCalculator.cs b/src/TaxCalculator/Estv/EstvFullTaxCalculator.cs deleted file mode 100644 index 9f558cfd..00000000 --- a/src/TaxCalculator/Estv/EstvFullTaxCalculator.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions; -using PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Estv; - -public class EstvFullTaxCalculator : IFullWealthAndIncomeTaxCalculator -{ - private readonly IEstvTaxCalculatorClient estvTaxCalculatorClient; - private readonly ITaxSupportedYearProvider taxSupportedYearProvider; - - public EstvFullTaxCalculator(IEstvTaxCalculatorClient estvTaxCalculatorClient, ITaxSupportedYearProvider taxSupportedYearProvider) - { - this.estvTaxCalculatorClient = estvTaxCalculatorClient; - this.taxSupportedYearProvider = taxSupportedYearProvider; - } - - public async Task> CalculateAsync( - int calculationYear, MunicipalityModel municipality, TaxPerson person, bool withMaxAvailableCalculationYear = false) - { - if (!municipality.EstvTaxLocationId.HasValue) - { - return "ESTV tax location id is null"; - } - - int supportedTaxYear = taxSupportedYearProvider.MapToSupportedYear(calculationYear); - - SimpleTaxResult estvResult = await estvTaxCalculatorClient.CalculateIncomeAndWealthTaxAsync( - municipality.EstvTaxLocationId.Value, supportedTaxYear, person); - - decimal simpleTaxRate = decimal.Zero; - if (estvResult.IncomeTaxCanton > decimal.Zero) - { - simpleTaxRate = estvResult.IncomeTaxCity / (decimal)estvResult.IncomeTaxCanton * 100M; - } - - decimal wealthTaxRate = decimal.Zero; - if (estvResult.FortuneTaxCanton > decimal.Zero) - { - simpleTaxRate = estvResult.FortuneTaxCity / (decimal)estvResult.FortuneTaxCanton * 100M; - } - - return new FullTaxResult - { - FederalTaxResult = new BasisTaxResult - { - TaxAmount = estvResult.IncomeTaxFed, - DeterminingFactorTaxableAmount = decimal.Zero - }, - StateTaxResult = new StateTaxResult - { - BasisIncomeTax = new BasisTaxResult - { - TaxAmount = estvResult.IncomeTaxCanton, - DeterminingFactorTaxableAmount = simpleTaxRate - }, - BasisWealthTax = new BasisTaxResult - { - TaxAmount = estvResult.FortuneTaxCanton, - DeterminingFactorTaxableAmount = wealthTaxRate - }, - ChurchTax = new ChurchTaxResult - { - TaxAmount = estvResult.IncomeTaxChurch + estvResult.FortuneTaxChurch, - TaxAmountPartner = Option.None, - }, - PollTaxAmount = estvResult.PersonalTax, - CantonRate = 100M, - MunicipalityRate = simpleTaxRate - } - }; - } -} diff --git a/src/TaxCalculator/Estv/EstvMunicipalityConnector.cs b/src/TaxCalculator/Estv/EstvMunicipalityConnector.cs deleted file mode 100644 index 21e83c95..00000000 --- a/src/TaxCalculator/Estv/EstvMunicipalityConnector.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using AutoMapper; -using LanguageExt; -using Microsoft.EntityFrameworkCore; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using Tax.Data; -using Tax.Data.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Estv; - -public class EstvMunicipalityConnector : IMunicipalityConnector -{ - private readonly IMapper mapper; - private readonly MunicipalityDbContext municipalityDbContext; - - public EstvMunicipalityConnector( - IMapper mapper, - MunicipalityDbContext municipalityDbContext) - { - this.mapper = mapper; - this.municipalityDbContext = municipalityDbContext; - this.municipalityDbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - } - - public Task> GetAllAsync() - { - return Task.FromResult( - mapper.Map>( - municipalityDbContext.MunicipalityEntities.ToList())); - } - - /// - /// Searches the specified search filter. - /// - /// The search filter. - /// List of municipalities. - public IEnumerable Search(MunicipalitySearchFilter searchFilter) - { - IQueryable result = municipalityDbContext.MunicipalityEntities; - - if (searchFilter.Canton != Canton.Undefined) - { - result = result.Where(item => item.Canton == searchFilter.Canton.ToString()); - } - - if (!string.IsNullOrEmpty(searchFilter.Name)) - { - result = - result.Where(item => item.Name.Contains(searchFilter.Name)); - } - - foreach (MunicipalityEntity entity in result) - { - var model = mapper.Map(entity); - - if (searchFilter.YearOfValidity.HasValue) - { - if (!model.DateOfMutation.HasValue) - { - yield return model; - } - else if (model.DateOfMutation.Value.Year > searchFilter.YearOfValidity) - { - yield return model; - } - } - else - { - yield return model; - } - } - } - - public Task> GetAsync(int bfsNumber, int year) - { - Option entity = - municipalityDbContext.MunicipalityEntities - .FirstOrDefault(item => item.BfsNumber == bfsNumber - && string.IsNullOrEmpty(item.DateOfMutation)); - - return entity - .Match>( - Some: item => mapper.Map(item), - None: () => $"Municipality not found by BFS number {bfsNumber}") - .AsTask(); - } - - /// - public Task> GetAllSupportTaxCalculationAsync() - { - const int maxEstvSupportedYear = 2022; - - IReadOnlyCollection list = municipalityDbContext.MunicipalityEntities - .Where(item => item.TaxLocationId != null) - .OrderBy(item => item.Canton) - .ThenBy(item => item.Name) - .Select(item => new TaxSupportedMunicipalityModel - { - BfsMunicipalityNumber = item.BfsNumber, - Name = item.Name, - Canton = Enum.Parse(item.Canton), - MaxSupportedYear = maxEstvSupportedYear, - EstvTaxLocationId = item.TaxLocationId - }) - .ToList(); - - return Task.FromResult(list); - } -} diff --git a/src/TaxCalculator/Estv/EstvTaxSupportedYearProvider.cs b/src/TaxCalculator/Estv/EstvTaxSupportedYearProvider.cs deleted file mode 100644 index bee9431e..00000000 --- a/src/TaxCalculator/Estv/EstvTaxSupportedYearProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Linq; -using PensionCoach.Tools.TaxCalculator.Abstractions; - -namespace PensionCoach.Tools.TaxCalculator.Estv; - -public class EstvTaxSupportedYearProvider : ITaxSupportedYearProvider -{ - private readonly int[] supportedTaxYears = { 2019, 2020, 2021, 2022, 2023 }; - - public int[] GetSupportedTaxYears() - { - return supportedTaxYears; - } - - public int MapToSupportedYear(int taxYear) - { - return GetSupportedTaxYears().Max(); - } -} diff --git a/src/TaxCalculator/Internals/SearchMunicipalityRequest.cs b/src/TaxCalculator/Internals/SearchMunicipalityRequest.cs deleted file mode 100644 index 668ee351..00000000 --- a/src/TaxCalculator/Internals/SearchMunicipalityRequest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace PensionCoach.Tools.TaxCalculator.Internals; - -internal record SearchMunicipalityRequest(string Zipcode, string Name); diff --git a/src/TaxCalculator/Internals/SearchResultType.cs b/src/TaxCalculator/Internals/SearchResultType.cs deleted file mode 100644 index 38fcdc17..00000000 --- a/src/TaxCalculator/Internals/SearchResultType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace PensionCoach.Tools.TaxCalculator.Internals; - -internal enum SearchResultType -{ - None, - Match, - NotUnique, - NotSet -} diff --git a/src/TaxCalculator/Internals/TaxLocationSearchHolder.cs b/src/TaxCalculator/Internals/TaxLocationSearchHolder.cs deleted file mode 100644 index 4ce80d2b..00000000 --- a/src/TaxCalculator/Internals/TaxLocationSearchHolder.cs +++ /dev/null @@ -1,15 +0,0 @@ -using PensionCoach.Tools.EstvTaxCalculators.Abstractions.Models; -using Tax.Data.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Internals; - -internal record TaxLocationSearchHolder -{ - public MunicipalityEntity MunicipalityEntity { get; init; } - - public TaxLocation[] TaxLocations { get; init; } - - public int SearchLevel { get; set; } - - public SearchResultType SearchResultType { get; set; } -} diff --git a/src/TaxCalculator/MarginalTaxCurveCalculatorConnector.cs b/src/TaxCalculator/MarginalTaxCurveCalculatorConnector.cs deleted file mode 100644 index e3f9cc58..00000000 --- a/src/TaxCalculator/MarginalTaxCurveCalculatorConnector.cs +++ /dev/null @@ -1,201 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator; - -public class MarginalTaxCurveCalculatorConnector : IMarginalTaxCurveCalculatorConnector -{ - private readonly IFullWealthAndIncomeTaxCalculator fullWealthAndIncomeTaxCalculator; - private readonly IFullCapitalBenefitTaxCalculator fullCapitalBenefitTaxCalculator; - private readonly IMunicipalityConnector municipalityResolver; - - public MarginalTaxCurveCalculatorConnector( - IFullWealthAndIncomeTaxCalculator fullWealthAndIncomeTaxCalculator, - IFullCapitalBenefitTaxCalculator fullCapitalBenefitTaxCalculator, - IMunicipalityConnector municipalityResolver) - { - this.fullWealthAndIncomeTaxCalculator = fullWealthAndIncomeTaxCalculator; - this.fullCapitalBenefitTaxCalculator = fullCapitalBenefitTaxCalculator; - this.municipalityResolver = municipalityResolver; - } - - public async Task> CalculateIncomeTaxCurveAsync( - int calculationYear, - int bfsMunicipalityId, - TaxPerson person, - int lowerLimit, - int upperLimit, - int numberOfSamples) - { - MarginalTaxCurveResult result = new(); - - Either municipalityResult = await municipalityResolver - .GetAsync(bfsMunicipalityId, calculationYear); - - (await municipalityResult - .MapAsync(CalculateInternalAsync)) - .Iter(t => result.MarginalTaxCurve = t); - - (await municipalityResult - .MapAsync(municipalityModel => CalculateSingleMarginalTaxRate(municipalityModel, person))) - .Iter(taxRate => taxRate.Iter(rate => result.CurrentMarginalTaxRate = rate)); - - Merge(result); - - return result; - - void Merge(MarginalTaxCurveResult beforeMergeResult) - { - if (beforeMergeResult.CurrentMarginalTaxRate is null) - { - return; - } - - if (beforeMergeResult.MarginalTaxCurve.All(p => p.Salary != beforeMergeResult.CurrentMarginalTaxRate.Salary)) - { - beforeMergeResult.MarginalTaxCurve.Add(beforeMergeResult.CurrentMarginalTaxRate); - } - } - - async Task> CalculateInternalAsync( - MunicipalityModel municipalityModel) - { - int stepSize = (upperLimit - lowerLimit) / numberOfSamples; - - List incomeTaxes = new(); - - int currentSalary = lowerLimit; - - while (currentSalary <= upperLimit) - { - var currentPerson = person with - { - TaxableIncome = currentSalary, - TaxableFederalIncome = currentSalary - }; - - (await CalculateSingleMarginalTaxRate(municipalityModel, currentPerson)) - .Iter(r => - { - incomeTaxes.Add(new MarginalTaxInfo(r.Salary, r.Rate, r.TotalTaxAmount)); - }); - - currentSalary += stepSize; - } - - return incomeTaxes; - } - - async Task> CalculateSingleMarginalTaxRate( - MunicipalityModel municipalityModel, TaxPerson taxPerson) - { - const decimal delta = 100M; - - var x0Person = person with - { - TaxableIncome = taxPerson.TaxableIncome, - TaxableFederalIncome = taxPerson.TaxableFederalIncome - }; - - Either tax0 = - await fullWealthAndIncomeTaxCalculator.CalculateAsync(calculationYear, municipalityModel, x0Person); - - var x1Person = taxPerson with - { - TaxableIncome = taxPerson.TaxableIncome + delta, - TaxableFederalIncome = taxPerson.TaxableFederalIncome + delta - }; - - Either tax1 = - await fullWealthAndIncomeTaxCalculator.CalculateAsync(calculationYear, municipalityModel, x1Person); - - var r = from t0 in tax0 - from t1 in tax1 - select new MarginalTaxInfo( - taxPerson.TaxableIncome, - (t1.TotalTaxAmount - t0.TotalTaxAmount) / delta, - t0.TotalTaxAmount); - - return r; - } - } - - public async Task> CalculateCapitalBenefitTaxCurveAsync( - int calculationYear, - int bfsMunicipalityId, - CapitalBenefitTaxPerson person, - int lowerLimit, - int upperLimit, - int numberOfSamples) - { - MarginalTaxCurveResult result = new(); - - Either municipalityResult = await municipalityResolver - .GetAsync(bfsMunicipalityId, calculationYear); - - (await municipalityResult - .MapAsync(CalculateInternalAsync)) - .Iter(t => result.MarginalTaxCurve = t); - - (await municipalityResult - .MapAsync(model => CalculateSingleMarginalTaxRate(model, person))) - .Iter(taxRate => taxRate.Iter(rate => result.CurrentMarginalTaxRate = rate)); - - return result; - - async Task> CalculateSingleMarginalTaxRate( - MunicipalityModel municipalityModel, CapitalBenefitTaxPerson taxPerson) - { - decimal delta = 1000M; - - Either tax0 = - await fullCapitalBenefitTaxCalculator.CalculateAsync(calculationYear, municipalityModel, taxPerson); - - var x1Person = taxPerson with { TaxableCapitalBenefits = taxPerson.TaxableCapitalBenefits + delta }; - - Either tax1 = - await fullCapitalBenefitTaxCalculator.CalculateAsync(calculationYear, municipalityModel, x1Person); - - Either r = - from t0 in tax0 - from t1 in tax1 - select new MarginalTaxInfo( - taxPerson.TaxableCapitalBenefits, - (t1.TotalTaxAmount - t0.TotalTaxAmount) / delta, - t0.TotalTaxAmount); - - return r; - } - - async Task> CalculateInternalAsync( - MunicipalityModel municipalityModel) - { - int stepSize = (upperLimit - lowerLimit) / numberOfSamples; - - List taxes = new(); - - int currentSalary = lowerLimit; - - while (currentSalary <= upperLimit) - { - var currentPerson = person with - { - TaxableCapitalBenefits = currentSalary - }; - - (await CalculateSingleMarginalTaxRate(municipalityModel, currentPerson)) - .Iter(taxes.Add); - - currentSalary += stepSize; - } - - return taxes; - } - } -} diff --git a/src/TaxCalculator/Mock/MockedFullTaxCalculator.cs b/src/TaxCalculator/Mock/MockedFullTaxCalculator.cs deleted file mode 100644 index 227bd679..00000000 --- a/src/TaxCalculator/Mock/MockedFullTaxCalculator.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Proprietary; - -namespace PensionCoach.Tools.TaxCalculator.Mock; - -public class MockedFullTaxCalculator - : IFullWealthAndIncomeTaxCalculator, IFullCapitalBenefitTaxCalculator, IMunicipalityConnector, ITaxSupportedYearProvider -{ - const int DefaultBfsMunicipalityId = 261; - const Canton DefaultCanton = Canton.ZH; - - private readonly ProprietaryFullTaxCalculator fullTaxCalculator; - private readonly ProprietaryFullCapitalBenefitTaxCalculator fullCapitalBenefitTaxCalculator; - - public MockedFullTaxCalculator( - ProprietaryFullTaxCalculator fullTaxCalculator, - ProprietaryFullCapitalBenefitTaxCalculator fullCapitalBenefitTaxCalculator) - { - this.fullTaxCalculator = fullTaxCalculator; - this.fullCapitalBenefitTaxCalculator = fullCapitalBenefitTaxCalculator; - } - - public Task> CalculateAsync( - int calculationYear, MunicipalityModel municipality, TaxPerson person, bool withMaxAvailableCalculationYear = false) - { - MunicipalityModel adaptedModel = GetAdaptedModel(); - - return fullTaxCalculator.CalculateAsync(calculationYear, adaptedModel, person, withMaxAvailableCalculationYear); - } - - public Task> CalculateAsync( - int calculationYear, MunicipalityModel municipality, CapitalBenefitTaxPerson person, bool withMaxAvailableCalculationYear = false) - { - MunicipalityModel adaptedModel = GetAdaptedModel(); - - return fullCapitalBenefitTaxCalculator.CalculateAsync(calculationYear, adaptedModel, person, withMaxAvailableCalculationYear); - } - - public Task> GetAllAsync() - { - return Search(null).AsTask(); - } - - public IEnumerable Search(MunicipalitySearchFilter searchFilter) - { - yield return GetAdaptedModel(); - } - - public Task> GetAsync(int bfsNumber, int year) - { - Either municipality = GetAdaptedModel(); - - return municipality.AsTask(); - } - - public Task> GetAllSupportTaxCalculationAsync() - { - MunicipalityModel adaptedModel = GetAdaptedModel(); - - IReadOnlyCollection municipalities = new List - { - new() { MaxSupportedYear = 2022, BfsMunicipalityNumber = adaptedModel.BfsNumber, Canton = adaptedModel.Canton } - }; - - return municipalities.AsTask(); - } - - public int[] GetSupportedTaxYears() - { - int[] years = { 2022, 2023 }; - - return years; - } - - public int MapToSupportedYear(int taxYear) - { - return GetSupportedTaxYears().Max(); - } - - private MunicipalityModel GetAdaptedModel() - { - return new MunicipalityModel { BfsNumber = DefaultBfsMunicipalityId, Canton = DefaultCanton }; - } -} diff --git a/src/TaxCalculator/Proprietary/ProprietaryFederalTaxCalculator.cs b/src/TaxCalculator/Proprietary/ProprietaryFederalTaxCalculator.cs deleted file mode 100644 index ba5afa68..00000000 --- a/src/TaxCalculator/Proprietary/ProprietaryFederalTaxCalculator.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using FluentValidation; -using FluentValidation.Results; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; -using Tax.Data; -using Tax.Data.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Proprietary -{ - public class ProprietaryFederalTaxCalculator : IFederalTaxCalculator - { - private readonly IValidator taxPersonValidator; - private readonly Func federalDbContextFunc; - - public ProprietaryFederalTaxCalculator( - IValidator taxPersonValidator, - Func federalDbContextFunc) - { - this.taxPersonValidator = taxPersonValidator; - this.federalDbContextFunc = federalDbContextFunc; - } - - /// - public Task> CalculateAsync(int calculationYear, FederalTaxPerson person) - { - Option validationResult = taxPersonValidator.Validate(person); - - using var dbContext = federalDbContextFunc(); - return validationResult - .Where(r => !r.IsValid) - .Map>(r => - { - var errorMessageLine = string.Join(";", r.Errors.Select(x => x.ErrorMessage)); - return $"validation failed: {errorMessageLine}"; - }) - .IfNone(true) - .Bind(_ => Map(person.CivilStatus)) - - // get all income level candidate - .Map(typeId => dbContext.Tariffs - .Where(item => item.Year == calculationYear) - .Where(item => item.TariffType == (int)typeId) - .ToList() - .Where(item => item.IncomeLevel <= person.TaxableAmount) - .OrderByDescending(item => item.IncomeLevel) - .DefaultIfEmpty(new FederalTaxTariffModel()) - .First()) - - // calculate result - .Map(tariff => CalculateTax(person, tariff)) - .AsTask(); - } - - private BasisTaxResult CalculateTax(FederalTaxPerson person, FederalTaxTariffModel tariff) - { - var referenceTaxableIncome = - person.TaxableAmount - person.TaxableAmount % tariff.IncomeIncrement; - - var incrementMultiplier = (referenceTaxableIncome - tariff.IncomeLevel) / tariff.IncomeIncrement; - - var baseTaxAmount = incrementMultiplier * tariff.TaxIncrement + tariff.TaxAmount; - - return new BasisTaxResult - { - DeterminingFactorTaxableAmount = referenceTaxableIncome, - TaxAmount = baseTaxAmount, - }; - } - - private Either Map(Option status) - { - return status.Match>( - Some: s => s switch - { - CivilStatus.Single => TariffType.Base, - CivilStatus.Married => TariffType.Married, - _ => TariffType.Undefined - }, - None: () => "Civil status unknown"); - } - } -} diff --git a/src/TaxCalculator/Proprietary/ProprietaryFullCapitalBenefitTaxCalculator.cs b/src/TaxCalculator/Proprietary/ProprietaryFullCapitalBenefitTaxCalculator.cs deleted file mode 100644 index ba0b303f..00000000 --- a/src/TaxCalculator/Proprietary/ProprietaryFullCapitalBenefitTaxCalculator.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Text; -using System.Threading.Tasks; -using AutoMapper; -using LanguageExt; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - -namespace PensionCoach.Tools.TaxCalculator.Proprietary -{ - public class ProprietaryFullCapitalBenefitTaxCalculator : IFullCapitalBenefitTaxCalculator - { - private readonly Func capitalBenefitCalculatorFunc; - private readonly IFederalCapitalBenefitTaxCalculator federalCalculator; - private readonly IMapper mapper; - - public ProprietaryFullCapitalBenefitTaxCalculator( - Func capitalBenefitCalculatorFunc, - IFederalCapitalBenefitTaxCalculator federalCalculator, - IMapper mapper) - { - this.capitalBenefitCalculatorFunc = capitalBenefitCalculatorFunc; - this.federalCalculator = federalCalculator; - this.mapper = mapper; - } - - /// - public async Task> CalculateAsync( - int calculationYear, - MunicipalityModel municipality, - CapitalBenefitTaxPerson capitalBenefitTaxPerson, - bool withMaxAvailableCalculationYear) - { - var maxCalculationYear = withMaxAvailableCalculationYear - ? Math.Min(calculationYear, 2019) - : calculationYear; - - var capitalBenefitTaxResultTask = - capitalBenefitCalculatorFunc(municipality.Canton) - .CalculateAsync(maxCalculationYear, municipality.BfsNumber, municipality.Canton, capitalBenefitTaxPerson); - - var federalTaxPerson = - mapper.Map(capitalBenefitTaxPerson); - - var federalTaxResultTask = - federalCalculator.CalculateAsync(maxCalculationYear, federalTaxPerson); - - await Task.WhenAll(capitalBenefitTaxResultTask, federalTaxResultTask); - - var sb = new StringBuilder(); - - var stateTaxResult = await capitalBenefitTaxResultTask; - var federalTaxResult = await federalTaxResultTask; - - stateTaxResult.MapLeft(r => sb.AppendLine(r)); - federalTaxResult.MapLeft(r => sb.AppendLine(r)); - - var fullResult = - from s in stateTaxResult.ToOption() - from f in federalTaxResult.ToOption() - select new FullCapitalBenefitTaxResult - { - StateResult = s, - FederalResult = f, - }; - - return fullResult - .Match>( - Some: r => r, - None: () => sb.ToString()); - } - } -} diff --git a/src/TaxCalculator/Proprietary/ProprietaryFullTaxCalculator.cs b/src/TaxCalculator/Proprietary/ProprietaryFullTaxCalculator.cs deleted file mode 100644 index c439327c..00000000 --- a/src/TaxCalculator/Proprietary/ProprietaryFullTaxCalculator.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Threading.Tasks; -using AutoMapper; -using LanguageExt; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - -namespace PensionCoach.Tools.TaxCalculator.Proprietary -{ - public class ProprietaryFullTaxCalculator : IFullWealthAndIncomeTaxCalculator - { - private readonly IMapper mapper; - private readonly IStateTaxCalculator stateTaxCalculator; - private readonly IFederalTaxCalculator federalTaxCalculator; - - public ProprietaryFullTaxCalculator( - IMapper mapper, - IStateTaxCalculator stateTaxCalculator, - IFederalTaxCalculator federalTaxCalculator) - { - this.mapper = mapper; - this.stateTaxCalculator = stateTaxCalculator; - this.federalTaxCalculator = federalTaxCalculator; - } - - public async Task> CalculateAsync( - int calculationYear, - MunicipalityModel municipality, - TaxPerson person, - bool withMaxAvailableCalculationYear) - { - var maxCalculationYear = withMaxAvailableCalculationYear - ? Math.Min(calculationYear, 2019) - : calculationYear; - - var federalTaxPerson = mapper.Map(person); - - var stateTaxResultTask = stateTaxCalculator.CalculateAsync(maxCalculationYear, municipality.BfsNumber, municipality.Canton, person); - var federalTaxResultTask = federalTaxCalculator.CalculateAsync(maxCalculationYear, federalTaxPerson); - - await Task.WhenAll(stateTaxResultTask, federalTaxResultTask); - - var stateTaxResult = await stateTaxResultTask; - var federalTaxResult = await federalTaxResultTask; - - var fullResult = new FullTaxResult(); - - return stateTaxResult - .Bind(r => - { - fullResult.StateTaxResult = r; - return federalTaxResult; - }) - .Map(r => - { - fullResult.FederalTaxResult = r; - return fullResult; - }); - } - } -} diff --git a/src/TaxCalculator/Proprietary/ProprietaryMunicipalityConnector.cs b/src/TaxCalculator/Proprietary/ProprietaryMunicipalityConnector.cs deleted file mode 100644 index 6f947f63..00000000 --- a/src/TaxCalculator/Proprietary/ProprietaryMunicipalityConnector.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using AutoMapper; -using LanguageExt; -using Microsoft.EntityFrameworkCore; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using Tax.Data; -using Tax.Data.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Proprietary; - -public class ProprietaryMunicipalityConnector : IMunicipalityConnector -{ - private readonly IMapper mapper; - private readonly MunicipalityDbContext municipalityDbContext; - private readonly Func dbContext; - - public ProprietaryMunicipalityConnector( - IMapper mapper, - MunicipalityDbContext municipalityDbContext, - Func dbContext) - { - this.mapper = mapper; - this.municipalityDbContext = municipalityDbContext; - this.municipalityDbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - this.dbContext = dbContext; - } - - public Task> GetAllAsync() - { - return Task.FromResult( - mapper.Map>( - municipalityDbContext.MunicipalityEntities.ToList())); - } - - /// - /// Searches the specified search filter. - /// - /// The search filter. - /// List of municipalities. - public IEnumerable Search(MunicipalitySearchFilter searchFilter) - { - IQueryable result = municipalityDbContext.MunicipalityEntities; - - if (searchFilter.Canton != Canton.Undefined) - { - result = result.Where(item => item.Canton == searchFilter.Canton.ToString()); - } - - if (!string.IsNullOrEmpty(searchFilter.Name)) - { - result = - result.Where(item => item.Name.Contains(searchFilter.Name)); - } - - foreach (var entity in result) - { - var model = mapper.Map(entity); - - if (searchFilter.YearOfValidity.HasValue) - { - if (!model.DateOfMutation.HasValue) - { - yield return model; - } - else if (model.DateOfMutation.Value.Year > searchFilter.YearOfValidity) - { - yield return model; - } - } - else - { - yield return model; - } - } - } - - public Task> GetAsync(int bfsNumber, int year) - { - Option entity = - municipalityDbContext.MunicipalityEntities - .FirstOrDefault(item => item.BfsNumber == bfsNumber - && string.IsNullOrEmpty(item.DateOfMutation)); - - return entity - .Match>( - Some: item => mapper.Map(item), - None: () => $"Municipality not found by BFS number {bfsNumber}") - .AsTask(); - } - - /// - public Task> GetAllSupportTaxCalculationAsync() - { - using var ctx = dbContext(); - IReadOnlyCollection municipalities = - ctx.Rates - .AsEnumerable() - .GroupBy(keySelector => new - { - Id = keySelector.BfsId, - Name = keySelector.MunicipalityName, - keySelector.Canton, - }) - .Select(item => new TaxSupportedMunicipalityModel - { - BfsMunicipalityNumber = item.Key.Id, - Name = item.Key.Name, - Canton = Enum.Parse(item.Key.Canton), - MaxSupportedYear = item.Max(entity => entity.Year), - }) - .OrderBy(item => item.Name) - .ToList(); - - return Task.FromResult(municipalities); - } -} diff --git a/src/TaxCalculator/TaxCalculator.csproj b/src/TaxCalculator/TaxCalculator.csproj deleted file mode 100644 index f7a7a099..00000000 --- a/src/TaxCalculator/TaxCalculator.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - net7.0 - PensionCoach.Tools.TaxCalculator - PensionCoach.Tools.TaxCalculator - - - - - - - - - - - - - - - - diff --git a/src/TaxCalculator/TaxCalculatorConnector.cs b/src/TaxCalculator/TaxCalculatorConnector.cs deleted file mode 100644 index 667420ec..00000000 --- a/src/TaxCalculator/TaxCalculatorConnector.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Threading.Tasks; -using LanguageExt; -using PensionCoach.Tools.CommonTypes.Municipality; -using PensionCoach.Tools.CommonTypes.Tax; -using PensionCoach.Tools.TaxCalculator.Abstractions; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator; - -public class TaxCalculatorConnector : ITaxCalculatorConnector -{ - private readonly int[] supportedTaxYears = { 2019 }; - - private readonly IFullWealthAndIncomeTaxCalculator fullWealthAndIncomeTaxCalculator; - private readonly IFullCapitalBenefitTaxCalculator fullCapitalBenefitTaxCalculator; - private readonly IMunicipalityConnector municipalityResolver; - - public TaxCalculatorConnector( - IFullWealthAndIncomeTaxCalculator fullWealthAndIncomeTaxCalculator, - IFullCapitalBenefitTaxCalculator fullCapitalBenefitTaxCalculator, - IMunicipalityConnector municipalityResolver) - { - this.fullWealthAndIncomeTaxCalculator = fullWealthAndIncomeTaxCalculator; - this.fullCapitalBenefitTaxCalculator = fullCapitalBenefitTaxCalculator; - this.municipalityResolver = municipalityResolver; - } - - public async Task> CalculateAsync( - int calculationYear, int bfsMunicipalityId, TaxPerson person, bool withMaxAvailableCalculationYear = false) - { - Either municipalityData = - await municipalityResolver.GetAsync(bfsMunicipalityId, calculationYear); - - return await municipalityData - .BindAsync(m => fullWealthAndIncomeTaxCalculator.CalculateAsync( - calculationYear, m, person)); - } - - public async Task> CalculateAsync( - int calculationYear, int bfsMunicipalityId, CapitalBenefitTaxPerson person, bool withMaxAvailableCalculationYear = false) - { - Either municipalityData = - await municipalityResolver.GetAsync(bfsMunicipalityId, calculationYear); - - return await municipalityData - .BindAsync(m => fullCapitalBenefitTaxCalculator.CalculateAsync( - calculationYear, - m, - person)); - } - - public Task GetSupportedTaxYears() - { - return supportedTaxYears.AsTask(); - } -} diff --git a/src/TaxCalculator/Utils/TaxCalculatorExtensions.cs b/src/TaxCalculator/Utils/TaxCalculatorExtensions.cs deleted file mode 100644 index dd6252ef..00000000 --- a/src/TaxCalculator/Utils/TaxCalculatorExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using PensionCoach.Tools.CommonTypes; -using Tax.Data.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Utils -{ - public static class TaxCalculatorExtensions - { - public static decimal GetTaxRate(this ReligiousGroupType religiousGroupType, TaxRateEntity entity) - { - return 0M; - } - } -} diff --git a/src/TaxCalculator/Validators/AggregatedTaxResultValidator.cs b/src/TaxCalculator/Validators/AggregatedTaxResultValidator.cs deleted file mode 100644 index 4a091eb0..00000000 --- a/src/TaxCalculator/Validators/AggregatedTaxResultValidator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using FluentValidation; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models; - -namespace PensionCoach.Tools.TaxCalculator.Validators -{ - /// - public class AggregatedTaxResultValidator : AbstractValidator - { - public AggregatedTaxResultValidator() - { - RuleFor(x => x.IncomeTax).NotNull(); - RuleFor(x => x.WealthTax).NotNull(); - } - } -} diff --git a/src/TaxCalculator/Validators/BasisTaxPersonValidator.cs b/src/TaxCalculator/Validators/BasisTaxPersonValidator.cs deleted file mode 100644 index 1710dcab..00000000 --- a/src/TaxCalculator/Validators/BasisTaxPersonValidator.cs +++ /dev/null @@ -1,18 +0,0 @@ -using FluentValidation; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - -namespace PensionCoach.Tools.TaxCalculator.Validators -{ - public class BasisTaxPersonValidator : AbstractValidator - { - private const string ValueMustNotBeNegative = "Value must not be negative"; - - public BasisTaxPersonValidator() - { - Include(new TaxPersonBasicValidator()); - RuleFor(p => p.TaxableAmount) - .GreaterThanOrEqualTo(decimal.Zero) - .WithMessage(ValueMustNotBeNegative); - } - } -} diff --git a/src/TaxCalculator/Validators/CantonValidator.cs b/src/TaxCalculator/Validators/CantonValidator.cs deleted file mode 100644 index 2f5abb9b..00000000 --- a/src/TaxCalculator/Validators/CantonValidator.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Linq; -using FluentValidation; -using PensionCoach.Tools.CommonTypes; - -namespace PensionCoach.Tools.TaxCalculator.Validators -{ - public class CantonValidator : AbstractValidator - { - private static readonly Canton[] SupportedCantons = { Canton.ZH, Canton.SG, Canton.SO }; - - public CantonValidator() - { - RuleFor(canton => canton) - .Must(c => SupportedCantons.Contains(c)) - .WithMessage(c => $"Canton {c} is not yet supported"); - } - } -} diff --git a/src/TaxCalculator/Validators/CapitalBenefitsTaxPersonValidator.cs b/src/TaxCalculator/Validators/CapitalBenefitsTaxPersonValidator.cs deleted file mode 100644 index 2ab711e6..00000000 --- a/src/TaxCalculator/Validators/CapitalBenefitsTaxPersonValidator.cs +++ /dev/null @@ -1,19 +0,0 @@ -using FluentValidation; -using PensionCoach.Tools.CommonTypes.Tax; - -namespace PensionCoach.Tools.TaxCalculator.Validators -{ - public class CapitalBenefitsTaxPersonValidator : AbstractValidator - { - private const string ValueMustNotBeNegative = "Value must not be negative"; - - public CapitalBenefitsTaxPersonValidator() - { - Include(new TaxPersonBasicValidator()); - - RuleFor(p => p.TaxableCapitalBenefits) - .GreaterThanOrEqualTo(decimal.Zero) - .WithMessage(ValueMustNotBeNegative); - } - } -} diff --git a/src/TaxCalculator/Validators/ChurchTaxPersonValidator.cs b/src/TaxCalculator/Validators/ChurchTaxPersonValidator.cs deleted file mode 100644 index 47c75f93..00000000 --- a/src/TaxCalculator/Validators/ChurchTaxPersonValidator.cs +++ /dev/null @@ -1,14 +0,0 @@ -using FluentValidation; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - -namespace PensionCoach.Tools.TaxCalculator.Validators -{ - /// - public class ChurchTaxPersonValidator : AbstractValidator - { - public ChurchTaxPersonValidator() - { - Include(new TaxPersonBasicValidator()); - } - } -} diff --git a/src/TaxCalculator/Validators/FederalTaxPersonValidator.cs b/src/TaxCalculator/Validators/FederalTaxPersonValidator.cs deleted file mode 100644 index 963970c3..00000000 --- a/src/TaxCalculator/Validators/FederalTaxPersonValidator.cs +++ /dev/null @@ -1,19 +0,0 @@ -using FluentValidation; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - -namespace PensionCoach.Tools.TaxCalculator.Validators -{ - public class FederalTaxPersonValidator : AbstractValidator - { - private const string ValueMustNotBeNegative = "Value must not be negative"; - - public FederalTaxPersonValidator() - { - Include(new TaxPersonBasicValidator()); - - RuleFor(p => p.TaxableAmount) - .GreaterThanOrEqualTo(decimal.Zero) - .WithMessage(ValueMustNotBeNegative); - } - } -} diff --git a/src/TaxCalculator/Validators/PollTaxPersonValidator.cs b/src/TaxCalculator/Validators/PollTaxPersonValidator.cs deleted file mode 100644 index f23d9f78..00000000 --- a/src/TaxCalculator/Validators/PollTaxPersonValidator.cs +++ /dev/null @@ -1,13 +0,0 @@ -using FluentValidation; -using PensionCoach.Tools.TaxCalculator.Abstractions.Models.Person; - -namespace PensionCoach.Tools.TaxCalculator.Validators -{ - public class PollTaxPersonValidator : AbstractValidator - { - public PollTaxPersonValidator() - { - Include(new TaxPersonBasicValidator()); - } - } -} diff --git a/src/TaxCalculator/Validators/TaxPersonBasicValidator.cs b/src/TaxCalculator/Validators/TaxPersonBasicValidator.cs deleted file mode 100644 index e47ab353..00000000 --- a/src/TaxCalculator/Validators/TaxPersonBasicValidator.cs +++ /dev/null @@ -1,18 +0,0 @@ -using FluentValidation; -using PensionCoach.Tools.CommonTypes; -using PensionCoach.Tools.CommonTypes.Tax; - -namespace PensionCoach.Tools.TaxCalculator.Validators -{ - public class TaxPersonBasicValidator : AbstractValidator - { - public TaxPersonBasicValidator() - { - RuleFor(x => x.Name).NotNull().NotEmpty(); - - RuleFor(x => x.NumberOfChildren).GreaterThanOrEqualTo(0); - - RuleFor(x => x.CivilStatus).Must(x => x != CivilStatus.Undefined); - } - } -} diff --git a/src/TaxCalculator/Validators/TaxPersonValidator.cs b/src/TaxCalculator/Validators/TaxPersonValidator.cs deleted file mode 100644 index f4b36e89..00000000 --- a/src/TaxCalculator/Validators/TaxPersonValidator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using FluentValidation; -using PensionCoach.Tools.CommonTypes.Tax; - -namespace PensionCoach.Tools.TaxCalculator.Validators -{ - public class TaxPersonValidator : AbstractValidator - { - private const string ValueMustNotBeNegative = "Value must not be negative"; - - public TaxPersonValidator() - { - Include(new TaxPersonBasicValidator()); - - RuleFor(p => p.TaxableIncome) - .GreaterThanOrEqualTo(decimal.Zero) - .WithMessage(ValueMustNotBeNegative); - RuleFor(p => p.TaxableWealth) - .GreaterThanOrEqualTo(decimal.Zero) - .WithMessage(ValueMustNotBeNegative); - RuleFor(p => p.TaxableFederalIncome) - .GreaterThanOrEqualTo(decimal.Zero) - .WithMessage(ValueMustNotBeNegative); - } - } -} diff --git a/src/aspire/Extensions.cs b/src/aspire/Extensions.cs new file mode 100644 index 00000000..5dae03f8 --- /dev/null +++ b/src/aspire/Extensions.cs @@ -0,0 +1,114 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Logs; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Aspire; + +public static class Extensions +{ + public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder) + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + return builder; + } + + public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder) + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + if (builder.Environment.IsDevelopment()) + { + // We want to view all traces in development + tracing.SetSampler(new AlwaysOnSampler()); + } + + tracing.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder) + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.Configure(logging => logging.AddOtlpExporter()); + builder.Services.ConfigureOpenTelemetryMeterProvider(metrics => metrics.AddOtlpExporter()); + builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter()); + } + + // Uncomment the following lines to enable the Prometheus exporter (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package) + // builder.Services.AddOpenTelemetry() + // .WithMetrics(metrics => metrics.AddPrometheusExporter()); + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + + return builder; + } + + public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder) + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Uncomment the following line to enable the Prometheus endpoint (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package) + // app.MapPrometheusScrapingEndpoint(); + + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + + return app; + } +} diff --git a/src/aspire/aspire.csproj b/src/aspire/aspire.csproj new file mode 100644 index 00000000..b4d90858 --- /dev/null +++ b/src/aspire/aspire.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + diff --git a/src/global.json b/src/global.json new file mode 100644 index 00000000..dad2db5e --- /dev/null +++ b/src/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "8.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file diff --git a/tye.yaml b/tye.yaml deleted file mode 100644 index d53c3e48..00000000 --- a/tye.yaml +++ /dev/null @@ -1,32 +0,0 @@ -name: pensiontools -services: -- name: pensiontools-webapi - project: src/TaxCalculator.WebApi/TaxCalculator.WebApi.csproj - bindings: - - protocol: http - host: localhost - port: 57094 - env: - - name: ApplicationMode - value: Estv -- name: pensiontools-ui - project: src/BlazorApp/BlazorApp.csproj - bindings: - - port: 5001 - env: - - name: ASPNETCORE_ENVIRONMENT - value: tye -ingress: - - name: ingress - bindings: - - port: 8080 - protocol: https - rules: - - path: /api - service: pensiontools-webapi - preservePath: true - - path: /swagger - preservePath: true - service: pensiontools-webapi - - service: pensiontools-ui -