Anlage 1. Programmcode zur Tarifberechnung
Die interaktiven Darstellungen in dieser Arbeit basieren direkt auf den Tarifformeln des § 32a Abs. 1 EStG. Diese Formeln wurden in einem JavaScript-Modul implementiert, das zusätzlich auch den Code zur Berechnung des relativen Verlusts aus der Kalten Progression sowie zur Tarifanpassung enthält. Die Steuerberechnung wurde mit dem BMF-Steuerrechner überprüft.
Die TaxScale
-Klasse ist in dieser Seite eingebunden und kann in der Browser-Konsole in den Dev Tools bspw. mit new TaxScale(2019)
ausprobiert werden.
/** * TaxScale * Calculates German ESt * * @author Lukas Bestle <project-bachelor@lukasbestle.com> * @copyright 2019 Lukas Bestle * @license MIT <https://opensource.org/licenses/MIT> * * @param {number|Array} data Tax year or custom data array */ export default function TaxScale(data) { if (typeof data !== 'object') { this.year = data; data = TaxScale.getDataForYear(data); } else { this.year = null; } // make a local copy of the data this.data = data.slice(0); }; /** * Returns the available years with * their ID and label * * @return {Object} */ TaxScale.years = function() { var years = {}, lastYear = null; // build an object of `from: to` pairs... Object.keys(data).forEach(function(year) { if (lastYear !== null && year - 1 > lastYear) { // at least one year was skipped, update the last year years[lastYear] = year - 1; } years[year] = lastYear = year; }); // ...and convert that to `id: label` pairs for (var yearFrom in years) { var yearTo = years[yearFrom]; if (yearFrom === yearTo) { years[yearFrom] = yearFrom; } else { years[yearFrom] = yearFrom + '–' + yearTo; } } return years; }; /** * Returns the data for a given tax year * * @param {number} year Tax year * @return {Array} */ TaxScale.getDataForYear = function(year) { // check variable type if (typeof year !== 'number') { throw new Error('Year must be a number.'); } // check if we are out of bounds var dataYears = Object.keys(data); var validityMin = dataYears[0]; // year that is defined first var validityMax = dataYears.slice(-1)[0]; // year that is defined last if (year < validityMin || year > validityMax) { throw new Error('The given year ' + year + ' is not implemented.'); } // find a matching year from the data object; // if the year does not exist, go back in time // (data of older year is still valid in this case) var vars; while (!vars) { vars = data[year]; year--; } return vars; }; /** * Returns a scale variable by name * * @param {string} variable Variable name * @return {number} Variable value for the current year */ TaxScale.prototype.getVariable = function(variable) { var key = variables.indexOf(variable); if (key < 0) { throw new Error('Variable type ' + variable + ' does not exist.'); } return this.data[key]; }; /** * Overwrites a specified scale variable * * @param {string} variable Variable name * @param {number} value New value */ TaxScale.prototype.setVariable = function(variable, value) { var key = variables.indexOf(variable); if (key < 0) { throw new Error('Variable type ' + variable + ' does not exist.'); } this.data[key] = value; // the scale is no longer the one of any year this.year = null; }; /** * Raises the exemption of the current scale by a * specified amount * * @param {number} percentage Percentage to raise exemption by * @return {TaxScale} Instance */ TaxScale.prototype.raiseExemption = function(percentage) { return this.raiseVariables('exemption', percentage); }; /** * Stretches the current scale by a specified amount * * @param {number} percentage Percentage to stretch scale by * @return {TaxScale} Instance */ TaxScale.prototype.stretch = function(percentage) { return this.raiseVariables([ 'exemption', 'threshold1', 'threshold2', 'threshold3' ], percentage); }; /** * Raises the specified threshold variable(s) by a specified amount and * recalculates the scale data; * can be called multiple times with different variables and percentages * * @param {Array|string} variables Scale variable or array of multiple variables * @param {number} percentage Percentage to raise variable(s) by * @return {TaxScale} Instance */ TaxScale.prototype.raiseVariables = function(variables, percentage) { // support for strings as argument if (typeof variables === 'string') { variables = [variables]; } // raise the specified variables for (var variable in variables) { variable = variables[variable]; this.setVariable(variable, Math.round(this.getVariable(variable) * (1 + percentage))); } // recalculate data once after everything has been raised recalculateData(this); return this; }; /** * Returns the ESt for a given zvE * * @param {number} zvE "Zu versteuerndes Einkommen" * @return {Object} Object with zvE, amount, amountExact, average and marginal */ TaxScale.prototype.calculate = function(zvE) { // § 32a Abs. 1 S. 3–5 EStG var x = Math.floor(zvE); var y = (x - this.getVariable('exemption')) / 10000; var z = (x - this.getVariable('threshold1')) / 10000; // § 32a Abs. 1 S. 2 EStG // marginal rates based on https://www.fbmn.h-da.de/~pfeifer/Einkommensteuertarif.pdf; // marginal rate for threshold zvEs is the one on the left of the threshold (like in the BMF Steuerrechner) var amount, marginal; if (x <= this.getVariable('exemption')) { amount = 0; marginal = 0; } else if (x <= this.getVariable('threshold1')) { amount = (this.getVariable('factor1') * y + this.getVariable('rate1')) * y; marginal = (2 * this.getVariable('factor1') * y + this.getVariable('rate1')) / 10000; } else if (x <= this.getVariable('threshold2')) { amount = (this.getVariable('factor2') * z + this.getVariable('rate2')) * z + this.getVariable('constant2'); marginal = (2 * this.getVariable('factor2') * z + this.getVariable('rate2')) / 10000; } else if (x <= this.getVariable('threshold3')) { amount = this.getVariable('rate3') * x - this.getVariable('constant3'); marginal = this.getVariable('rate3'); } else { amount = this.getVariable('rate4') * x - this.getVariable('constant4'); marginal = this.getVariable('rate4'); } // § 32a Abs. 1 S. 6 EStG var amountRounded = Math.floor(amount); return { zvE: x, amount: amountRounded, amountExact: amount, average: (x > 0)? amountRounded / x : 0, marginal: marginal }; }; /** * Calculates the loss from bracket creep for a given zvE * relative to the adjusted zvE * * @param {number} zvEOld "Zu versteuerndes Einkommen" * @param {number} inflation Inflation rate * @param {TaxScale} comparisonTaxScale Base scale to compare against (current one if undefined) * @return {number} Relative loss percentage */ TaxScale.prototype.calculateRelativeLoss = function(zvEOld, inflation, comparisonTaxScale) { if (comparisonTaxScale === undefined) { comparisonTaxScale = this; } // adjust zvE to inflation var zvENew = zvEOld * (1 + inflation); // calculate actual and inflation-adjusted tax amounts var taxOld = comparisonTaxScale.calculate(zvEOld).amount; var taxNewI = taxOld * (1 + inflation); var taxNewT = this.calculate(zvENew).amount; // calculate absolute loss var lossAbsolute = taxNewT - taxNewI; // calculate relative loss; // avoid NaN for zvENew === 0 return (lossAbsolute / zvENew) || 0; }; /** * Recalculates the scale data based on new thresholds or marginal rates * Can be called via: * taxScale.raiseExemption() * taxScale.stretch() * taxScale.raiseVariables() * * @param {TaxScale} taxScale TaxScale instance to recalculate */ function recalculateData(taxScale) { // progressive zone 1 var factor1 = 5000 * (taxScale.getVariable('rate2') - taxScale.getVariable('rate1')) / (taxScale.getVariable('threshold1') - taxScale.getVariable('exemption')); factor1 = Math.round(factor1 * 100) / 100; taxScale.setVariable('factor1', factor1); // progressive zone 2 var y = (taxScale.getVariable('threshold1') - taxScale.getVariable('exemption')) / 10000; var constant2 = (taxScale.getVariable('factor1') * y + taxScale.getVariable('rate1')) * y; constant2 = Math.round(constant2 * 100) / 100; taxScale.setVariable('constant2', constant2); var factor2 = 5000 * (taxScale.getVariable('rate3') * 10000 - taxScale.getVariable('rate2')) / (taxScale.getVariable('threshold2') - taxScale.getVariable('threshold1')); factor2 = Math.round(factor2 * 100) / 100; taxScale.setVariable('factor2', factor2); // proportional zone 1 var taxBelow3 = taxScale.calculate(taxScale.getVariable('threshold2')).amountExact; var constant3 = taxScale.getVariable('rate3') * taxScale.getVariable('threshold2') - taxBelow3; constant3 = Math.round(constant3 * 100) / 100; taxScale.setVariable('constant3', constant3); // proportional zone 2 var taxBelow4 = taxScale.calculate(taxScale.getVariable('threshold3')).amountExact; var constant4 = taxScale.getVariable('rate4') * taxScale.getVariable('threshold3') - taxBelow4; constant4 = Math.round(constant4 * 100) / 100; taxScale.setVariable('constant4', constant4); } // names for the data variables and // their order in the data array var variables = [ 'exemption', 'threshold1', 'factor1', 'rate1', 'threshold2', 'factor2', 'rate2', 'constant2', 'threshold3', 'rate3', 'constant3', 'rate4', 'constant4', ]; /** * Implementation data for each year from § 32a Abs. 1 S. 2 EStG: * * ---- * * Sie beträgt im Veranlagungszeitraum xxxx vorbehaltlich [...] * jeweils in Euro für zu versteuernde Einkommen * * 1. bis {exemption} Euro (Grundfreibetrag): * 0; * 2. von {exemption + 1} Euro bis {threshold1} Euro: * ({factor1} · y + {rate1}) · y; * 3. von {threshold1 + 1} Euro bis {threshold2} Euro: * ({factor2} · z + {rate2}) · z + {constant2}; * 4. von {threshold2 + 1} Euro bis {threshold3} Euro: * {rate3} · x – {constant3}; * 5. von {threshold3 + 1} Euro an: * {rate4} · x – {constant4}. * * --- * * If a year is not defined, the data from the year before * is still valid and therefore not duplicated! */ var data = { 2007: [ /*exemption*/ 7664.00, /*threshold1*/ 12739.00, /*factor1*/ 883.74, /*rate1*/ 1500.00, /*threshold2*/ 52151.00, /*factor2*/ 228.74, /*rate2*/ 2397.00, /*constant2*/ 989.00, /*threshold3*/ 250000.00, /*rate3*/ 0.42, /*constant3*/ 7914.00, /*rate4*/ 0.45, /*constant4*/ 15414.00 ], 2009: [ /*exemption*/ 7834.00, /*threshold1*/ 13139.00, /*factor1*/ 939.68, /*rate1*/ 1400.00, /*threshold2*/ 52551.00, /*factor2*/ 228.74, /*rate2*/ 2397.00, /*constant2*/ 1007.00, /*threshold3*/ 250400.00, /*rate3*/ 0.42, /*constant3*/ 8064.00, /*rate4*/ 0.45, /*constant4*/ 15576.00 ], 2010: [ /*exemption*/ 8004.00, /*threshold1*/ 13469.00, /*factor1*/ 912.17, /*rate1*/ 1400.00, /*threshold2*/ 52881.00, /*factor2*/ 228.74, /*rate2*/ 2397.00, /*constant2*/ 1038.00, /*threshold3*/ 250730.00, /*rate3*/ 0.42, /*constant3*/ 8172.00, /*rate4*/ 0.45, /*constant4*/ 15694.00 ], 2013: [ /*exemption*/ 8130.00, /*threshold1*/ 13469.00, /*factor1*/ 933.70, /*rate1*/ 1400.00, /*threshold2*/ 52881.00, /*factor2*/ 228.74, /*rate2*/ 2397.00, /*constant2*/ 1014.00, /*threshold3*/ 250730.00, /*rate3*/ 0.42, /*constant3*/ 8196.00, /*rate4*/ 0.45, /*constant4*/ 15718.00 ], 2014: [ /*exemption*/ 8354.00, /*threshold1*/ 13469.00, /*factor1*/ 974.58, /*rate1*/ 1400.00, /*threshold2*/ 52881.00, /*factor2*/ 228.74, /*rate2*/ 2397.00, /*constant2*/ 971.00, /*threshold3*/ 250730.00, /*rate3*/ 0.42, /*constant3*/ 8239.00, /*rate4*/ 0.45, /*constant4*/ 15761.00 ], 2015: [ /*exemption*/ 8472.00, /*threshold1*/ 13469.00, /*factor1*/ 997.60, /*rate1*/ 1400.00, /*threshold2*/ 52881.00, /*factor2*/ 228.74, /*rate2*/ 2397.00, /*constant2*/ 948.68, /*threshold3*/ 250730.00, /*rate3*/ 0.42, /*constant3*/ 8261.29, /*rate4*/ 0.45, /*constant4*/ 15783.19 ], 2016: [ /*exemption*/ 8652.00, /*threshold1*/ 13669.00, /*factor1*/ 993.62, /*rate1*/ 1400.00, /*threshold2*/ 53665.00, /*factor2*/ 225.40, /*rate2*/ 2397.00, /*constant2*/ 952.48, /*threshold3*/ 254446.00, /*rate3*/ 0.42, /*constant3*/ 8394.14, /*rate4*/ 0.45, /*constant4*/ 16027.52 ], 2017: [ /*exemption*/ 8820.00, /*threshold1*/ 13769.00, /*factor1*/ 1007.27, /*rate1*/ 1400.00, /*threshold2*/ 54057.00, /*factor2*/ 223.76, /*rate2*/ 2397.00, /*constant2*/ 939.57, /*threshold3*/ 256303.00, /*rate3*/ 0.42, /*constant3*/ 8475.44, /*rate4*/ 0.45, /*constant4*/ 16164.53 ], 2018: [ /*exemption*/ 9000.00, /*threshold1*/ 13996.00, /*factor1*/ 997.80, /*rate1*/ 1400.00, /*threshold2*/ 54949.00, /*factor2*/ 220.13, /*rate2*/ 2397.00, /*constant2*/ 948.49, /*threshold3*/ 260532.00, /*rate3*/ 0.42, /*constant3*/ 8621.75, /*rate4*/ 0.45, /*constant4*/ 16437.70 ], 2019: [ /*exemption*/ 9168.00, /*threshold1*/ 14254.00, /*factor1*/ 980.14, /*rate1*/ 1400.00, /*threshold2*/ 55960.00, /*factor2*/ 216.16, /*rate2*/ 2397.00, /*constant2*/ 965.58, /*threshold3*/ 265326.00, /*rate3*/ 0.42, /*constant3*/ 8780.90, /*rate4*/ 0.45, /*constant4*/ 16740.68 ], 2020: [ /*exemption*/ 9408.00, /*threshold1*/ 14532.00, /*factor1*/ 972.87, /*rate1*/ 1400.00, /*threshold2*/ 57051.00, /*factor2*/ 212.02, /*rate2*/ 2397.00, /*constant2*/ 972.79, /*threshold3*/ 270500.00, /*rate3*/ 0.42, /*constant3*/ 8963.74, /*rate4*/ 0.45, /*constant4*/ 17078.74 ] };