diff --git a/l10n_ca_hr_payroll/__init__.py b/l10n_ca_hr_payroll/__init__.py new file mode 100644 index 00000000..1f5fb7fa --- /dev/null +++ b/l10n_ca_hr_payroll/__init__.py @@ -0,0 +1,10 @@ +from . import models + +def _post_install_hook(cr, registry): + """ + This method will set the default for the Payslip Sum Behavior + """ + cr.execute("SELECT id FROM ir_config_parameter WHERE key = 'hr_payroll.payslip.sum_behavior';") + existing = cr.fetchall() + if not existing: + cr.execute("INSERT INTO ir_config_parameter (key, value) VALUES ('hr_payroll.payslip.sum_behavior', 'date');") diff --git a/l10n_ca_hr_payroll/__manifest__.py b/l10n_ca_hr_payroll/__manifest__.py new file mode 100644 index 00000000..ea7309f8 --- /dev/null +++ b/l10n_ca_hr_payroll/__manifest__.py @@ -0,0 +1,29 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +{ + 'name': 'Canada - Payroll', + 'author': 'Hibou Corp. ', + 'version': '14.0.2020.0.0', + 'category': 'Payroll Localization', + 'depends': [ + 'hr_payroll_hibou', + ], + 'description': """ +Canada - Payroll Rules. +========================================= + + """, + + 'data': [ + 'data/base.xml', + 'data/federal.xml', + 'security/ir.model.access.csv', + # 'views/hr_contract_views.xml', + # 'views/us_payroll_config_views.xml', + ], + 'demo': [ + ], + 'auto_install': False, + 'post_init_hook': '_post_install_hook', + 'license': 'OPL-1', +} diff --git a/l10n_ca_hr_payroll/data/base.xml b/l10n_ca_hr_payroll/data/base.xml new file mode 100644 index 00000000..652f1b63 --- /dev/null +++ b/l10n_ca_hr_payroll/data/base.xml @@ -0,0 +1,163 @@ + + + + + Canada Employee + + + + + + Canada Employee Standard + + + + + + + + + + + + + + Wages: Salary + SALARY + + + + + Wages: Overtime + OVERTIME + + + + + Wages: Pension Income + PENSION + + + + + Wages: Taxable Benefits + TAXED_BENEFITS + + + + + + + Wages: Bonus + BONUS + + + + + Wages: Retroactive Pay Increase + RETRO_PAY + + + + + Wages: Non-Periodic Payments + NON-PERIOD + + + + + + + Deduction: Registerd Pension Plan + DED_RPP + + + + + Deduction: Registerd Retirement Savings Plan + DED_RRSP + + + + + Deduction: Pooled Registered Pension Plan + DED_RPP + + + + + Deduction: Retirement Compensation Arrangement + DED_RCA + + + + + Deduction: Alimony Before May 5th, 1997 + DED_ALIMONY_PRE_1997 + + + + + Deduction: Maintenance Before May 5th, 1997 + DED_ALIMONY_PRE_1997 + + + + + Deduction: Union Dues + DED_UNION_DUES + + + + + Deduction: Living In Prescribed Zone + DED_PRESCRIBED_ZONE + + + + + Deduction: Employee Requested + DED_EMPLOYEE_REQUESTED + + + + + Deduction: TD1 Deductions + DED_TD1 + + + + + Deduction: TD1 Deductions + DED_TD1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_ca_hr_payroll/data/ca_al_alberta.xml b/l10n_ca_hr_payroll/data/ca_al_alberta.xml new file mode 100644 index 00000000..5476f60c --- /dev/null +++ b/l10n_ca_hr_payroll/data/ca_al_alberta.xml @@ -0,0 +1,49 @@ + + + + + CA Federal Tax Rate + ca_fed_tax_rate + + + + + + { + 'annually': ( + ( 0, 0.1500, 0.00), + ( 49029, 0.2050, 2696.00), + ( 98040, 0.2600, 8088.00), + ( 151978, 0.2900, 12648.00), + ( 216511, 0.3300, 21308.00), + ( 'inf', 0.3300, 21308.00), + ), + } + + + + + + + CA Federal - Canada Revenue Agency - Federal Income Tax + + + + + + + + EE: CA Federal Income Tax + EE_CA_FIT + python + result, _ = ca_fit_federal_income_tax_withholding(payslip) + code + result, result_rate = ca_fit_federal_income_tax_withholding(payslip) + + + + + + + + \ No newline at end of file diff --git a/l10n_ca_hr_payroll/data/ca_cpp.xml b/l10n_ca_hr_payroll/data/ca_cpp.xml new file mode 100644 index 00000000..b29ac57a --- /dev/null +++ b/l10n_ca_hr_payroll/data/ca_cpp.xml @@ -0,0 +1,49 @@ + + + + + CA Canada Pension Plan + ca_cpp + + + + + + { + 'annually': ( + ( 0, 0.1500, 0.00), + ( 49029, 0.2050, 2696.00), + ( 98040, 0.2600, 8088.00), + ( 151978, 0.2900, 12648.00), + ( 216511, 0.3300, 21308.00), + ( 'inf', 0.3300, 21308.00), + ), + } + + + + + + + CA Federal - Canada Revenue Agency - Canada Pension Plan + + + + + + + + EE: CA Canada Pension Plan + EE_CA_CPP + python + result = ca_cpp_canada_pension_plan_withholding(payslip) + code + result = ca_cpp_canada_pension_plan_withholding(payslip) + + + + + + + + \ No newline at end of file diff --git a/l10n_ca_hr_payroll/data/federal.xml b/l10n_ca_hr_payroll/data/federal.xml new file mode 100644 index 00000000..5476f60c --- /dev/null +++ b/l10n_ca_hr_payroll/data/federal.xml @@ -0,0 +1,49 @@ + + + + + CA Federal Tax Rate + ca_fed_tax_rate + + + + + + { + 'annually': ( + ( 0, 0.1500, 0.00), + ( 49029, 0.2050, 2696.00), + ( 98040, 0.2600, 8088.00), + ( 151978, 0.2900, 12648.00), + ( 216511, 0.3300, 21308.00), + ( 'inf', 0.3300, 21308.00), + ), + } + + + + + + + CA Federal - Canada Revenue Agency - Federal Income Tax + + + + + + + + EE: CA Federal Income Tax + EE_CA_FIT + python + result, _ = ca_fit_federal_income_tax_withholding(payslip) + code + result, result_rate = ca_fit_federal_income_tax_withholding(payslip) + + + + + + + + \ No newline at end of file diff --git a/l10n_ca_hr_payroll/models/__init__.py b/l10n_ca_hr_payroll/models/__init__.py new file mode 100644 index 00000000..cddb74f4 --- /dev/null +++ b/l10n_ca_hr_payroll/models/__init__.py @@ -0,0 +1,3 @@ +from . import ca_payroll_config +from . import hr_ca_contract +from . import hr_payslip diff --git a/l10n_ca_hr_payroll/models/ca_payroll_config.py b/l10n_ca_hr_payroll/models/ca_payroll_config.py new file mode 100644 index 00000000..aecf520a --- /dev/null +++ b/l10n_ca_hr_payroll/models/ca_payroll_config.py @@ -0,0 +1,66 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import api, fields, models + + +class HRContractCanadaPayrollConfig(models.Model): + _name = 'hr.contract.ca_payroll_config' + _description = 'Contract Canada Payroll Forms' + + # Quebec https://www.revenuquebec.ca/en/online-services/tools/webras-and-winras-calculation-of-source-deductions-and-employer-contributions/ + # https://www.canada.ca/en/revenue-agency/services/forms-publications/payroll/t4127-payroll-deductions-formulas/t4127-jan.html + + name = fields.Char(string="Description") + employee_id = fields.Many2one('hr.employee', string="Employee", required=True) + state_id = fields.Many2one('res.country.state', string="Applied State") + state_code = fields.Char(related='state_id.code') + + contributes_to_rpp = fields.Boolean( + string='Employee Contributes to a registered pension plan (RPP)?', + help='For tax deduction purposes, employers can deduct amounts contributed to an RPP, RRSP, PRPP, or RCA by or on behalf of an employee to determine the employee\'s taxable income', + ) + rpp_withdrawal_per_check = fields.Float( + string='RPP to Withdrawal Per Paycheck', + help='Enter the dollar amount to be withdrawn per paycheck for a registered pension plan' + ) + + contributes_to_rrsp = fields.Boolean( + string='Contributes to a registered retirement savings plan (RRSP)', + help='For tax deduction purposes, employers can deduct amounts contributed to an RPP, RRSP, PRPP, or RCA by or on behalf of an employee to determine the employee\'s taxable income', + ) + rrsp_withdrawal_per_check = fields.Float( + string='RRSP to Withdrawal Per Paycheck', + help='Enter the dollar amount to be withdrawn per paycheck for a registered retirement savings plan (RRSP)' + ) + + contributes_to_prpp = fields.Boolean( + string='Contributes to a pooled registered pension plan (PRPP)?', + help='For tax deduction purposes, employers can deduct amounts contributed to an RPP, RRSP, PRPP, or RCA by or on behalf of an employee to determine the employee\'s taxable income', + ) + contributes_to_rca = fields.Boolean( + string='Contributes to a retirement compensation arrangement (RCA)?', + help='For tax deduction purposes, employers can deduct amounts contributed to an RPP, RRSP, PRPP, or RCA by or on behalf of an employee to determine the employee\'s taxable income', + ) + alimony_or_maintenance_deduction_required = fields.Boolean( + string='Alimony or maintenance payments required?', + help='Annual deductions such as child care expenses and support payments, requested by an employee or pensioner and authorized by a tax services office or tax centre', + ) + union_dues_deducted = fields.Boolean( + string='Dues deducted?', + help='Union dues for the pay period paid to a trade union, an association of public servants, or dues required under the law of a province to a parity or advisory committee or similar body', + ) + lives_in_prescribed_zone = fields.Boolean( + string='Perscribed zone deduction?', + help='Annual deduction for living in a prescribed zone, as shown on Form TD1' + ) + other_anual_deductions = fields.Boolean( + string='Other annual deductions?', + help='Annual deductions such as child care expenses and support payments, requested by an employee or pensioner and authorized by a tax services office or tax centre' + ) + paid_commission = fields.Boolean( + string='Paid a commission?', + help='Does the employee receive any commissions?', + ) + + def ca_payroll_config_value(self, name): + return self.ca_payroll_config_id[name] \ No newline at end of file diff --git a/l10n_ca_hr_payroll/models/common.py b/l10n_ca_hr_payroll/models/common.py new file mode 100644 index 00000000..5a35556b --- /dev/null +++ b/l10n_ca_hr_payroll/models/common.py @@ -0,0 +1,25 @@ + +def _compute_employee_contribution_deductions(payslip): + # todo: _compute_employee_contribution_deductions + return 0.0 + +def _compute_annual_taxable_income(payslip): + # A = Annual taxable income = [P × (I – F – F2 – U1 )] – HD – F1 + # # If the result is negative, T = L. + # annual_taxable_income = ( + # annual_pay_periods_p + # *( + # gross_remuneration_i + # - employee_contribution_deductions_f + # - required_deductions_f2 + # - union_dues_u1 + # ) + # - prescribed_zone_hd + # - employee_requested_deduction_f1 + # ) + pay_periods = payslip.dict.get_pay_periods_in_year() + annual_pay_periods_p = pay_periods[payslip.contract_id.schedule_pay] + gross_remuneration_i = annual_pay_periods_p * payslip.contract_id.wage + employee_contribution_deductions_f = _compute_employee_contribution_deductions(payslip) + required_deductions_f2 = _compute_employee_contribution_deductions(payslip) + pass \ No newline at end of file diff --git a/l10n_ca_hr_payroll/models/federal/__init__.py b/l10n_ca_hr_payroll/models/federal/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/models/federal/ca_cpp.py b/l10n_ca_hr_payroll/models/federal/ca_cpp.py new file mode 100644 index 00000000..d7ad6d1c --- /dev/null +++ b/l10n_ca_hr_payroll/models/federal/ca_cpp.py @@ -0,0 +1,9 @@ +from odoo import fields +from .common import TestCAPayslip +import logging + +_logger = logging.getLogger("__name__") + +def ca_cpp_canada_pension_plan_withholding(payslip): + _logger.warning('ca_cpp_canada_pension_plan_withholding************************') + pass \ No newline at end of file diff --git a/l10n_ca_hr_payroll/models/federal/ca_ei.py b/l10n_ca_hr_payroll/models/federal/ca_ei.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/models/federal/ca_fit.py b/l10n_ca_hr_payroll/models/federal/ca_fit.py new file mode 100644 index 00000000..69b70f5e --- /dev/null +++ b/l10n_ca_hr_payroll/models/federal/ca_fit.py @@ -0,0 +1,50 @@ +import logging + +_logger = logging.getLogger("__name__") + +def ca_fit_federal_income_tax_withholding(payslip): + # annual_taxable_income = _compute_annual_taxable_income(payslip) + + + # _logger.warning('payslip.contract_id************************************') + # _logger.warning(str(payslip.contract_id.read())) + # _logger.warning('payslip.contract_id.structure_type_id.read()************************************') + # _logger.warning(str(payslip.contract_id.structure_type_id.read())) + # _logger.warning('payslip.contract_id.structure_type_id.struct_ids[0].read()************************************') + # _logger.warning(str(payslip.contract_id.structure_type_id.struct_ids[0].read())) + # _logger.warning('payslip.contract_id.structure_type_id.struct_ids[0].rule_ids[0].read()************************************') + # _logger.warning(str(payslip.contract_id.structure_type_id.struct_ids[0].rule_ids[0].read())) + _logger.warning('payslip.rule_parameter(rule_parameter_ca_fed_tax_rate)************************************') + _logger.warning(str(payslip.rule_parameter)) + rates = payslip.rule_parameter('ca_fed_tax_rate')['annually'] #this is the hr.rule.parameter code + # _logger.warning(str(rates)) + wage = payslip.contract_id.wage + _logger.warning(f'wage = {str(wage)}') + i = 0 + _logger.warning(f'rates ================================== {str(rates)}') + for annual_taxable_income, rate, federal_constant in rates: + if isinstance(annual_taxable_income, str): + _logger.warning(f'annual_taxable_income is str {annual_taxable_income}') + _logger.warning(f'wage*rate = {str(wage*rate)}, and rate is {str(rate)}') + return wage, -rate + if annual_taxable_income/12 >= wage: + if i != 0: + _logger.warning(f'if i != 0') + rate = rates[i-1][1]*100 + _logger.warning(f'rate = **************************************** {rate}') + _logger.warning(f' wage*rate = *************************************** {wage*rate}') + return wage, -rate + else: + _logger.warning(f'return 0.0, 0.0') + return 0.0, 0.0 + else: + _logger.warning(f' annual_taxable_income/12 = {str(annual_taxable_income/12)} which is below wage = {str(str(wage))}*****************************') + i +=1 + continue + + + + + + + return 0.0, 0.0 \ No newline at end of file diff --git a/l10n_ca_hr_payroll/models/hr_ca_contract.py b/l10n_ca_hr_payroll/models/hr_ca_contract.py new file mode 100644 index 00000000..76dcfd16 --- /dev/null +++ b/l10n_ca_hr_payroll/models/hr_ca_contract.py @@ -0,0 +1,10 @@ +from odoo import api, fields, models + + +class CAHRContract(models.Model): + _inherit = 'hr.contract' + + ca_payroll_config_id = fields.Many2one('hr.contract.ca_payroll_config', 'Canada Payroll Forms') + + + diff --git a/l10n_ca_hr_payroll/models/hr_payslip.py b/l10n_ca_hr_payroll/models/hr_payslip.py new file mode 100644 index 00000000..a6db2ef4 --- /dev/null +++ b/l10n_ca_hr_payroll/models/hr_payslip.py @@ -0,0 +1,32 @@ +# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. + +from odoo import api, fields, models + +from .federal.ca_fit import ca_fit_federal_income_tax_withholding + + +class HRPayslip(models.Model): + _inherit = 'hr.payslip' + + # From IRS Publication 15-T or logically (annually, bi-monthly) + PAY_PERIODS_IN_YEAR = { + 'annually': 1, + 'semi-annually': 2, + 'quarterly': 4, + 'bi-monthly': 6, + 'monthly': 12, + 'semi-monthly': 24, + 'bi-weekly': 26, + 'weekly': 52, + 'daily': 260, + } + + def _get_base_local_dict(self): + res = super()._get_base_local_dict() + res.update({ + 'ca_fit_federal_income_tax_withholding': ca_fit_federal_income_tax_withholding, + }) + return res + + def get_pay_periods_in_year(self): + return self.PAY_PERIODS_IN_YEAR.get(self.contract_id.schedule_pay, 0) diff --git a/l10n_ca_hr_payroll/models/quebec/__init__.py b/l10n_ca_hr_payroll/models/quebec/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/models/territory/__init__.py b/l10n_ca_hr_payroll/models/territory/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/models/territory/ca_al_alberta.py b/l10n_ca_hr_payroll/models/territory/ca_al_alberta.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/models/territory/ca_bc_british_columbia.py b/l10n_ca_hr_payroll/models/territory/ca_bc_british_columbia.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/models/territory/ca_mb_manitoba.py b/l10n_ca_hr_payroll/models/territory/ca_mb_manitoba.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/models/territory/ca_nl_newfoundand_and_labrador.py b/l10n_ca_hr_payroll/models/territory/ca_nl_newfoundand_and_labrador.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/models/territory/ca_ns_nova_scotia.py b/l10n_ca_hr_payroll/models/territory/ca_ns_nova_scotia.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/models/territory/ca_nt_northwest_territories.py b/l10n_ca_hr_payroll/models/territory/ca_nt_northwest_territories.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/models/territory/ca_nu_nunavut.py b/l10n_ca_hr_payroll/models/territory/ca_nu_nunavut.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/models/territory/ca_on_ontario.py b/l10n_ca_hr_payroll/models/territory/ca_on_ontario.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/models/territory/ca_pe_prince_edward_island.py b/l10n_ca_hr_payroll/models/territory/ca_pe_prince_edward_island.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/models/territory/ca_sk_saskatchewan.py b/l10n_ca_hr_payroll/models/territory/ca_sk_saskatchewan.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/models/territory/ca_yt_yukon.py b/l10n_ca_hr_payroll/models/territory/ca_yt_yukon.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_ca_hr_payroll/security/ir.model.access.csv b/l10n_ca_hr_payroll/security/ir.model.access.csv new file mode 100644 index 00000000..80e30de5 --- /dev/null +++ b/l10n_ca_hr_payroll/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_hr_contract_ca_payroll_config,access_hr_contract_ca_payroll_config,model_hr_contract_ca_payroll_config,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/l10n_ca_hr_payroll/tests/__init__.py b/l10n_ca_hr_payroll/tests/__init__.py new file mode 100644 index 00000000..9a13c238 --- /dev/null +++ b/l10n_ca_hr_payroll/tests/__init__.py @@ -0,0 +1,2 @@ +from . import common +from . import test_ca_federal_payslip \ No newline at end of file diff --git a/l10n_ca_hr_payroll/tests/common.py b/l10n_ca_hr_payroll/tests/common.py new file mode 100644 index 00000000..c5021dc8 --- /dev/null +++ b/l10n_ca_hr_payroll/tests/common.py @@ -0,0 +1,160 @@ +from . import common + +import logging +from odoo.addons.hr_payroll_hibou.tests import common + +_logger = logging.getLogger("__name__") +#todo need to work in currency + + +class TestCAPayslip(common.TestPayslip): + + def setUp(self): + super(TestCAPayslip, self).setUp() + self.structure_type = self.env.ref('l10n_ca_hr_payroll.ca_structure_type_employee') + self.structure = self.env.ref('l10n_ca_hr_payroll.hr_ca_payroll_structure') + self.structure_type.default_struct_id = self.structure + self._log('US structue_type %s and structure %s' % (self.structure_type, self.structure)) + _logger.warning(str(self.structure_type)) + + + def _createEmployee(self): + return self.env['hr.employee'].create({ + 'birthday': '1985-03-14', + 'country_id': self.ref('base.ca'), + 'department_id': self.ref('hr.dep_rd'), + 'gender': 'male', + 'name': 'Jared' + }) + + def _createCAContract(self, employee, wage=7000, pay_schedule='monthly'): + country_id = self.env['res.country'].search([('code', '=', 'CA')]) + self.assertEqual(employee.country_id, country_id, 'The employee\'s country_id is not for Canada') + + contract = self._createContract(employee, + wage=wage, + structure_type_id=self.env.ref( + 'l10n_ca_hr_payroll.ca_structure_type_employee'), + pay_schedule=pay_schedule) + self.assertEqual(contract.wage, wage, + 'The contract salary of "%s" does not equal the test salary of "%s".' % ( + contract.wage, wage)) + _logger.warning('Created Contract &&&&&&&&&&&&&&&&&&&&&&&') + return contract + + def get_providence(self): + pass + + def get_ca_cpp_canada_pension_plan_withholding(self): + _logger.warning(f'self.rpp_withdrawal_per_check = {str(self.rpp_withdrawal_per_check)} --------------------------------') + if self.rpp_withdrawal_per_check > 0: + return self.rpp_withdrawal_per_check + else: + return 0.0 + + # def get_ca_state(self, code, cache={}): + # country_key = 'CA_COUNTRY' + # if code in cache: + # return cache[code] + # if country_key not in cache: + # cache[country_key] = self.env.ref('base.ca') + # ca_country = cache[country_key] + # ca_state = self.env['res.country.state'].search([ + # ('country_id', '=', ca_country.id), + # ('code', '=', code), + # ], limit=1) + # cache[code] = ca_state + # return ca_state + + + + + + + # _logger.warning(str(payslip.read())) + + # start asserting + + + + + # # to work in shell + # employee = env['hr.employee'].create({ + # 'birthday': '1985-03-14', + # 'country_id': env['res.country'].search([('code', '=', 'CA')]).id, + # 'department_id': env.ref('hr.dep_rd').id, + # 'gender': 'male', + # 'name': 'Jared' + # }) + # schedule_pay = 'monthly' + # salary = 80000.0 + # contract_values = { + # 'wage': salary, + # 'name': 'Test Contract', + # 'employee_id': employee.id, + # 'structure_type_id': env.ref("l10n_ca_hr_payroll.ca_structure_type_employee"), + # } + # contract = env['hr.contract'].create(contract_values) + # contract.structure_type_id.default_struct_id.schedule_pay = schedule_pay + # date_from = '2021-01-01' + # date_to = '2021-01-31' + # slip = env['hr.payslip'].create({ + # 'name': 'Test %s From: %s To: %s' % (employee.name, date_from, date_to), + # 'employee_id': employee.id, + # 'date_from': date_from, + # 'date_to': date_to + # }) + + + # def _createContract(self, employee, **kwargs): + # # Override + # if not 'schedule_pay' in kwargs: + # kwargs['schedule_pay'] = 'monthly' + # schedule_pay = kwargs['schedule_pay'] + # config_model = self.env['hr.contract.us_payroll_config'] + # contract_model = self.env['hr.contract'] + # config_values = { + # 'name': 'Test Config Values', + # 'employee_id': employee.id, + # } + # contract_values = { + # 'name': 'Test Contract', + # 'employee_id': employee.id, + # } + # + # # Backwards compatability with 'futa_type' + # if 'futa_type' in kwargs: + # kwargs['fed_940_type'] = kwargs['futa_type'] + # + # for key, val in kwargs.items(): + # # Assume any Odoo object is in a Many2one + # if hasattr(val, 'id'): + # val = val.id + # found = False + # if hasattr(contract_model, key): + # contract_values[key] = val + # found = True + # if hasattr(config_model, key): + # config_values[key] = val + # found = True + # if not found: + # self._logger.warn('cannot locate attribute names "%s" on contract or payroll config' % (key, )) + # + # # US Payroll Config Defaults Should be set on the Model + # config = config_model.create(config_values) + # contract_values['us_payroll_config_id'] = config.id + # self._get_contract_defaults(contract_values) + # self._log('creating contract with finial values: %s' % (contract_values, )) + # contract = contract_model.create(contract_values) + # + # # Compatibility with Odoo 13/14 + # contract.structure_type_id.default_struct_id.schedule_pay = schedule_pay + # return contract + + + + + + + + diff --git a/l10n_ca_hr_payroll/tests/notes.py b/l10n_ca_hr_payroll/tests/notes.py new file mode 100644 index 00000000..8376982f --- /dev/null +++ b/l10n_ca_hr_payroll/tests/notes.py @@ -0,0 +1,119 @@ +# from datetime import date +# from .common import TestCAPayslip +# import logging +# +# _logger = logging.getLogger("__name__") +# +# +# class TestPayslip(TestCAPayslip): +# # https://www.canada.ca/en/revenue-agency/services/forms-publications/payroll/t4127-payroll-deductions-formulas/t4127-jan/t4127-jan-payroll-deductions-formulas-computer-programs.html +# +# # P = The number of pay periods in the year: +# # Weekly P = 52 (or 53 where applicable) +# # Biweekly P = 26 (or 27 where applicable) +# # Semi-monthly P = 24 +# # Monthly P = 12 +# # Other P = 10, 13, 22, or any other number of pay periods for the year +# +# def test_2021_federal_payslip(self): +# +# salary = 80000.0 +# annual_pay_periods_p = 12 +# +# # i = Gross remuneration for the pay period.This includes overtime earned and paid in the same pay period, +# # pension income, qualified pension income, and taxable benefits, +# # but does not include bonuses, retroactive pay increases, or other non-periodic payments +# +# # included +# overtime = 200.0 +# pension_income = 3000.0 +# qualified_pension_income = 2000.0 +# taxable_benefits = 400.0 +# +# # not included +# bonuses = 50.0 +# retroactive_pay_increase = 45.0 +# nonperiodic_payments = 55.0 +# non_gross_remuneration = bonuses + retroactive_pay_increase + nonperiodic_payments +# +# gross_remuneration_for_period_i = (salary/annual_pay_periods_p) + overtime + pension_income + qualified_pension_income + taxable_benefits +# +# # F = Payroll deductions for the pay period for employee contributions to a registered pension plan (RPP) for current and past services, a registered retirement savings plan (RRSP), to a pooled registered pension plan (PRPP), or a retirement compensation arrangement (RCA).For tax deduction purposes, employers can deduct amounts contributed to an RPP, RRSP, PRPP, or RCA by or on behalf of an employee to determine the employee's taxable income +# rpp = 100.0 #registered pension plan +# rrsp = 150.0 #registered retirement savings plan +# prpp = 200.0 #pooled registered pension plan +# rca = 250.0 #retirement compensation arrangement +# employee_contribution_deductions_f = rpp + rrsp + prpp + rca +# +# #F2 = Alimony or maintenance payments required by a legal document dated before May 1, 1997, to be payroll-deducted authorized by a tax services office or tax centre +# alimony_before_1997_05_01 = 100.0 +# maintenance_payments_before_1997_05_01 = 150.0 +# required_deductions_f2 = alimony_before_1997_05_01 + maintenance_payments_before_1997_05_01 +# +# #U1 = Union dues for the pay period paid to a trade union, an association of public servants, or dues required under the law of a province to a parity or advisory committee or similar body +# union_dues_u1 = 50.0 +# +# #HD = Annual deduction for living in a prescribed zone, as shown on Form TD1 +# prescribed_zone_hd = 60.0 +# +# #F1 = Annual deductions such as child care expenses and support payments, requested by an employee or pensioner and authorized by a tax services office or tax centre +# employee_requested_deduction_f1 = 70.0 +# +# #L = Additional tax deductions for the pay period requested by the employee or pensioner as shown on Form TD1 +# additional_ee_deductions_l = 1200.0 +# +# #T = Estimated federal and provincial or territorial tax deductions for the pay period +# estimated_total_tax = None +# +# #A = Annual taxable income = [P × (I – F – F2 – U1 )] – HD – F1 +# # If the result is negative, T = L. +# annual_taxable_income = ( +# annual_pay_periods_p +# *( +# gross_remuneration_i +# - employee_contribution_deductions_f +# - required_deductions_f2 +# - union_dues_u1 +# ) +# - prescribed_zone_hd +# - employee_requested_deduction_f1 +# ) +# +# if annual_taxable_income < 0: +# estimated_total_tax = additional_ee_deductions_l +# +# employee = self._createEmployee() +# country_id = self.env['res.country'].search([('code', '=', 'CA')]) +# self.assertEqual(employee.country_id, country_id, 'The employee\'s country_id is not for Canada') +# +# contract = self._createContract(employee, wage=salary) +# self.assertEqual(contract.wage, salary, 'The contract salary of "%s" does not equal the test salary of "%s".' % (contract.wage, salary)) +# +# self._log('2021 tax first payslip:') +# payslip = self._createPayslip(employee, '2021-01-01', '2021-01-31') +# self.assertEqual(payslip.contract_id, contract) +# +# #Determine the taxable income for the pay period (pay minus allowable deductions) and multiply it by the number of pay periods in the year to get an estimated annual taxable income amount. This annual taxable income amount is factor A. +# #assert(gross_remuneration_for_period_i = gross payslip_pay - non_gross_remuneration) +# +# # Calculate the basic federal tax on the estimated annual taxable income, after allowable federal non-refundable tax credits. The basic federal tax is factor T3. +# # T3 = Annual basic federal tax +# # = (R × A) – K – K1 – K2 – K3 – K4 +# +# # (R federal rate for income based on table +# # X A Annual Income) +# # - K Federal constant. The constant is the tax overcharged when applying the 20.5%, 26%, 29%, and 33% rates to the annual taxable income A +# # - K1 Federal non-refundable personal tax credit (the lowest federal tax rate is used to calculate this credit) +# # - K2 Federal Canada Pension Plan contributions and employment insurance premiums tax credits for the year (the lowest federal tax rate is used to calculate this credit). +# # Note: If an employee has already contributed the maximum CPP and EI, for the year with the employer, use the maximum CPP and EI deduction to determine the credit for the rest of the year. If, during the pay period in which the employee reaches the maximum, the CPP and EI, when annualized, is less than the annual maximum, use the maximum annual deduction(s) in that pay period +# # - K3 Other federal non-refundable tax credits (such as medical expenses and charitable donations) authorized by a tax services office or tax centre +# # - K4 Factor calculated using the Canada employment amount credit (the lowest federal tax rate is used to calculate this credit) +# # If the result is negative, T3 = $0. +# +# # Calculate the annual federal tax payable. This is factor T1. +# +# +# # Calculate the basic provincial or territorial tax on the estimated annual taxable income, after allowable provincial or territorial personal tax credits. The annual basic provincial or territorial tax is factor T4. +# # Calculate the annual provincial or territorial tax deduction. This is factor T2. +# # To get the estimated federal and provincial or territorial tax deductions for a pay period, add the federal and provincial or territorial tax, and divide the result by the number of pay periods. This is factor T. +# diff --git a/l10n_ca_hr_payroll/tests/test_ca_federal_payslip.py b/l10n_ca_hr_payroll/tests/test_ca_federal_payslip.py new file mode 100644 index 00000000..d2b7e27f --- /dev/null +++ b/l10n_ca_hr_payroll/tests/test_ca_federal_payslip.py @@ -0,0 +1,90 @@ +from odoo import fields +from .common import TestCAPayslip +import logging + +_logger = logging.getLogger("__name__") + + +class TestPayslip(TestCAPayslip): + + def test_basic_federal_tax(self, + salary=7000.0, + date_from='2021-01-01', + date_to='2021-01-31', + state_code=None, + **extra_contract): + annual_pay_periods_p = 12 + employee = self._createEmployee() + contract = self._createCAContract(employee=employee) + + + self._log('2021 tax first payslip:') + payslip = self._createPayslip(employee, date_from, date_to) + # self.assertEqual(payslip.struct_type_id, ) + self.assertEqual(payslip.contract_id, contract, f'Payslip contract {str(payslip.contract_id)} is not correct') + self.assertEqual(payslip.struct_id.name, 'Canada Employee Standard', + f'payroll structure {payslip.struct_id.name} is not correct') + self.assertEqual(payslip.date_from, fields.Date.from_string(date_from), + f'payslip date_from {payslip.date_from} is not correct ') + self.assertEqual(payslip.date_to, fields.Date.from_string(date_to), + f'payslip date_to {payslip.date_to} is not correct ') + self.assertEqual(payslip.employee_id.name, 'Jared', + f'payslip employee {payslip.employee_id.name} is not correct') + + _logger.warning(str(payslip.read())) + for line in payslip.line_ids: + _logger.warning(f'payslip line read {str(line)}************************************') + _logger.warning(line.read()) + + # if line.name == 'EE: CA Federal Income Tax': + # _logger.warning(f'payslip line read {str(line)}************************************') + # _logger.warning(line.read()) + # _logger.warning('payslip.contract_id************************************') + # _logger.warning(str(payslip.contract_id.read())) + # _logger.warning('payslip.contract_id.structure_type_id.read()************************************') + # _logger.warning(str(payslip.contract_id.structure_type_id.read())) + # _logger.warning('payslip.contract_id.structure_type_id.struct_ids[0].read()************************************') + # _logger.warning(str(payslip.contract_id.structure_type_id.struct_ids[0].read())) + # _logger.warning('payslip.contract_id.structure_type_id.struct_ids[0].rule_ids[0].read()************************************') + # _logger.warning(str(payslip.contract_id.structure_type_id.struct_ids[0].rule_ids[0].read())) + # _logger.warning('payslip.rule_parameter(rule_parameter_ca_fed_tax_rate)************************************') + + self.assertPayrollAlmostEqual(payslip.net_wage, 5565) + # self.assertEqual(payslip.net_wage, 5565, 'total tax is off') + + # schedule_pay = payslip.contract_id.schedule_pay + # additional = payslip.contract_id.us_payroll_config_value('state_income_tax_additional_withholding') + # sit_allowances = payslip.contract_id.us_payroll_config_value('ca_de4_sit_allowances') + # additional_allowances = payslip.contract_id.us_payroll_config_value('ca_de4_sit_additional_allowances') + # low_income_exemption = payslip.rule_parameter('us_ca_sit_income_exemption_rate')[schedule_pay] + # estimated_deduction = payslip.rule_parameter('us_ca_sit_estimated_deduction_rate')[schedule_pay] + # tax_table = payslip.rule_parameter('us_ca_sit_tax_rate')[filing_status].get(schedule_pay) + # standard_deduction = payslip.rule_parameter('us_ca_sit_standard_deduction_rate')[schedule_pay] + # exemption_allowances = payslip.rule_parameter('us_ca_sit_exemption_allowance_rate')[schedule_pay] + + #Determine the taxable income for the pay period (pay minus allowable deductions) and multiply it by the number of pay periods in the year to get an estimated annual taxable income amount. This annual taxable income amount is factor A. + #assert(gross_remuneration_for_period_i = gross payslip_pay - non_gross_remuneration) + + # Calculate the basic federal tax on the estimated annual taxable income, after allowable federal non-refundable tax credits. The basic federal tax is factor T3. + # T3 = Annual basic federal tax + # = (R × A) – K – K1 – K2 – K3 – K4 + + # (R federal rate for income based on table + # X A Annual Income) + # - K2 Federal Canada Pension Plan contributions and employment insurance premiums tax credits for the year (the lowest federal tax rate is used to calculate this credit). + # Note: If an employee has already contributed the maximum CPP and EI, for the year with the employer, use the maximum CPP and EI deduction to determine the credit for the rest of the year. If, during the pay period in which the employee reaches the maximum, the CPP and EI, when annualized, is less than the annual maximum, use the maximum annual deduction(s) in that pay period + # - K3 Other federal non-refundable tax credits (such as medical expenses and charitable donations) authorized by a tax services office or tax centre + # - K4 Factor calculated using the Canada employment amount credit (the lowest federal tax rate is used to calculate this credit) + # - K Federal constant. The constant is the tax overcharged when applying the 20.5%, 26%, 29%, and 33% rates to the annual taxable income A + # - K1 Federal non-refundable personal tax credit (the lowest federal tax rate is used to calculate this credit) + + # If the result is negative, T3 = $0. + + # Calculate the annual federal tax payable. This is factor T1. + + + # Calculate the basic provincial or territorial tax on the estimated annual taxable income, after allowable provincial or territorial personal tax credits. The annual basic provincial or territorial tax is factor T4. + # Calculate the annual provincial or territorial tax deduction. This is factor T2. + # To get the estimated federal and provincial or territorial tax deductions for a pay period, add the federal and provincial or territorial tax, and divide the result by the number of pay periods. This is factor T. + + # test_federal_with \ No newline at end of file