[MOV] l10n_ca_hr_payroll: copy from WIP 14

This commit is contained in:
Jared Kipe
2021-12-23 07:45:42 -08:00
parent dd765ba35e
commit 33a9b41b9d
31 changed files with 966 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
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');")

View File

@@ -0,0 +1,30 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
{
'name': 'Canada - Payroll',
'author': 'Hibou Corp. <hello@hibou.io>',
'version': '15.0.2022.0.0',
'category': 'Payroll Localization',
'depends': [
'hr_payroll_hibou',
],
'description': """
Canada - Payroll Rules.
=======================
""",
'data': [
'security/ir.model.access.csv',
'data/base.xml',
'data/federal.xml',
'data/ca_cpp.xml',
# 'views/hr_contract_views.xml',
# 'views/ca_payroll_config_views.xml',
],
'demo': [
],
'auto_install': False,
'post_init_hook': '_post_install_hook',
'license': 'OPL-1',
}

View File

@@ -0,0 +1,168 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="ca_structure_type_employee" model="hr.payroll.structure.type">
<field name="name">Canada Employee</field>
<field name="default_resource_calendar_id" ref="resource.resource_calendar_std"/>
<field name="country_id" ref="base.ca"/>
</record>
<record id="hr_ca_payroll_structure" model="hr.payroll.structure">
<field name="name">Canada Employee Standard</field>
<field name="country_id" ref="base.ca"/>
<field name="type_id" ref="l10n_ca_hr_payroll.ca_structure_type_employee"/>
<field name="unpaid_work_entry_type_ids" eval="[
(4, ref('hr_work_entry_contract.work_entry_type_unpaid_leave')),
]"/>
</record>
<record id="l10n_ca_hr_payroll.ca_structure_type_employee" model="hr.payroll.structure.type">
<field name="default_struct_id" ref="hr_ca_payroll_structure"/>
</record>
<!-- Categories -->
<!-- Included in Gross Remuneration -->
<!-- <record id="hr_payroll_category_basic_ca_salary" model="hr.salary.rule.category">-->
<!-- <field name="name">Wages: Salary</field>-->
<!-- <field name="code">SALARY</field>-->
<!-- <field name="parent_id" ref="hr_payroll.BASIC"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_basic_ca_overtime" model="hr.salary.rule.category">-->
<!-- <field name="name">Wages: Overtime</field>-->
<!-- <field name="code">OVERTIME</field>-->
<!-- <field name="parent_id" ref="hr_payroll.BASIC"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_basic_ca_pension_income" model="hr.salary.rule.category">-->
<!-- <field name="name">Wages: Pension Income</field>-->
<!-- <field name="code">PENSION</field>-->
<!-- <field name="parent_id" ref="hr_payroll.BASIC"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_basic_ca_taxable_benefits" model="hr.salary.rule.category">-->
<!-- <field name="name">Wages: Taxable Benefits</field>-->
<!-- <field name="code">TAXED_BENEFITS</field>-->
<!-- <field name="parent_id" ref="hr_payroll.BASIC"/>-->
<!-- </record>-->
<!-- &lt;!&ndash; Excluded from Gross Remuneration&ndash;&gt;-->
<!-- <record id="hr_payroll_category_alw_ca_bonus" model="hr.salary.rule.category">-->
<!-- <field name="name">Wages: Bonus</field>-->
<!-- <field name="code">BONUS</field>-->
<!-- <field name="parent_id" ref="hr_payroll.ALW"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_alw_ca_retro_pay_increase" model="hr.salary.rule.category">-->
<!-- <field name="name">Wages: Retroactive Pay Increase</field>-->
<!-- <field name="code">RETRO_PAY</field>-->
<!-- <field name="parent_id" ref="hr_payroll.ALW"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_alw_ca_nonperiodic_payment" model="hr.salary.rule.category">-->
<!-- <field name="name">Wages: Non-Periodic Payments</field>-->
<!-- <field name="code">NON-PERIOD</field>-->
<!-- <field name="parent_id" ref="hr_payroll.ALW"/>-->
<!-- </record>-->
<!-- &lt;!&ndash; Employee Contributed Deductions &ndash;&gt;-->
<!-- <record id="hr_payroll_category_ded_ca_rpp" model="hr.salary.rule.category">-->
<!-- <field name="name">Deduction: Registerd Pension Plan</field>-->
<!-- <field name="code">DED_RPP</field>-->
<!-- <field name="parent_id" ref="hr_payroll.DED"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_ded_ca_rrsp" model="hr.salary.rule.category">-->
<!-- <field name="name">Deduction: Registerd Retirement Savings Plan</field>-->
<!-- <field name="code">DED_RRSP</field>-->
<!-- <field name="parent_id" ref="hr_payroll.DED"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_ded_ca_prpp" model="hr.salary.rule.category">-->
<!-- <field name="name">Deduction: Pooled Registered Pension Plan</field>-->
<!-- <field name="code">DED_RPP</field>-->
<!-- <field name="parent_id" ref="hr_payroll.DED"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_ded_ca_rca" model="hr.salary.rule.category">-->
<!-- <field name="name">Deduction: Retirement Compensation Arrangement</field>-->
<!-- <field name="code">DED_RCA</field>-->
<!-- <field name="parent_id" ref="hr_payroll.DED"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_ded_ca_alimony_pre_1997" model="hr.salary.rule.category">-->
<!-- <field name="name">Deduction: Alimony Before May 5th, 1997</field>-->
<!-- <field name="code">DED_ALIMONY_PRE_1997</field>-->
<!-- <field name="parent_id" ref="hr_payroll.DED"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_ded_ca_maintenance_pre_1997" model="hr.salary.rule.category">-->
<!-- <field name="name">Deduction: Maintenance Before May 5th, 1997</field>-->
<!-- <field name="code">DED_ALIMONY_PRE_1997</field>-->
<!-- <field name="parent_id" ref="hr_payroll.DED"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_ded_ca_union_dues" model="hr.salary.rule.category">-->
<!-- <field name="name">Deduction: Union Dues</field>-->
<!-- <field name="code">DED_UNION_DUES</field>-->
<!-- <field name="parent_id" ref="hr_payroll.DED"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_ded_ca_prescribed_zone" model="hr.salary.rule.category">-->
<!-- <field name="name">Deduction: Living In Prescribed Zone</field>-->
<!-- <field name="code">DED_PRESCRIBED_ZONE</field>-->
<!-- <field name="parent_id" ref="hr_payroll.DED"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_ded_ca_employee_requested" model="hr.salary.rule.category">-->
<!-- <field name="name">Deduction: Employee Requested</field>-->
<!-- <field name="code">DED_EMPLOYEE_REQUESTED</field>-->
<!-- <field name="parent_id" ref="hr_payroll.DED"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_ded_ca_td1" model="hr.salary.rule.category">-->
<!-- <field name="name">Deduction: TD1 Deductions</field>-->
<!-- <field name="code">DED_TD1</field>-->
<!-- <field name="parent_id" ref="hr_payroll.DED"/>-->
<!-- </record>-->
<!-- <record id="hr_payroll_category_ded_ca_td1" model="hr.salary.rule.category">-->
<!-- <field name="name">Deduction: TD1 Deductions</field>-->
<!-- <field name="code">DED_TD1</field>-->
<!-- <field name="parent_id" ref="hr_payroll.DED"/>-->
<!-- </record>-->
<!-- rules for canada-->
<!-- BASIC-->
<!-- included in gross remuneration-->
<!-- salary-->
<!-- wages-->
<!-- overtime-->
<!-- pension_income-->
<!-- qualified_pension_income-->
<!-- taxable_benefits-->
<!-- not included in gross remuneration-->
<!-- bonuses-->
<!-- retroactive_pay_increase-->
<!-- nonperiodic_payments-->
<!-- DED-->
<!-- employee contributions-->
<!-- rpp registered pension plan-->
<!-- rrsp registered retirement savings plan-->
<!-- prpp pooled registered pension plan-->
<!-- rca retirement compensation arrangement-->
</odoo>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="rule_parameter_ca_fed_tax_rate" model="hr.rule.parameter">
<field name="name">CA Federal Tax Rate</field>
<field name="code">ca_fed_tax_rate</field>
<field name="country_id" ref="base.ca"/>
</record>
<data noupdate="1">
<record id="rule_parameter_ca_fed_tax_rate_2021_01" model="hr.rule.parameter.value">
<field name="parameter_value">{
'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),
),
}</field>
<field name="rule_parameter_id" ref="rule_parameter_ca_fed_tax_rate"/>
<field name="date_from" eval="datetime(2021, 1, 1).date()"/>
</record>
</data>
<record id="res_partner_ca_fed" model="res.partner">
<field name="name">CA Federal - Canada Revenue Agency - Federal Income Tax</field>
</record>
<!-- Rules -->
<record id="hr_payroll_rule_ee_ca_fit" model="hr.salary.rule">
<field name="sequence" eval="195"/>
<field name="struct_id" ref="hr_ca_payroll_structure"/>
<field name="category_id" ref="hr_payroll_category_basic_ca_salary"/>
<field name="name">EE: CA Federal Income Tax</field>
<field name="code">EE_CA_FIT</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = ca_fit_federal_income_tax_withholding(payslip)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = ca_fit_federal_income_tax_withholding(payslip)</field>
<field name="partner_id" ref="res_partner_ca_fed"/>
<field name="appears_on_payslip" eval="True"/>
</record>
</odoo>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- <record id="rule_parameter_ca_cpp" model="hr.rule.parameter">-->
<!-- <field name="name">CA Canada Pension Plan</field>-->
<!-- <field name="code">ca_cpp</field>-->
<!-- <field name="country_id" ref="base.ca"/>-->
<!-- </record>-->
<!-- <data noupdate="1">-->
<!-- <record id="rule_parameter_ca_cpp_2021_01" model="hr.rule.parameter.value">-->
<!-- <field name="parameter_value">{-->
<!-- '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),-->
<!-- ),-->
<!-- }</field>-->
<!-- <field name="rule_parameter_id" ref="rule_parameter_ca_cpp"/>-->
<!-- <field name="date_from" eval="datetime(2021, 1, 1).date()"/>-->
<!-- </record>-->
<!-- </data>-->
<!-- <record id="res_partner_ca_cpp" model="res.partner">-->
<!-- <field name="name">CA Federal - Canada Revenue Agency - Canada Pension Plan</field>-->
<!-- </record>-->
<!-- &lt;!&ndash; Rules &ndash;&gt;-->
<!-- <record id="hr_payroll_rule_ee_ca_cpp" model="hr.salary.rule">-->
<!-- <field name="sequence" eval="110"/>-->
<!-- <field name="struct_id" ref="hr_ca_payroll_structure"/>-->
<!-- <field name="category_id" ref="hr_payroll_category_ded_ca_rpp"/>-->
<!-- <field name="name">EE: CA Canada Pension Plan</field>-->
<!-- <field name="code">EE_CA_CPP</field>-->
<!-- <field name="condition_select">python</field>-->
<!-- <field name="condition_python">result, _ = ca_cpp_canada_pension_plan_withholding(payslip, categories)</field>-->
<!-- <field name="amount_select">code</field>-->
<!-- <field name="amount_python_compute">result, result_rate = ca_cpp_canada_pension_plan_withholding(payslip, categories)</field>-->
<!-- <field name="partner_id" ref="res_partner_ca_cpp"/>-->
<!-- <field name="appears_on_payslip" eval="True"/>-->
<!-- </record>-->
</odoo>

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="rule_parameter_ca_fed_tax_rate" model="hr.rule.parameter">
<field name="name">CA Federal Tax Rate</field>
<field name="code">ca_fed_tax_rate</field>
<field name="country_id" ref="base.ca"/>
</record>
<data noupdate="1">
<record id="rule_parameter_ca_fed_tax_rate_2021_01" model="hr.rule.parameter.value">
<field name="parameter_value">[
( 0, 0.1500, 0.00),
( 49020, 0.2050, 2696.00),
( 98040, 0.2600, 8088.00),
( 151978, 0.2900, 12648.00),
( 216511, 0.3300, 21308.00),
( 'inf', 0.3300, 21308.00),
]</field>
<field name="rule_parameter_id" ref="rule_parameter_ca_fed_tax_rate"/>
<field name="date_from" eval="datetime(2021, 1, 1).date()"/>
</record>
</data>
<record id="res_partner_ca_fed" model="res.partner">
<field name="name">CA Federal - Canada Revenue Agency - Federal Income Tax</field>
</record>
<!-- Categories -->
<record id="hr_payroll_category_ee_fed_cpp" model="hr.salary.rule.category">
<field name="name">EE: Canada Pension Plan</field>
<field name="code">EE_CA_CPP</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
<record id="hr_payroll_category_ee_fed_ei" model="hr.salary.rule.category">
<field name="name">EE: CA Employment Insurance</field>
<field name="code">EE_CA_EI</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
<record id="hr_payroll_category_ee_fed_fit" model="hr.salary.rule.category">
<field name="name">EE: Federal Income Tax Withholding</field>
<field name="code">EE_CA_FIT</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
<!-- Rules -->
<record id="hr_payroll_rule_ee_fed_cpp" model="hr.salary.rule">
<field name="sequence" eval="181"/>
<field name="struct_id" ref="hr_ca_payroll_structure"/>
<field name="category_id" ref="hr_payroll_category_ee_fed_cpp"/>
<field name="name">EE: Canada Pension Plan</field>
<field name="code">EE_CA_CPP</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = ca_cpp(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = ca_cpp(payslip, categories, worked_days, inputs)</field>
<field name="partner_id" ref="res_partner_ca_fed"/>
<field name="appears_on_payslip" eval="True"/>
</record>
<record id="hr_payroll_rule_ee_fed_ei" model="hr.salary.rule">
<field name="sequence" eval="186"/>
<field name="struct_id" ref="hr_ca_payroll_structure"/>
<field name="category_id" ref="hr_payroll_category_ee_fed_ei"/>
<field name="name">EE: CA Employment Insurance</field>
<field name="code">EE_CA_EI</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = ca_ei(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = ca_ei(payslip, categories, worked_days, inputs)</field>
<field name="partner_id" ref="res_partner_ca_fed"/>
<field name="appears_on_payslip" eval="True"/>
</record>
<record id="hr_payroll_rule_ee_fed_fit" model="hr.salary.rule">
<field name="sequence" eval="195"/>
<field name="struct_id" ref="hr_ca_payroll_structure"/>
<field name="category_id" ref="hr_payroll_category_ee_fed_fit"/>
<field name="name">EE: CA Federal Income Tax</field>
<field name="code">EE_CA_FIT</field>
<field name="condition_select">python</field>
<field name="condition_python">result, _ = ca_fit_federal_income_tax_withholding(payslip, categories, worked_days, inputs)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result, result_rate = ca_fit_federal_income_tax_withholding(payslip, categories, worked_days, inputs)</field>
<field name="partner_id" ref="res_partner_ca_fed"/>
<field name="appears_on_payslip" eval="True"/>
</record>
</odoo>

View File

@@ -0,0 +1,6 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import ca_payroll_config
from . import hr_contract
from . import hr_payslip
from .federal import ca_fit

View File

@@ -0,0 +1,81 @@
# 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?',
# )
# fed_fit_exempt = fields.Boolean()
# td1_fit_additional = fields.Float()
# td1_fit_tc = fields.Float()
# fed_td1_1_basic_personal_amount = fields.Float()
# fed_td1_2_caregiver_amount = fields.Float()
# fed_td1_3_age_amount = fields.Float()
# fed_td1_4_pension_income_amount = fields.Float()
# fed_td1_5_tuition = fields.Float()
# fed_td1_6_disability_amount = fields.Float()
# fed_td1_7_spouse_amount = fields.Float()
# fed_td1_8_dependant_amount = fields.Float()
fed_td1_total_claim_amount = fields.Float()
fed_td1_deduction_prescribed_zone = fields.Float()
fed_td1_additional = fields.Float()
is_cpp_exempt = fields.Boolean()
is_ei_exempt = fields.Boolean()

View File

@@ -0,0 +1,26 @@
# # Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
#
# 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

View File

@@ -0,0 +1 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.

View File

@@ -0,0 +1,118 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
def ca_cpp(payslip, categories, worked_days, inputs):
if payslip.contract_id.ca_payroll_config_value('is_cpp_exempt'):
return 0.0, 0.0
return 0.0, 0.0
# from odoo import fields
# from datetime import datetime, timedelta
# import logging
#
#
# _logger = logging.getLogger("__name__")
#
# def ca_cpp_canada_pension_plan_withholding(payslip, categories):
# #K2 = [(0.15 × ((0.0545 × ((S1 × PI) + B1 $3,500)*, maximum $3,166.45)))) + (0.15 × ((0.0158 × ((S1 × IE) + B1), maximum $889.54))]
#
# payperiods_s1 = _compute_payperiod_ratio_s1(payslip)
# pensionable_income_pi = _compute_pensionable_income_pi(payslip, categories)
# return 0.0, 0.0
#
# def _compute_payperiod_ratio_s1(payslip):
# wage_type = payslip.wage_type
# pay_periods = payslip.dict.PAY_PERIODS_IN_YEAR[wage_type]
# if wage_type == 'annually':
# return 1
# elif wage_type == 'semi_annually':
# if payslip.date_to.month < 7:
# return 1/pay_periods
# else:
# return 2/pay_periods
# elif wage_type == 'quarterly':
# quarters = {
# 1:1,
# 2:1,
# 3:1,
# 4:2,
# 5:2,
# 6:2,
# 7:3,
# 8:3,
# 9:3,
# 10:4,
# 11:4,
# 12:4,
# }
# quarter = quarters[payslip.date_to.month]
# return quarter/pay_periods
# elif wage_type == 'bi-monthly':
# bi_monthly_int = {
# 1:1,
# 2:1,
# 3:2,
# 4:2,
# 5:3,
# 6:3,
# 7:4,
# 8:4,
# 9:5,
# 10:5,
# 11:6,
# 12:6,
# }
# bi_monthly = bi_monthly_int[payslip.date_to.month]
# return bi_monthly/pay_periods
# elif wage_type == 'monthly':
# return payslip.date_to.month/pay_periods
# elif wage_type == 'semi-monthly':
# pay_period = payslip.date_to.month * 2
# if payslip.date_to.day <= 15:
# return pay_period/pay_periods
# else:
# pay_period += 1
# return pay_period/pay_periods
# elif wage_type == 'bi-weekly':
# week_num = payslip.date_to.isocalendar()[1]
# if week_num == 53:
# return 1
# else:
# return week_num/pay_periods
# elif wage_type == 'weekly':
# return payslip.date_to.isocalendar()[1]/pay_periods
# elif wage_type == 'daily':
# day_of_year = payslip.date_to.timetuple().tm_yday
# return day_of_year/pay_periods
# else:
# raise Exception(f'Payslip does not have a valid wage_type. The wagetype presented is "{wage_type}".')
#
# def _compute_pensionable_income_of_slip(slip):
# pensionable_income = 0.0
# for line in slip.line_ids:
# if line.category_id.code == 'BASIC':
# pensionable_income += line.amount
# return pensionable_income
#
# def _compute_pensionable_income_year_to_date_piytd(payslip, categories):
# employee_payslips = payslip.dict.env['hr.payslip'].search([
# ('employee_id', '=', payslip.dict.employee_id.id),
# ('id', '!=', payslip.dict.id),
# ])
# piytd = 0.0
# for slip in employee_payslips:
# piytd += _compute_pensionable_income_of_slip(slip)
# return piytd
#
# def _compute_pensionable_income_pi(payslip, categories):
# """
# PI = Pensionable income for the pay period, or the gross income plus any taxable benefits for the pay period, plus PIYTD
# """
# pensionable_income_year_to_date_piytd = _compute_pensionable_income_year_to_date_piytd(payslip, categories)
# pensionable_income_for_current_payslip = _compute_pensionable_income_of_slip(payslip)
# return pensionable_income_year_to_date_piytd + pensionable_income_for_current_payslip
#

View File

@@ -0,0 +1,11 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
def ca_ei(payslip, categories, worked_days, inputs):
if payslip.contract_id.ca_payroll_config_value('is_ei_exempt'):
return 0.0, 0.0
IE = categories.GROSS # TODO how to adjust
year = payslip.dict.get_year()
D1 = -payslip.sum_category('EE_CA_EI', str(year) + '-01-01', str(year + 1) + '-01-01')
EI = round(min(889.54 - D1, 0.0158 * IE), 2)
return -EI, 100.0

View File

@@ -0,0 +1,70 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
def _compute_annual_taxable_income(payslip, categories):
"""
A = Annual taxable income
= [P × (I F F2 U1 )] HD F1
"""
P = payslip.dict.get_pay_periods_in_year()
I = categories.GROSS \
- categories.ALW_FIT_EXEMPT \
+ categories.DED_FIT_EXEMPT
F = 0.0 # Payroll deductions for RPP or RRSP ...
F2 = 0.0
U1 = 0.0 # Union Dues
HD = 0.0 # Annual deduction for living in a prescribed zone Form TD1
F1 = 0.0 # Annual deductions such as child care and authorized
A = (P * (I - F - F2 - U1)) - HD - F1
return A
def ca_fit_federal_income_tax_withholding(payslip, categories, worked_days, inputs):
L = payslip.contract_id.ca_payroll_config_value('fed_td1_additional')
A = _compute_annual_taxable_income(payslip, categories)
# If the result is negative, T = L.
if A <= 0.0 and L:
return -L, 100.0
elif A <= 0.0:
return 0.0, 0.0
TC = payslip.contract_id.ca_payroll_config_value('fed_td1_total_claim_amount')
P = payslip.dict.get_pay_periods_in_year()
"""
T3 = Annual basic federal tax
= (R × A) K K1 K2 K3 K4
If the result is negative, T3 = $0.
"""
rates = payslip.rule_parameter('ca_fed_tax_rate')
for annual_taxable_income, rate, federal_constant in rates:
annual_taxable_income = float(annual_taxable_income)
if A < annual_taxable_income:
break
R, K = rate, federal_constant
K1 = 0.15 * TC
K2 = 0.0
if not payslip.contract_id.ca_payroll_config_value('is_cpp_exempt'):
C = -categories.EE_CA_CPP
K2 += 0.15 * min(P * C, 3166.45) # min because we can only have up to
if not payslip.contract_id.ca_payroll_config_value('is_ei_exempt'):
EI = -categories.EE_CA_EI
K2 += 0.15 * min(P * EI, 889.54)
K3 = 0.0 # medical
CEA = 1257.0 # TODO this is an indexed parameter
K4 = min(0.15 * A, 0.15 * CEA)
T3 = (R * A) - K - K1 - K2 - K3 - K4
LCF = min(750.0, 0.15 * 0.0) # 0.0 => amount deducted or withheld during the year for the acquisition by the employee of approved shares of the capital stock of a prescribed labour-sponsored venture capital corporation
T1 = T3 - LCF
if T1 < 0.0:
T1 = 0.0
T = (T1 / P) + L
if T > 0.0:
T = round(T, 2)
return A, -(T / A * 100.0)
return 0.0, 0.0

View File

@@ -0,0 +1,12 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo import api, fields, models
class HRContract(models.Model):
_inherit = 'hr.contract'
ca_payroll_config_id = fields.Many2one('hr.contract.ca_payroll_config', 'Canada Payroll Forms')
def ca_payroll_config_value(self, name):
return self.ca_payroll_config_id[name]

View File

@@ -0,0 +1,36 @@
# 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
from .federal.ca_cpp import ca_cpp
from .federal.ca_ei import ca_ei
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,
'ca_cpp': ca_cpp,
'ca_ei': ca_ei,
})
return res
def get_pay_periods_in_year(self):
return self.PAY_PERIODS_IN_YEAR.get(self.contract_id.schedule_pay, 0)

View File

@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 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

View File

@@ -0,0 +1,9 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import common
# Tests moved to `l10n_ca_hr_payroll_params`
# common remains for site specific tests
from . import test_ca_fed_2021_1
# from . import test_ca_province_payslip

View File

@@ -0,0 +1,195 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo.addons.hr_payroll_hibou.tests import common
import logging
_logger = logging.getLogger(__name__)
STANDARD_TOTAL_CLAIM_AMOUNT = 13808.0
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
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 _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.ca_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,
}
if 'fed_td1_total_claim_amount' not in kwargs:
kwargs['fed_td1_total_claim_amount'] = STANDARD_TOTAL_CLAIM_AMOUNT
if 'state_id' not in kwargs:
kwargs['state_id'] = self.get_ca_state('AB')
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['ca_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
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
# 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