From fa57f71cfaa09e4616a6a13e6ddfbe913590d87e Mon Sep 17 00:00:00 2001 From: Jared Kipe Date: Wed, 8 Jan 2020 08:42:58 -0800 Subject: [PATCH] IMP `l10n_us_hr_payroll` Port `l10n_us_mt_hr_payroll` MT Montana including migration. --- l10n_us_hr_payroll/__manifest__.py | 1 + l10n_us_hr_payroll/data/final.xml | 3 + l10n_us_hr_payroll/data/state/mt_montana.xml | 166 ++++++++++++++++++ .../12.0.2020.1.0/post-migration.py | 17 +- l10n_us_hr_payroll/migrations/data.py | 20 +++ l10n_us_hr_payroll/migrations/helper.py | 7 +- l10n_us_hr_payroll/models/hr_payslip.py | 2 + l10n_us_hr_payroll/models/state/general.py | 2 +- l10n_us_hr_payroll/models/state/mt_montana.py | 41 +++++ .../models/us_payroll_config.py | 12 ++ l10n_us_hr_payroll/tests/__init__.py | 2 + .../tests/test_us_mt_montana_payslip_2019.py | 139 +++++++++++++++ .../tests/test_us_mt_montana_payslip_2020.py | 17 ++ .../views/us_payroll_config_views.xml | 6 + 14 files changed, 432 insertions(+), 3 deletions(-) create mode 100644 l10n_us_hr_payroll/data/state/mt_montana.xml create mode 100644 l10n_us_hr_payroll/models/state/mt_montana.py create mode 100755 l10n_us_hr_payroll/tests/test_us_mt_montana_payslip_2019.py create mode 100755 l10n_us_hr_payroll/tests/test_us_mt_montana_payslip_2020.py diff --git a/l10n_us_hr_payroll/__manifest__.py b/l10n_us_hr_payroll/__manifest__.py index fb47d741..060f2657 100755 --- a/l10n_us_hr_payroll/__manifest__.py +++ b/l10n_us_hr_payroll/__manifest__.py @@ -29,6 +29,7 @@ USA Payroll Rules. 'data/federal/fed_941_fit_parameters.xml', 'data/federal/fed_941_fit_rules.xml', 'data/state/fl_florida.xml', + 'data/state/mt_montana.xml', 'data/state/pa_pennsylvania.xml', 'data/final.xml', 'views/hr_contract_views.xml', diff --git a/l10n_us_hr_payroll/data/final.xml b/l10n_us_hr_payroll/data/final.xml index d266884a..d8756337 100644 --- a/l10n_us_hr_payroll/data/final.xml +++ b/l10n_us_hr_payroll/data/final.xml @@ -17,6 +17,9 @@ ref('hr_payroll_rule_ee_fed_941_fit'), ref('hr_payroll_rule_er_us_fl_suta'), + ref('hr_payroll_rule_er_us_mt_suta'), + ref('hr_payroll_rule_er_us_mt_suta_aft'), + ref('hr_payroll_rule_ee_us_mt_sit'), ref('hr_payroll_rule_er_us_pa_suta'), ref('hr_payroll_rule_ee_us_pa_suta'), ref('hr_payroll_rule_ee_us_pa_sit'), diff --git a/l10n_us_hr_payroll/data/state/mt_montana.xml b/l10n_us_hr_payroll/data/state/mt_montana.xml new file mode 100644 index 00000000..b757e11d --- /dev/null +++ b/l10n_us_hr_payroll/data/state/mt_montana.xml @@ -0,0 +1,166 @@ + + + + + + US MT Montana SUTA Wage Base + us_mt_suta_wage_base + 33000.00 + + + + US MT Montana SUTA Wage Base + us_mt_suta_wage_base + 34100.00 + + + + + + + + US MT Montana SUTA Rate (UI) + us_mt_suta_rate + 1.18 + + + + US MT Montana SUTA Rate (UI) + us_mt_suta_rate + 1.18 + + + + + + + US MT Montana SUTA Administrative Fund Tax Rate + us_mt_suta_aft_rate + 0.13 + + + + US MT Montana SUTA Administrative Fund Tax Rate + us_mt_suta_aft_rate + 0.13 + + + + + + + US MT Montana SIT Rate Table + us_mt_suta_sit_rate + { + 'weekly': [ + ( 135.00, 0.0, 1.80), + ( 288.00, 2.0, 4.40), + ( 2308.00, 9.0, 6.00), + ( 'inf', 130.0, 6.60), + ], + 'bi-weekly': [ + ( 269.00, 0.0, 1.80), + ( 577.00, 5.0, 4.40), + ( 4615.00, 18.0, 6.00), + ( 'inf', 261.0, 6.60), + ], + 'semi-monthly': [ + ( 292.00, 0.0, 1.80), + ( 625.00, 5.0, 4.40), + ( 5000.00, 20.0, 6.00), + ( 'inf', 282.0, 6.60), + ], + 'monthly': [ + ( 583.00, 0.0, 1.80), + ( 1250.00, 11.0, 4.40), + ( 10000.00, 40.0, 6.00), + ( 'inf', 565.0, 6.60), + ], + 'annually': [ + ( 7000.00, 0.0, 1.80), + ( 15000.00, 126.0, 4.40), + ( 120000.00, 478.0, 6.00), + ( 'inf', 6778.0, 6.60), + ], + } + + + + + + + US MT Montana SIT Exemption Rate Table + us_mt_suta_sit_exemption_rate + { + 'weekly': 37.0, + 'bi-weekly': 73.0, + 'semi-monthly': 79.0, + 'monthly': 158.0, + 'annually': 1900.0, + } + + + + + + + US Montana - Department of Labor & Industries + + + + US Montana - Department of Labor & Industries + + + + + US Montana - Department of Revenue - Income Tax + + + + US Montana - Department of Revenue - Income Tax + + + + + + + + + + ER: US MT Montana State Unemployment (UI-5) + ER_US_MT_SUTA + python + result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mt_suta_wage_base', rate='us_mt_suta_rate', state_code='MT') + code + result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mt_suta_wage_base', rate='us_mt_suta_rate', state_code='MT') + + + + + + + + ER: US MT Montana State Unemployment Administrative Fund Tax (AFT) (UI-5) + ER_US_MT_SUTA_AFT + python + result, _ = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mt_suta_wage_base', rate='us_mt_suta_aft_rate', state_code='MT') + code + result, result_rate = general_state_unemployment(payslip, categories, worked_days, inputs, wage_base='us_mt_suta_wage_base', rate='us_mt_suta_aft_rate', state_code='MT') + + + + + + + + EE: US MT Montana State Income Tax Withholding (MW-3) + EE_US_MT_SIT + python + result, _ = mt_montana_state_income_withholding(payslip, categories, worked_days, inputs) + code + result, result_rate = mt_montana_state_income_withholding(payslip, categories, worked_days, inputs) + + + + + \ No newline at end of file diff --git a/l10n_us_hr_payroll/migrations/12.0.2020.1.0/post-migration.py b/l10n_us_hr_payroll/migrations/12.0.2020.1.0/post-migration.py index 95984afb..3f4499ba 100644 --- a/l10n_us_hr_payroll/migrations/12.0.2020.1.0/post-migration.py +++ b/l10n_us_hr_payroll/migrations/12.0.2020.1.0/post-migration.py @@ -1,6 +1,7 @@ # Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. -from odoo.addons.l10n_us_hr_payroll.migrations.data import FIELDS_CONTRACT_TO_US_PAYROLL_FORMS_2020 +from odoo.addons.l10n_us_hr_payroll.migrations.data import FIELDS_CONTRACT_TO_US_PAYROLL_FORMS_2020, \ + XMLIDS_COPY_ACCOUNTING_2020 from odoo.addons.l10n_us_hr_payroll.migrations.helper import field_exists, \ temp_field_exists, \ remove_temp_field, \ @@ -68,3 +69,17 @@ def migrate(cr, installed_version): for field in fields_to_move: remove_temp_field(cr, 'hr_contract', field) + + # Some added rules should have the same accounting side effects of other migrated rules + # To ease the transition, we will copy the accounting fields from one to the other. + for source, destinations in XMLIDS_COPY_ACCOUNTING_2020.items(): + source_rule = env.ref(source, raise_if_not_found=False) + if source_rule: + for destination in destinations: + destination_rule = env.ref(destination, raise_if_not_found=False) + if destination_rule: + _logger.warn('Mirgrating accounting from rule: ' + source + ' to rule: ' + destination) + destination_rule.write({ + 'account_debit': source_rule.account_debit.id, + 'account_credit': source_rule.account_credit.id, + }) diff --git a/l10n_us_hr_payroll/migrations/data.py b/l10n_us_hr_payroll/migrations/data.py index 99534b57..8a1a475c 100644 --- a/l10n_us_hr_payroll/migrations/data.py +++ b/l10n_us_hr_payroll/migrations/data.py @@ -9,6 +9,9 @@ FIELDS_CONTRACT_TO_US_PAYROLL_FORMS_2020 = { 'fica_exempt': 'fed_941_fica_exempt', 'futa_type': 'fed_940_type', # State + 'mt_mw4_additional_withholding': 'state_income_tax_additional_withholding', + 'mt_mw4_exemptions': 'mt_mw4_sit_exemptions', + 'mt_mw4_exempt': 'mt_mw4_sit_exempt', 'pa_additional_withholding': 'state_income_tax_additional_withholding', } @@ -32,6 +35,10 @@ XMLIDS_TO_REMOVE_2020 = [ 'l10n_us_fl_hr_payroll.hr_payroll_fl_unemp_wages', 'l10n_us_fl_hr_payroll.hr_payroll_fl_unemp', 'l10n_us_fl_hr_payroll.hr_payroll_rules_fl_unemp_wages_2018', + 'l10n_us_mt_hr_payroll.hr_payroll_mt_unemp_wages', + 'l10n_us_mt_hr_payroll.hr_payroll_mt_unemp', + 'l10n_us_mt_hr_payroll.hr_payroll_mt_income_withhold', + 'l10n_us_mt_hr_payroll.hr_payroll_rules_mt_unemp_wages', 'l10n_us_pa_hr_payroll.res_partner_pador_unemp_employee', 'l10n_us_pa_hr_payroll.contrib_register_pador_unemp_employee', 'l10n_us_pa_hr_payroll.hr_payroll_pa_unemp_wages', @@ -59,6 +66,12 @@ XMLIDS_TO_RENAME_2020 = { 'l10n_us_fl_hr_payroll.hr_payroll_rules_fl_unemp_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_fl_suta', 'l10n_us_fl_hr_payroll.res_partner_fldor': 'l10n_us_hr_payroll.res_partner_us_fl_dor', 'l10n_us_fl_hr_payroll.contrib_register_fldor': 'l10n_us_hr_payroll.contrib_register_us_fl_dor', + 'l10n_us_mt_hr_payroll.res_partner_mtdor_unemp': 'l10n_us_hr_payroll.res_partner_us_mt_dor', + 'l10n_us_mt_hr_payroll.res_partner_mtdor_withhold': 'l10n_us_hr_payroll.res_partner_us_mt_dor_sit', + 'l10n_us_mt_hr_payroll.contrib_register_mtdor_unemp': 'l10n_us_hr_payroll.contrib_register_us_mt_dor', + 'l10n_us_mt_hr_payroll.contrib_register_mtdor_withhold': 'l10n_us_hr_payroll.contrib_register_us_mt_dor_sit', + 'l10n_us_mt_hr_payroll.hr_payroll_rules_mt_unemp': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_mt_suta', + 'l10n_us_mt_hr_payroll.hr_payroll_rules_mt_inc_withhold': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_mt_sit', 'l10n_us_pa_hr_payroll.res_partner_pador_unemp_company': 'l10n_us_hr_payroll.res_partner_us_pa_dor', 'l10n_us_pa_hr_payroll.res_partner_pador_withhold': 'l10n_us_hr_payroll.res_partner_us_pa_dor_sit', 'l10n_us_pa_hr_payroll.contrib_register_pador_unemp_company': 'l10n_us_hr_payroll.contrib_register_us_pa_dor', @@ -66,4 +79,11 @@ XMLIDS_TO_RENAME_2020 = { 'l10n_us_pa_hr_payroll.hr_payroll_rules_pa_unemp_employee_2018': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_pa_suta', 'l10n_us_pa_hr_payroll.hr_payroll_rules_pa_unemp_company_2018': 'l10n_us_hr_payroll.hr_payroll_rule_er_us_pa_suta', 'l10n_us_pa_hr_payroll.hr_payroll_rules_pa_inc_withhold_2018': 'l10n_us_hr_payroll.hr_payroll_rule_ee_us_pa_sit', + +} + +XMLIDS_COPY_ACCOUNTING_2020 = { + 'l10n_us_hr_payroll.hr_payroll_rule_er_us_mt_suta': [ + 'l10n_us_hr_payroll.hr_payroll_rule_er_us_mt_suta_aft', + ], } diff --git a/l10n_us_hr_payroll/migrations/helper.py b/l10n_us_hr_payroll/migrations/helper.py index b41cf304..f5a7a87d 100644 --- a/l10n_us_hr_payroll/migrations/helper.py +++ b/l10n_us_hr_payroll/migrations/helper.py @@ -39,7 +39,12 @@ def temp_field_values(cr, table_name, id, field_names): values = cr.dictfetchone() if not values: return {} - return {k.lstrip(TMP_PREFIX): v for k, v in values.items()} + + def _remove_tmp_prefix(key): + if key.startswith(TMP_PREFIX): + return key[len(TMP_PREFIX):] + return key + return {_remove_tmp_prefix(k): v for k, v in values.items()} """ diff --git a/l10n_us_hr_payroll/models/hr_payslip.py b/l10n_us_hr_payroll/models/hr_payslip.py index 90476930..9104c69e 100644 --- a/l10n_us_hr_payroll/models/hr_payslip.py +++ b/l10n_us_hr_payroll/models/hr_payslip.py @@ -11,6 +11,7 @@ from .federal.fed_941 import ee_us_941_fica_ss, \ ee_us_941_fit from .state.general import general_state_unemployment, \ general_state_income_withholding +from .state.mt_montana import mt_montana_state_income_withholding class HRPayslip(models.Model): @@ -42,6 +43,7 @@ class HRPayslip(models.Model): 'ee_us_941_fit': ee_us_941_fit, 'general_state_unemployment': general_state_unemployment, 'general_state_income_withholding': general_state_income_withholding, + 'mt_montana_state_income_withholding': mt_montana_state_income_withholding, } def get_year(self): diff --git a/l10n_us_hr_payroll/models/state/general.py b/l10n_us_hr_payroll/models/state/general.py index fd259f0e..0d11b054 100644 --- a/l10n_us_hr_payroll/models/state/general.py +++ b/l10n_us_hr_payroll/models/state/general.py @@ -96,7 +96,7 @@ def general_state_unemployment(payslip, categories, worked_days, inputs, wage_ba def general_state_income_withholding(payslip, categories, worked_days, inputs, wage_base=None, wage_start=None, rate=None, state_code=None): """ - Returns SUTA eligible wage and rate. + Returns SIT eligible wage and rate. WAGE = GROSS - WAGE_US_941_FIT_EXEMPT :return: result, result_rate (wage, percent) diff --git a/l10n_us_hr_payroll/models/state/mt_montana.py b/l10n_us_hr_payroll/models/state/mt_montana.py new file mode 100644 index 00000000..fd363301 --- /dev/null +++ b/l10n_us_hr_payroll/models/state/mt_montana.py @@ -0,0 +1,41 @@ +from .general import _state_applies + + +def mt_montana_state_income_withholding(payslip, categories, worked_days, inputs): + #, wage_base = None, wage_start = None, rate = None, state_code = None + """ + Returns SIT eligible wage and rate. + WAGE = GROSS - WAGE_US_941_FIT_EXEMPT + + :return: result, result_rate (wage, percent) + """ + state_code = 'MT' + if not _state_applies(payslip, state_code): + return 0.0, 0.0 + + if payslip.dict.contract_id.us_payroll_config_value('mt_mw4_sit_exempt'): + return 0.0, 0.0 + + # Determine Wage + wage = categories.GROSS - categories.WAGE_US_941_FIT_EXEMPT + schedule_pay = payslip.dict.contract_id.schedule_pay + additional = payslip.dict.contract_id.us_payroll_config_value('state_income_tax_additional_withholding') + exemptions = payslip.dict.contract_id.us_payroll_config_value('mt_mw4_sit_exemptions') + exemption_rate = payslip.dict.rule_parameter('us_mt_suta_sit_exemption_rate').get(schedule_pay) + withholding_rate = payslip.dict.rule_parameter('us_mt_suta_sit_rate').get(schedule_pay) + if not exemption_rate or not withholding_rate or wage == 0.0: + return 0.0, 0.0 + + adjusted_wage = wage - (exemption_rate * (exemptions or 0)) + withholding = 0.0 + if adjusted_wage > 0.0: + prior_wage_cap = 0.0 + for row in withholding_rate: + wage_cap, base, rate = row + wage_cap = float(wage_cap) # e.g. 'inf' + if adjusted_wage < wage_cap: + withholding = round(base + ((rate / 100.0) * (adjusted_wage - prior_wage_cap))) + break + prior_wage_cap = wage_cap + withholding += additional + return wage, -((withholding / wage) * 100.0) diff --git a/l10n_us_hr_payroll/models/us_payroll_config.py b/l10n_us_hr_payroll/models/us_payroll_config.py index e75b5210..b90af562 100644 --- a/l10n_us_hr_payroll/models/us_payroll_config.py +++ b/l10n_us_hr_payroll/models/us_payroll_config.py @@ -46,3 +46,15 @@ class HRContractUSPayrollConfig(models.Model): help='Form W4 (2020+) 4(b)') fed_941_fit_w4_additional_withholding = fields.Float(string='Federal W4 Additional Withholding [4(c)]', help='Form W4 (2020+) 4(c)') + + mt_mw4_sit_exemptions = fields.Integer(string='Montana MW-4 Exemptions', + help='MW-4 Box G') + # Don't use the main state_income_tax_exempt because of special meaning and reporting + # Use additional withholding but name it on the form 'MW-4 Box H' + mt_mw4_sit_exempt = fields.Selection([ + ('', 'Not Exempt'), + ('tribe', 'Registered Tribe'), + ('reserve', 'Reserve or National Guard'), + ('north_dakota', 'North Dakota'), + ('montana_for_marriage', 'Montana for Marriage'), + ], string='Montana MW-4 Exempt from Withholding', help='MW-4 Section 2') diff --git a/l10n_us_hr_payroll/tests/__init__.py b/l10n_us_hr_payroll/tests/__init__.py index 57f8e5e1..cc0a4321 100755 --- a/l10n_us_hr_payroll/tests/__init__.py +++ b/l10n_us_hr_payroll/tests/__init__.py @@ -6,5 +6,7 @@ from . import test_us_payslip_2020 from . import test_us_fl_florida_payslip_2019 from . import test_us_fl_florida_payslip_2020 +from . import test_us_mt_montana_payslip_2019 +from . import test_us_mt_montana_payslip_2020 from . import test_us_pa_pennsylvania_payslip_2019 from . import test_us_pa_pennsylvania_payslip_2020 diff --git a/l10n_us_hr_payroll/tests/test_us_mt_montana_payslip_2019.py b/l10n_us_hr_payroll/tests/test_us_mt_montana_payslip_2019.py new file mode 100755 index 00000000..ff6e2daf --- /dev/null +++ b/l10n_us_hr_payroll/tests/test_us_mt_montana_payslip_2019.py @@ -0,0 +1,139 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from .common import TestUsPayslip, process_payslip + + +class TestUsMtPayslip(TestUsPayslip): + # Calculations from https://app.mt.gov/myrevenue/Endpoint/DownloadPdf?yearId=705 + MT_UNEMP = -1.18 / 100.0 + MT_UNEMP_AFT = -0.13 / 100.0 + + def test_2019_taxes_one(self): + # Payroll Period Semi-Monthly example + salary = 550 + mt_mw4_exemptions = 5 + + employee = self._createEmployee() + contract = self._createContract(employee, + wage=salary, + state_id=self.get_us_state('MT'), + mt_mw4_sit_exemptions=mt_mw4_exemptions, + schedule_pay='semi-monthly') + + self._log('2019 Montana tax single first payslip:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['ER_US_SUTA'], salary * (self.MT_UNEMP + self.MT_UNEMP_AFT)) # New non-combined... + + mt_taxable_income = salary - (79.0 * mt_mw4_exemptions) + mt_withhold = round(0 + (0.018 * (mt_taxable_income - 0))) + self.assertPayrollEqual(mt_taxable_income, 155.0) + self.assertPayrollEqual(mt_withhold, 3.0) + self.assertPayrollEqual(cats['EE_US_SIT'], -mt_withhold) + + def test_2019_taxes_two(self): + # Payroll Period Bi-Weekly example + salary = 2950 + mt_mw4_exemptions = 2 + + employee = self._createEmployee() + contract = self._createContract(employee, + wage=salary, + state_id=self.get_us_state('MT'), + mt_mw4_sit_exemptions=mt_mw4_exemptions, + schedule_pay='bi-weekly') + + self._log('2019 Montana tax single first payslip:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['ER_US_SUTA'], round(salary * (self.MT_UNEMP + self.MT_UNEMP_AFT), 2)) + + # Note!! + # The example calculation uses A = 16 but the actual table describes this as A = 18 + mt_taxable_income = salary - (73.0 * mt_mw4_exemptions) + mt_withhold = round(18 + (0.06 * (mt_taxable_income - 577))) + self.assertPayrollEqual(mt_taxable_income, 2804.0) + self.assertPayrollEqual(mt_withhold, 152.0) + self.assertPayrollEqual(cats['EE_US_SIT'], -mt_withhold) + + def test_2019_taxes_three(self): + # Payroll Period Weekly example + salary = 135 + mt_mw4_exemptions = 1 + + employee = self._createEmployee() + contract = self._createContract(employee, + wage=salary, + state_id=self.get_us_state('MT'), + mt_mw4_sit_exemptions=mt_mw4_exemptions, + schedule_pay='weekly') + + self._log('2019 Montana tax single first payslip:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + + self.assertPayrollEqual(cats['ER_US_SUTA'], round(salary * (self.MT_UNEMP + self.MT_UNEMP_AFT), 2)) + + mt_taxable_income = salary - (37.0 * mt_mw4_exemptions) + mt_withhold = round(0 + (0.018 * (mt_taxable_income - 0))) + self.assertPayrollEqual(mt_taxable_income, 98.0) + self.assertPayrollEqual(mt_withhold, 2.0) + self.assertPayrollEqual(cats['EE_US_SIT'], -mt_withhold) + + def test_2019_taxes_three_exempt(self): + # Payroll Period Weekly example + salary = 135 + mt_mw4_exemptions = 1 + + employee = self._createEmployee() + contract = self._createContract(employee, + wage=salary, + state_id=self.get_us_state('MT'), + mt_mw4_sit_exemptions=mt_mw4_exemptions, + mt_mw4_sit_exempt='reserve', + schedule_pay='weekly') + + self._log('2019 Montana tax single first payslip:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + + payslip.compute_sheet() + + cats = self._getCategories(payslip) + self.assertPayrollEqual(cats.get('EE_US_SIT', 0.0), 0.0) + + def test_2019_taxes_three_additional(self): + # Payroll Period Weekly example + salary = 135 + mt_mw4_exemptions = 1 + mt_mw4_additional_withholding = 20.0 + + employee = self._createEmployee() + contract = self._createContract(employee, + wage=salary, + state_id=self.get_us_state('MT'), + mt_mw4_sit_exemptions=mt_mw4_exemptions, + state_income_tax_additional_withholding=mt_mw4_additional_withholding, + schedule_pay='weekly') + + self._log('2019 Montana tax single first payslip:') + payslip = self._createPayslip(employee, '2019-01-01', '2019-01-31') + + payslip.compute_sheet() + cats = self._getCategories(payslip) + + mt_taxable_income = salary - (37.0 * mt_mw4_exemptions) + mt_withhold = round(0 + (0.018 * (mt_taxable_income - 0))) + self.assertPayrollEqual(mt_taxable_income, 98.0) + self.assertPayrollEqual(mt_withhold, 2.0) + self.assertPayrollEqual(cats['EE_US_SIT'], -mt_withhold + -mt_mw4_additional_withholding) diff --git a/l10n_us_hr_payroll/tests/test_us_mt_montana_payslip_2020.py b/l10n_us_hr_payroll/tests/test_us_mt_montana_payslip_2020.py new file mode 100755 index 00000000..ec861a0d --- /dev/null +++ b/l10n_us_hr_payroll/tests/test_us_mt_montana_payslip_2020.py @@ -0,0 +1,17 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from datetime import date +from .common import TestUsPayslip, process_payslip + + +class TestUsMtPayslip(TestUsPayslip): + # Calculations from https://app.mt.gov/myrevenue/Endpoint/DownloadPdf?yearId=705 + MT_UNEMP_WAGE_MAX = 34100.0 + MT_UNEMP = 1.18 + MT_UNEMP_AFT = 0.13 + + def test_2020_taxes_one(self): + combined_rate = self.MT_UNEMP + self.MT_UNEMP_AFT # Combined for test as they both go to the same category and have the same cap + self._test_er_suta('MT', combined_rate, date(2020, 1, 1), wage_base=self.MT_UNEMP_WAGE_MAX) + + # TODO Montana Incometax rates for 2020 when released diff --git a/l10n_us_hr_payroll/views/us_payroll_config_views.xml b/l10n_us_hr_payroll/views/us_payroll_config_views.xml index 4c6e183c..323f1b92 100644 --- a/l10n_us_hr_payroll/views/us_payroll_config_views.xml +++ b/l10n_us_hr_payroll/views/us_payroll_config_views.xml @@ -44,6 +44,12 @@

No additional fields.

+ +

Form MT-4 - State Income Tax

+ + + +