diff --git a/gross-to-net/README.md b/gross-to-net/README.md index 43a0eb6..d49a514 100644 --- a/gross-to-net/README.md +++ b/gross-to-net/README.md @@ -1,84 +1,75 @@ -# Gross-to-net Pay Check Coding Challenge - -Requirements: - -- Calculate the net pay as follows: net pay = taxable earnings - pre-tax deductions - taxes - post-tax deductions + non-taxable earnings -- Earning types - - Salary earnings are defined by a flat `amount` - - Hourly earnings are defined by an amount of `hours` and a `rate` -- Earnings that are not taxable are not withheld by deductions or taxes -- Pre-tax deductions are based on gross taxable-pay -- Flat vs percentage deductions (percentages encoded as `value / 100`) -- Taxes are based on gross taxable-pay less pre-tax deductions -- Taxes with a cap should not be withheld above the cap -- Post-tax deductions are based on gross taxable-pay less pre-tax deductions and taxes -- Deduction and tax priority - - When taxable earnings are less than deductions and taxes, withholding priority is as follows: pre-tax deductions before taxes before post-tax deductions. - - For each deduction and tax, a higher `priority` value indicates this item should be withheld first. - - If a deduction or tax is cannot be withheld to the full amount, the deficit is recorded. -- Output the following - - Gross pay - - Net pay to employee - - Amounts withheld for deductions and taxes by code with deficits - -Sample input object: - -```json -{ - "earnings": [ - { - "code": "overtime", - "type": "hourly", - "hours": 20, - "rate": 15, - "isTaxable": true - }, - { - "code": "regular", - "type": "hourly", - "hours": 20, - "rate": 13, - "isTaxable": false - }, - { - "code": "bonus", - "type": "salary", - "amount": 1500, - "isTaxable": true - } - ], - "deductions": [ - { - "code": "401k", - "type": "percentage", - "priority": 1, - "value": 10, - "isPreTax": false - }, - { - "code": "healthInsurance", - "type": "flat", - "priority": 2, - "value": 20, - "isPreTax": true - } - ], - "taxes": [ - { - "code": "federalIncome", - "type": "percentage", - "priority": 1, - "value": 10 - }, - { - "code": "fica", - "type": "cappedPercentage", - "priority": 2, - "value": 10, - "cap": 50 - } - ] -} +# Gross-to-Net Payroll Calculator + +Implement the `Calculate` method in `PayrollCalculator.cs` which accepts three collections as arguments... `earnings`, `deductions`, and `taxes` then returns a `PayrollResult` with `grossPay`, `grossTaxableEarnings`, a list of `witholdings`, and `netPay`. + +## Requirements + +### NetPay Calculation + +`NetPay = GrossTaxableEarnings - PreTaxDeductions - Taxes - PostTaxDeductions + NonTaxableEarnings` + +### Earnings + +- Types: `salary`, `hourly` +- Salary earnings have a flat `amount` +- Hourly earnings amount is `hours × rate` +- Only taxable earnings are included when calculating deductions/taxes + +### Deductions + +- Types: `flat`, `percentage` +- Flat decductions have a fixed dollar `value` +- Percentage deductions are calculated as `value / 100` of basis +- Pre-tax deductions reduce tax basis, calculated with gross taxable earnings +- Post-tax deductions do not reduce tax basis + +### Taxes + +- Types: `percentage`, `cappedPercentage` +- Percentage taxes are calculated as `value / 100` of basis using (gross taxable earnings - pre-tax deductions) +- Capped Percentage has a max value defined by the `cap` property + +### Witholdings + +- Types: `tax`, `deductions` +- Deductions are either Pre-Tax or Post-Tax depending on the `isPrefix` value. +- Witholding groups are applied using the following order: + - PreTax Deductions + - Taxes + - PostTax Deductions +- Within each group, withholdings are applied in order of their `priority` value (highest `priority` first) +- When a withholding exceeds available earnings the difference is added as a `deficit` as NetPay cannot go below zero. + +## Build and Run + +To test your solution, Program.main() contains a test harness for loading test data from a json file and comparing the expected output to the actual output provided by your `Calculate` method and report the result. + +A json file containing basic test cases is included in the project, `test-cases.json`, but you are encouraged to add additional test cases as needed. Additional test cases may be used to verify edge cases and common defects during your interview. + +### IDE + +An online VSCode codespace will be used during assesmments and candidates are free to install extensions available in the vs code default extension marketplace. + +Start Debug: `F5` + +### Commmand-line + +Navigate to the project root e.g. `gross-to-net/dotnet/` then use _dotnet_ CLI commands. + +Building from CLI + +```ps +dotnet build +``` + +Running from CLI + +```ps +dotnet run ``` -All test cases and solutions can be found in `test-cases.json` \ No newline at end of file +Show Help Text + +```ps +dotnet run -- --help +``` \ No newline at end of file diff --git a/gross-to-net/directory-structure.txt b/gross-to-net/directory-structure.txt new file mode 100644 index 0000000..d415746 --- /dev/null +++ b/gross-to-net/directory-structure.txt @@ -0,0 +1,64 @@ +Folder PATH listing for volume Windows +Volume serial number is 7EA5-7906 +C:. +| advanced-test-cases.json +| directory-structure.txt +| input.json +| INTERVIEWER_README.md +| README.md +| test-cases.json +| +\---dotnet + | GrossToNet.csproj + | PayrollCalculator.cs + | Program.cs + | + +---bin + | \---Debug + | \---net8.0 + | advanced-test-cases.json + | GrossToNet.deps.json + | GrossToNet.dll + | GrossToNet.exe + | GrossToNet.pdb + | GrossToNet.runtimeconfig.json + | input.json + | test-cases.json + | + +---models + | Deduction.cs + | Earning.cs + | PayrollData.cs + | PayrollResult.cs + | Tax.cs + | Withholding.cs + | + \---obj + | GrossToNet.csproj.nuget.dgspec.json + | GrossToNet.csproj.nuget.g.props + | GrossToNet.csproj.nuget.g.targets + | project.assets.json + | project.nuget.cache + | + \---Debug + \---net8.0 + | .NETCoreApp,Version=v8.0.AssemblyAttributes.cs + | apphost.exe + | GrossToNet.AssemblyInfo.cs + | GrossToNet.AssemblyInfoInputs.cache + | GrossToNet.assets.cache + | GrossToNet.csproj.CoreCompileInputs.cache + | GrossToNet.csproj.FileListAbsolute.txt + | GrossToNet.dll + | GrossToNet.GeneratedMSBuildEditorConfig.editorconfig + | GrossToNet.genruntimeconfig.cache + | GrossToNet.GlobalUsings.g.cs + | GrossToNet.pdb + | GrossToNet.sourcelink.json + | + +---ref + | GrossToNet.dll + | + \---refint + GrossToNet.dll + diff --git a/gross-to-net/dotnet/GrossToNet.csproj b/gross-to-net/dotnet/GrossToNet.csproj index 035ab5d..a53762f 100644 --- a/gross-to-net/dotnet/GrossToNet.csproj +++ b/gross-to-net/dotnet/GrossToNet.csproj @@ -7,9 +7,10 @@ enable enable + - - PreserveNewest + + Always - - + + \ No newline at end of file diff --git a/gross-to-net/dotnet/PayrollCalculator.cs b/gross-to-net/dotnet/PayrollCalculator.cs new file mode 100644 index 0000000..0b11e3b --- /dev/null +++ b/gross-to-net/dotnet/PayrollCalculator.cs @@ -0,0 +1,20 @@ +using GrossToNet.Models; + +namespace GrossToNet; + +/// +/// Payroll calculator that processes earnings, deductions, and taxes to compute net pay. +/// +/// CALCULATION: Net pay = gross pay - pre-tax deductions - taxes - post-tax deductions +/// +/// See README.md for detailed requirements and examples. +/// +public class PayrollCalculator +{ + public PayrollResult Calculate(IEnumerable earnings, IEnumerable deductions, IEnumerable taxes) + { + + // TODO: Implement payroll calculation logic here + throw new NotImplementedException("Payroll calculation not yet implemented. Please implement the Calculate method."); + } +} \ No newline at end of file diff --git a/gross-to-net/dotnet/Program.cs b/gross-to-net/dotnet/Program.cs index 0c8b6bf..b12551a 100644 --- a/gross-to-net/dotnet/Program.cs +++ b/gross-to-net/dotnet/Program.cs @@ -1,13 +1,223 @@ -namespace GrossToNet; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using GrossToNet.Models; + +namespace GrossToNet; class Program { - private static string InputFilePath => Path.Combine(AppContext.BaseDirectory, "input.json"); + private const string TestFile = "test-cases.json"; + static void Main(string[] args) + { + // Check for help argument + if (args.Any(arg => arg == "-h" || arg == "--help" || arg == "-?")) + { + DisplayHelp(); + Environment.Exit(0); + } + + Console.WriteLine("=".PadRight(70, '=')); + Console.WriteLine("GROSS-TO-NET PAYROLL CALCULATOR - TEST RUNNER"); + Console.WriteLine("=".PadRight(70, '=')); + Console.WriteLine(); + + // Check for custom file argument + string? customFileName = null; + for (int i = 0; i < args.Length; i++) + { + if ((args[i] == "-f" || args[i] == "--file") && i + 1 < args.Length) + { + customFileName = args[i + 1]; + break; + } + } + + string testFile; + bool useAdvancedTests = false; + + if (customFileName != null) + { + testFile = customFileName; + } + else + { + const string AdvancedTestCases = "advanced-" + TestFile; + var advancedFilePath = Path.Combine(AppContext.BaseDirectory, AdvancedTestCases); + useAdvancedTests = File.Exists(advancedFilePath); + testFile = useAdvancedTests ? AdvancedTestCases : TestFile; + } + + var filePath = Path.Combine(AppContext.BaseDirectory, testFile); + + if (useAdvancedTests) + { + Console.WriteLine("Running ADVANCED test cases (advanced test file detected)."); + } + else + { + Console.WriteLine("Running STANDARD test cases."); + } + + if (!File.Exists(filePath)) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"✗ Test cases file not found: {filePath}"); + Console.ResetColor(); + Environment.Exit(1); + } + + Console.WriteLine($"Loading " + testFile + $" from: {filePath}"); + var testCases = JsonSerializer.Deserialize>(File.ReadAllText(filePath), new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }); + + if (testCases == null || !testCases.Any()) + { + throw new Exception("Failed to load payroll data from input.json"); + } + + Console.WriteLine($"Loaded {testCases.Count()} test cases."); + + PayrollCalculator calculator = new PayrollCalculator(); + + foreach (var testCase in testCases) + { + try + { + testCase.ActualOutput = calculator.Calculate(testCase.Input.Earnings, testCase.Input.Deductions, testCase.Input.Taxes); + } + catch (NotImplementedException nex) + { + testCase.Error = nex; + testCase.Passed = false; + testCase.Message = "Payroll calculation not yet implemented."; + } + catch (Exception ex) + { + testCase.Error = ex; + testCase.Passed = false; + testCase.Message = $"Calculator threw an exception: {ex.Message}"; + } + + ValidateTestCase(testCase); + } + + var failedTests = testCases.Where(tc => tc.Passed == false).ToList(); + if (failedTests.Any()) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"✗ {failedTests.Count} out of {testCases.Count()} tests failed:"); + Console.ResetColor(); + Environment.Exit(1); + } + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"✓ All {testCases.Count()} test cases passed!"); + Console.ResetColor(); + Environment.Exit(0); + + } + + private static void ValidateTestCase(TestCase testCase) + { + var actual = testCase.ActualOutput!; + + if (testCase.Passed == false) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\t ✗ Test failed: {testCase.Description} --> {(testCase.Error != null ? $"{testCase.Error.GetType().Name} --> " : string.Empty)}{testCase.Message}"); + Console.ResetColor(); + return; + } + + if (testCase.ExpectedOutput == null) + { + testCase.Passed = false; + testCase.Message = "No expected output provided for test case."; + return; + } + var expected = testCase.ExpectedOutput!; + + if (actual.GrossPay == expected.GrossPay + && actual.GrossTaxableEarnings == expected.GrossTaxableEarnings + && actual.NetPay == expected.NetPay + && actual.Withholdings.Count == expected.Withholdings.Count) + { + testCase.Passed = true; + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"✓ Test passed: {testCase.Description}"); + Console.ResetColor(); + return; + } - static void Main(string[] args) + testCase.Passed = false; + + Console.ForegroundColor = ConsoleColor.Red; + + if (actual.GrossPay != expected.GrossPay) + { + Console.WriteLine($"\tGrossPay mismatch Actual: {actual.GrossPay} Expected: {expected.GrossPay}"); + } + if (actual.GrossTaxableEarnings != expected.GrossTaxableEarnings) + { + Console.WriteLine($"\tGrossTaxableEarnings mismatch Actual: {actual.GrossTaxableEarnings} Expected: {expected.GrossTaxableEarnings}"); + } + if (actual.NetPay != expected.NetPay) + { + Console.WriteLine($"\tNetPay mismatch Actual: {actual.NetPay} Expected: {expected.NetPay}"); + } + if (actual.Withholdings.Count != expected.Withholdings.Count) { - // Your code goes here! - - throw new NotImplementedException(); + Console.WriteLine($"\tWithholdings count mismatch Actual: {actual.Withholdings.Count} Expected: {expected.Withholdings.Count}"); } -} \ No newline at end of file + Console.ResetColor(); + } + + private static void DisplayHelp() + { + Console.WriteLine("GROSS-TO-NET PAYROLL CALCULATOR - TEST RUNNER"); + Console.WriteLine(); + Console.WriteLine("USAGE:"); + Console.WriteLine(" dotnet run [options]"); + Console.WriteLine(); + Console.WriteLine("OPTIONS:"); + Console.WriteLine(" -f, --file Specify a custom test file to run"); + Console.WriteLine(" -h, --help, -? Display this help information"); + Console.WriteLine(); + Console.WriteLine("BEHAVIOR:"); + Console.WriteLine(" - If -f/--file is specified, runs the specified test file"); + Console.WriteLine(" - Otherwise, checks for 'advanced-test-cases.json' and runs it if found"); + Console.WriteLine(" - If no advanced file exists, runs 'test-cases.json' by default"); + Console.WriteLine(); + Console.WriteLine("EXAMPLES:"); + Console.WriteLine(" dotnet run"); + Console.WriteLine(" dotnet run -- -f custom-tests.json"); + Console.WriteLine(" dotnet run -- --file advanced-test-cases.json"); + } +} + +public record PayrollData(IEnumerable Earnings, IEnumerable Deductions, IEnumerable Taxes); + +// Helper classes for test case deserialization +public class TestCase +{ + [JsonPropertyName("input")] + public required PayrollData Input { get; set; } + + [JsonPropertyName("output")] + public PayrollResult? ExpectedOutput { get; set; } + + public string? Description { get; set; } + + [JsonIgnore] + public bool? Passed { get; set; } + [JsonIgnore] + public string? Message { get; set; } + [JsonIgnore] + public Exception? Error { get; set; } + [JsonIgnore] + public PayrollResult? ActualOutput { get; set; } +} + diff --git a/gross-to-net/dotnet/models/Deduction.cs b/gross-to-net/dotnet/models/Deduction.cs new file mode 100644 index 0000000..5641316 --- /dev/null +++ b/gross-to-net/dotnet/models/Deduction.cs @@ -0,0 +1,35 @@ +namespace GrossToNet.Models; + +/// +/// Represents a deduction from income (e.g., 401k, health insurance). +/// Deductions can be percentage-based or flat amounts, and can be pre-tax or post-tax. +/// +public class Deduction +{ + /// + /// Identifier for the deduction type (e.g., "401k", "healthInsurance") + /// + public required string Code { get; set; } + + /// + /// The deduction calculation type: "percentage" or "flat" + /// + public required string Type { get; set; } + + /// + /// Processing priority; lower values are processed first. + /// Determines order of deduction application. + /// + public required int Priority { get; set; } + + /// + /// The deduction value: percentage (0-100) or flat amount + /// + public required decimal Value { get; set; } + + /// + /// Whether this deduction is applied before taxes (pre-tax) or after (post-tax) + /// + public required bool IsPreTax { get; set; } + +} diff --git a/gross-to-net/dotnet/models/Earning.cs b/gross-to-net/dotnet/models/Earning.cs new file mode 100644 index 0000000..a646c2d --- /dev/null +++ b/gross-to-net/dotnet/models/Earning.cs @@ -0,0 +1,38 @@ +namespace GrossToNet.Models; + +/// +/// Represents an earning item that contributes to gross income. +/// Can be hourly-based (calculated from hours and rate) or salary-based (flat amount). +/// +public class Earning +{ + /// + /// Identifier for the earning type (e.g., "overtime", "regular", "bonus") + /// + public required string Code { get; set; } + + /// + /// The earning calculation type: "hourly" or "salary" + /// + public required string Type { get; set; } + + /// + /// Number of hours worked (only used when Type is "hourly") + /// + public decimal? Hours { get; set; } + + /// + /// Hourly rate (only used when Type is "hourly") + /// + public decimal? Rate { get; set; } + + /// + /// Fixed salary amount (only used when Type is "salary") + /// + public decimal? Amount { get; set; } + + /// + /// Whether this earning is subject to taxation + /// + public required bool IsTaxable { get; set; } +} diff --git a/gross-to-net/dotnet/models/PayrollResult.cs b/gross-to-net/dotnet/models/PayrollResult.cs new file mode 100644 index 0000000..4ebe6e6 --- /dev/null +++ b/gross-to-net/dotnet/models/PayrollResult.cs @@ -0,0 +1,27 @@ +namespace GrossToNet.Models; + +/// +/// Represents the result of a payroll calculation +/// +public class PayrollResult +{ + /// + /// Total gross pay (sum of all earnings) + /// + public decimal GrossPay { get; set; } + + /// + /// Total gross taxable earnings (sum of earnings marked as taxable) + /// + public decimal GrossTaxableEarnings { get; set; } + + /// + /// List of all withholdings (deductions and taxes) applied in order + /// + public List Withholdings { get; set; } = new(); + + /// + /// Net pay after all withholdings + /// + public decimal NetPay { get; set; } +} diff --git a/gross-to-net/dotnet/models/Tax.cs b/gross-to-net/dotnet/models/Tax.cs new file mode 100644 index 0000000..f559b73 --- /dev/null +++ b/gross-to-net/dotnet/models/Tax.cs @@ -0,0 +1,34 @@ +namespace GrossToNet.Models; + +/// +/// Represents a tax calculation applied to income. +/// Can be a simple percentage or a capped percentage (with a maximum tax amount). +/// +public class Tax +{ + /// + /// Identifier for the tax type (e.g., "federalIncome", "fica") + /// + public required string Code { get; set; } + + /// + /// The tax calculation type: "percentage" or "cappedPercentage" + /// + public required string Type { get; set; } + + /// + /// Processing priority; lower values are processed first. + /// Determines order of tax calculation. + /// + public required int Priority { get; set; } + + /// + /// The tax rate as a percentage (0-100) + /// + public required decimal Value { get; set; } + + /// + /// Maximum tax amount (only used when Type is "cappedPercentage") + /// + public decimal? Cap { get; set; } +} diff --git a/gross-to-net/dotnet/models/Withholding.cs b/gross-to-net/dotnet/models/Withholding.cs new file mode 100644 index 0000000..86ab6e2 --- /dev/null +++ b/gross-to-net/dotnet/models/Withholding.cs @@ -0,0 +1,27 @@ +namespace GrossToNet.Models; + +/// +/// Represents a single withholding entry (tax or deduction) that was applied +/// +public class Withholding +{ + /// + /// Type of withholding: "deduction" or "tax" + /// + public required string Type { get; set; } + + /// + /// Code identifying the specific deduction or tax + /// + public required string Code { get; set; } + + /// + /// Amount actually withheld (may be less than requested if insufficient funds) + /// + public decimal AmountWithheld { get; set; } + + /// + /// Amount that could not be withheld due to insufficient remaining pay + /// + public decimal Deficit { get; set; } +} diff --git a/gross-to-net/dotnet/test-cases.json b/gross-to-net/dotnet/test-cases.json new file mode 100644 index 0000000..7fb3eaa --- /dev/null +++ b/gross-to-net/dotnet/test-cases.json @@ -0,0 +1,319 @@ +[ + { + "description": "Test 1: Simple hourly worker with no deductions or taxes", + "input": { + "earnings": [ + { + "code": "regular", + "type": "hourly", + "hours": 40, + "rate": 25.00, + "isTaxable": true + } + ], + "deductions": [], + "taxes": [] + }, + "output": { + "grossPay": 1000.00, + "grossTaxableEarnings": 1000.00, + "withholdings": [], + "netPay": 1000.00 + } + }, + { + "description": "Test 2: Simple salary worker with no deductions or taxes", + "input": { + "earnings": [ + { + "code": "salary", + "type": "salary", + "amount": 5000.00, + "isTaxable": true + } + ], + "deductions": [], + "taxes": [] + }, + "output": { + "grossPay": 5000.00, + "grossTaxableEarnings": 5000.00, + "withholdings": [], + "netPay": 5000.00 + } + }, + { + "description": "Test 3: Mixed taxable and non-taxable hourly earnings", + "input": { + "earnings": [ + { + "code": "regular", + "type": "hourly", + "hours": 40, + "rate": 20.00, + "isTaxable": true + }, + { + "code": "reimbursement", + "type": "hourly", + "hours": 10, + "rate": 15.00, + "isTaxable": false + } + ], + "deductions": [], + "taxes": [] + }, + "output": { + "grossPay": 950.00, + "grossTaxableEarnings": 800.00, + "withholdings": [], + "netPay": 950.00 + } + }, + { + "description": "Test 4: Mixed taxable and non-taxable salary earnings", + "input": { + "earnings": [ + { + "code": "salary", + "type": "salary", + "amount": 3000.00, + "isTaxable": true + }, + { + "code": "expense_reimbursement", + "type": "salary", + "amount": 500.00, + "isTaxable": false + } + ], + "deductions": [], + "taxes": [] + }, + "output": { + "grossPay": 3500.00, + "grossTaxableEarnings": 3000.00, + "withholdings": [], + "netPay": 3500.00 + } + }, + { + "description": "Test 5: Hourly worker with flat pre-tax deduction", + "input": { + "earnings": [ + { + "code": "regular", + "type": "hourly", + "hours": 40, + "rate": 25.00, + "isTaxable": true + } + ], + "deductions": [ + { + "code": "healthInsurance", + "type": "flat", + "priority": 1, + "value": 150.00, + "isPreTax": true + } + ], + "taxes": [] + }, + "output": { + "grossPay": 1000.00, + "grossTaxableEarnings": 1000.00, + "withholdings": [ + { + "type": "deduction", + "code": "healthInsurance", + "amountWithheld": 150.00, + "deficit": 0.00 + } + ], + "netPay": 850.00 + } + }, + { + "description": "Test 6: Salary worker with percentage pre-tax deduction", + "input": { + "earnings": [ + { + "code": "salary", + "type": "salary", + "amount": 5000.00, + "isTaxable": true + } + ], + "deductions": [ + { + "code": "401k", + "type": "percentage", + "priority": 1, + "value": 10, + "isPreTax": true + } + ], + "taxes": [] + }, + "output": { + "grossPay": 5000.00, + "grossTaxableEarnings": 5000.00, + "withholdings": [ + { + "type": "deduction", + "code": "401k", + "amountWithheld": 500.00, + "deficit": 0.00 + } + ], + "netPay": 4500.00 + } + }, + { + "description": "Test 7: Hourly worker with flat post-tax deduction", + "input": { + "earnings": [ + { + "code": "regular", + "type": "hourly", + "hours": 40, + "rate": 25.00, + "isTaxable": true + } + ], + "deductions": [ + { + "code": "garnishment", + "type": "flat", + "priority": 1, + "value": 200.00, + "isPreTax": false + } + ], + "taxes": [] + }, + "output": { + "grossPay": 1000.00, + "grossTaxableEarnings": 1000.00, + "withholdings": [ + { + "type": "deduction", + "code": "garnishment", + "amountWithheld": 200.00, + "deficit": 0.00 + } + ], + "netPay": 800.00 + } + }, + { + "description": "Test 8: Salary worker with percentage post-tax deduction", + "input": { + "earnings": [ + { + "code": "salary", + "type": "salary", + "amount": 4000.00, + "isTaxable": true + } + ], + "deductions": [ + { + "code": "charitableDonation", + "type": "percentage", + "priority": 1, + "value": 5, + "isPreTax": false + } + ], + "taxes": [] + }, + "output": { + "grossPay": 4000.00, + "grossTaxableEarnings": 4000.00, + "withholdings": [ + { + "type": "deduction", + "code": "charitableDonation", + "amountWithheld": 200.00, + "deficit": 0.00 + } + ], + "netPay": 3800.00 + } + }, + { + "description": "Test 9: Hourly worker with percentage tax (basic income tax)", + "input": { + "earnings": [ + { + "code": "regular", + "type": "hourly", + "hours": 40, + "rate": 30.00, + "isTaxable": true + } + ], + "deductions": [], + "taxes": [ + { + "code": "federalIncome", + "type": "percentage", + "priority": 1, + "value": 15 + } + ] + }, + "output": { + "grossPay": 1200.00, + "grossTaxableEarnings": 1200.00, + "withholdings": [ + { + "type": "tax", + "code": "federalIncome", + "amountWithheld": 180.00, + "deficit": 0.00 + } + ], + "netPay": 1020.00 + } + }, + { + "description": "Test 10: Salary worker with capped percentage tax", + "input": { + "earnings": [ + { + "code": "salary", + "type": "salary", + "amount": 5000.00, + "isTaxable": true + } + ], + "deductions": [], + "taxes": [ + { + "code": "socialSecurity", + "type": "cappedPercentage", + "priority": 1, + "value": 6.2, + "cap": 250.00 + } + ] + }, + "output": { + "grossPay": 5000.00, + "grossTaxableEarnings": 5000.00, + "withholdings": [ + { + "type": "tax", + "code": "socialSecurity", + "amountWithheld": 250.00, + "deficit": 0.00 + } + ], + "netPay": 4750.00 + } + } +] \ No newline at end of file diff --git a/gross-to-net/input.json b/gross-to-net/input.json deleted file mode 100644 index 71eaa7e..0000000 --- a/gross-to-net/input.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "earnings": [ - { - "code": "overtime", - "type": "hourly", - "hours": 20, - "rate": 15, - "isTaxable": true - }, - { - "code": "regular", - "type": "hourly", - "hours": 20, - "rate": 13, - "isTaxable": false - }, - { - "code": "bonus", - "type": "salary", - "amount": 1500, - "isTaxable": true - } - ], - "deductions": [ - { - "code": "401k", - "type": "percentage", - "priority": 1, - "value": 10, - "isPreTax": false - }, - { - "code": "healthInsurance", - "type": "flat", - "priority": 2, - "value": 20, - "isPreTax": true - } - ], - "taxes": [ - { - "code": "federalIncome", - "type": "percentage", - "priority": 1, - "value": 10 - }, - { - "code": "fica", - "type": "cappedPercentage", - "priority": 2, - "value": 10, - "cap": 50 - } - ] -} \ No newline at end of file diff --git a/gross-to-net/test-cases.json b/gross-to-net/test-cases.json deleted file mode 100644 index 4e04e20..0000000 --- a/gross-to-net/test-cases.json +++ /dev/null @@ -1,166 +0,0 @@ -[ - { - "input": { - "earnings": [ - { - "code": "overtime", - "type": "hourly", - "hours": 20, - "rate": 15, - "isTaxable": true - }, - { - "code": "regular", - "type": "hourly", - "hours": 20, - "rate": 13, - "isTaxable": false - }, - { - "code": "bonus", - "type": "salary", - "amount": 1500, - "isTaxable": true - } - ], - "deductions": [ - { - "code": "401k", - "type": "percentage", - "priority": 1, - "value": 10, - "isPreTax": false - }, - { - "code": "healthInsurance", - "type": "flat", - "priority": 2, - "value": 20, - "isPreTax": true - } - ], - "taxes": [ - { - "code": "federalIncome", - "type": "percentage", - "priority": 1, - "value": 10 - }, - { - "code": "fica", - "type": "cappedPercentage", - "priority": 2, - "value": 10, - "cap": 50 - } - ] - }, - "output": { - "GrossPay": 2060, - "GrossTaxableEarnings": 1800, - "Withholdings": [ - { - "Type": "deduction", - "Code": "healthInsurance", - "AmountWithheld": 20, - "Deficit": 0 - }, - { - "Type": "tax", - "Code": "fica", - "AmountWithheld": 50, - "Deficit": 0 - }, - { - "Type": "tax", - "Code": "federalIncome", - "AmountWithheld": 178, - "Deficit": 0 - }, - { - "Type": "deduction", - "Code": "401k", - "AmountWithheld": 155.2, - "Deficit": 0.0 - } - ], - "NetPay": 1656.8 - } - }, - { - "input": { - "earnings": [ - { - "code": "overtime", - "type": "hourly", - "hours": 5, - "rate": 12, - "isTaxable": true - }, - { - "code": "bonus", - "type": "salary", - "amount": 115, - "isTaxable": false - }, - { - "code": "regular", - "type": "hourly", - "hours": 10, - "rate": 13, - "isTaxable": true - } - ], - "deductions": [ - { - "code": "healthInsurance", - "type": "percentage", - "priority": 1, - "value": 20, - "isPreTax": true - }, - { - "code": "uniform", - "type": "flat", - "priority": 1, - "value": 400, - "isPreTax": false - } - ], - "taxes": [ - { - "code": "federalIncome", - "type": "cappedPercentage", - "priority": 1, - "value": 7.5, - "cap": 250 - } - ] - }, - "output": { - "GrossPay": 305, - "GrossTaxableEarnings": 190, - "Withholdings": [ - { - "Type": "deduction", - "Code": "healthInsurance", - "AmountWithheld": 38, - "Deficit": 0 - }, - { - "Type": "tax", - "Code": "federalIncome", - "AmountWithheld": 11.4, - "Deficit": 0.0 - }, - { - "Type": "deduction", - "Code": "uniform", - "AmountWithheld": 140.6, - "Deficit": 259.4 - } - ], - "NetPay": 115.0 - } - } -] \ No newline at end of file