Files
Odoo-Modules/fusion_payroll/data/hr_salary_rules.xml
2026-02-22 01:22:18 -05:00

359 lines
15 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- ============================================================ -->
<!-- OVERTIME PAY - 1.5x Regular Rate -->
<!-- ============================================================ -->
<record id="hr_overtime_pay" model="hr.salary.rule">
<field name="name">Overtime Pay</field>
<field name="code">OT_PAY</field>
<field name="sequence">101</field>
<field name="struct_id" ref="hr_payroll_structure_canada"/>
<field name="category_id" ref="hr_payroll.ALW"/>
<field name="condition_select">python</field>
<field name="condition_python">result = 'OT_HOURS' in inputs</field>
<field name="amount_select">code</field>
<field name="appears_on_payslip">True</field>
<field name="amount_python_compute">
# Overtime Pay - 1.5x regular hourly rate
OT_MULTIPLIER = 1.5
ot_hours = inputs['OT_HOURS'].amount if 'OT_HOURS' in inputs else 0
# Calculate hourly rate from paid amount (assuming semi-monthly ~86.67 hours)
hourly_rate = payslip.paid_amount / 86.67 if payslip.paid_amount else 0
result = ot_hours * hourly_rate * OT_MULTIPLIER
</field>
</record>
<!-- ============================================================ -->
<!-- STAT HOLIDAY PAY -->
<!-- ============================================================ -->
<record id="hr_stat_holiday_pay" model="hr.salary.rule">
<field name="name">Stat Holiday Pay</field>
<field name="code">STAT_PAY</field>
<field name="sequence">102</field>
<field name="struct_id" ref="hr_payroll_structure_canada"/>
<field name="category_id" ref="hr_payroll.ALW"/>
<field name="condition_select">python</field>
<field name="condition_python">result = 'STAT_HOURS' in inputs</field>
<field name="amount_select">code</field>
<field name="appears_on_payslip">True</field>
<field name="amount_python_compute">
# Stat Holiday Pay
stat_hours = inputs['STAT_HOURS'].amount if 'STAT_HOURS' in inputs else 0
hourly_rate = payslip.paid_amount / 86.67 if payslip.paid_amount else 0
result = stat_hours * hourly_rate
</field>
</record>
<!-- ============================================================ -->
<!-- BONUS PAY -->
<!-- ============================================================ -->
<record id="hr_bonus_pay" model="hr.salary.rule">
<field name="name">Bonus</field>
<field name="code">BONUS_PAY</field>
<field name="sequence">103</field>
<field name="struct_id" ref="hr_payroll_structure_canada"/>
<field name="category_id" ref="hr_payroll.ALW"/>
<field name="condition_select">python</field>
<field name="condition_python">result = 'BONUS' in inputs</field>
<field name="amount_select">code</field>
<field name="appears_on_payslip">True</field>
<field name="amount_python_compute">
# Bonus Pay - direct amount from input
result = inputs['BONUS'].amount if 'BONUS' in inputs else 0
</field>
</record>
<!-- ============================================================ -->
<!-- CPP EMPLOYEE - Canada Pension Plan (Employee Portion) -->
<!-- Uses rule parameters for rates and limits -->
<!-- ============================================================ -->
<record id="hr_cpp_employee" model="hr.salary.rule">
<field name="name">CPP Employee</field>
<field name="code">CPP_EE</field>
<field name="sequence">150</field>
<field name="struct_id" ref="hr_payroll_structure_canada"/>
<field name="category_id" ref="hr_payroll.DED"/>
<field name="condition_select">none</field>
<field name="amount_select">code</field>
<field name="appears_on_payslip">True</field>
<field name="amount_python_compute">
# CPP Employee Deduction - Using Rule Parameters
CPP_RATE = payslip._rule_parameter('ca_cpp_rate')
CPP_EXEMPTION = payslip._rule_parameter('ca_cpp_exemption')
CPP_MAX = payslip._rule_parameter('ca_cpp_max')
PAY_PERIODS = 24 # Semi-monthly
exemption_per_period = CPP_EXEMPTION / PAY_PERIODS
gross = categories['GROSS']
pensionable = max(0, gross - exemption_per_period)
cpp = pensionable * CPP_RATE
# YTD check - get year start
from datetime import date
year_start = date(payslip.date_from.year, 1, 1)
ytd = payslip._sum('CPP_EE', year_start, payslip.date_from) or 0
remaining = CPP_MAX + ytd # ytd is negative
if remaining &lt;= 0:
result = 0
elif cpp > remaining:
result = -remaining
else:
result = -cpp
</field>
</record>
<!-- ============================================================ -->
<!-- CPP EMPLOYER - 1:1 Match -->
<!-- ============================================================ -->
<record id="hr_cpp_employer" model="hr.salary.rule">
<field name="name">CPP Employer</field>
<field name="code">CPP_ER</field>
<field name="sequence">151</field>
<field name="struct_id" ref="hr_payroll_structure_canada"/>
<field name="category_id" ref="hr_payroll.COMP"/>
<field name="condition_select">none</field>
<field name="amount_select">code</field>
<field name="appears_on_payslip">True</field>
<field name="amount_python_compute">
# CPP Employer - 1:1 match with employee
result = abs(CPP_EE) if CPP_EE else 0
</field>
</record>
<!-- ============================================================ -->
<!-- CPP2 EMPLOYEE - Second Canada Pension Plan -->
<!-- Uses rule parameters for rates and limits -->
<!-- ============================================================ -->
<record id="hr_cpp2_employee" model="hr.salary.rule">
<field name="name">CPP2 Employee</field>
<field name="code">CPP2_EE</field>
<field name="sequence">152</field>
<field name="struct_id" ref="hr_payroll_structure_canada"/>
<field name="category_id" ref="hr_payroll.DED"/>
<field name="condition_select">none</field>
<field name="amount_select">code</field>
<field name="appears_on_payslip">True</field>
<field name="amount_python_compute">
# CPP2 (Second CPP) - Using Rule Parameters
CPP2_RATE = payslip._rule_parameter('ca_cpp2_rate')
YMPE = payslip._rule_parameter('ca_ympe')
YAMPE = payslip._rule_parameter('ca_yampe')
CPP2_MAX = payslip._rule_parameter('ca_cpp2_max')
PAY_PERIODS = 24
gross = categories['GROSS']
annual_equiv = gross * PAY_PERIODS
result = 0
# CPP2 only on earnings between YMPE and YAMPE
if annual_equiv > YMPE:
cpp2_base = min(annual_equiv, YAMPE) - YMPE
cpp2_per_period = (cpp2_base * CPP2_RATE) / PAY_PERIODS
# YTD check
from datetime import date
year_start = date(payslip.date_from.year, 1, 1)
ytd = abs(payslip._sum('CPP2_EE', year_start, payslip.date_from) or 0)
remaining = CPP2_MAX - ytd
if remaining > 0:
result = -min(cpp2_per_period, remaining)
</field>
</record>
<!-- ============================================================ -->
<!-- CPP2 EMPLOYER - 1:1 Match -->
<!-- ============================================================ -->
<record id="hr_cpp2_employer" model="hr.salary.rule">
<field name="name">CPP2 Employer</field>
<field name="code">CPP2_ER</field>
<field name="sequence">153</field>
<field name="struct_id" ref="hr_payroll_structure_canada"/>
<field name="category_id" ref="hr_payroll.COMP"/>
<field name="condition_select">none</field>
<field name="amount_select">code</field>
<field name="appears_on_payslip">True</field>
<field name="amount_python_compute">
# CPP2 Employer - 1:1 match
result = abs(CPP2_EE) if CPP2_EE else 0
</field>
</record>
<!-- ============================================================ -->
<!-- EI EMPLOYEE - Employment Insurance -->
<!-- Uses rule parameters for rates and limits -->
<!-- ============================================================ -->
<record id="hr_ei_employee" model="hr.salary.rule">
<field name="name">EI Employee</field>
<field name="code">EI_EE</field>
<field name="sequence">154</field>
<field name="struct_id" ref="hr_payroll_structure_canada"/>
<field name="category_id" ref="hr_payroll.DED"/>
<field name="condition_select">none</field>
<field name="amount_select">code</field>
<field name="appears_on_payslip">True</field>
<field name="amount_python_compute">
# EI Employee - Using Rule Parameters
EI_RATE = payslip._rule_parameter('ca_ei_rate')
EI_MAX = payslip._rule_parameter('ca_ei_max')
gross = categories['GROSS']
ei = gross * EI_RATE
# YTD check
from datetime import date
year_start = date(payslip.date_from.year, 1, 1)
ytd = abs(payslip._sum('EI_EE', year_start, payslip.date_from) or 0)
remaining = EI_MAX - ytd
if remaining &lt;= 0:
result = 0
elif ei > remaining:
result = -remaining
else:
result = -ei
</field>
</record>
<!-- ============================================================ -->
<!-- EI EMPLOYER - 1.4x Employee Premium -->
<!-- ============================================================ -->
<record id="hr_ei_employer" model="hr.salary.rule">
<field name="name">EI Employer</field>
<field name="code">EI_ER</field>
<field name="sequence">155</field>
<field name="struct_id" ref="hr_payroll_structure_canada"/>
<field name="category_id" ref="hr_payroll.COMP"/>
<field name="condition_select">none</field>
<field name="amount_select">code</field>
<field name="appears_on_payslip">True</field>
<field name="amount_python_compute">
# EI Employer - Using Rule Parameter for multiplier
EI_ER_MULT = payslip._rule_parameter('ca_ei_employer_mult')
result = abs(EI_EE) * EI_ER_MULT if EI_EE else 0
</field>
</record>
<!-- ============================================================ -->
<!-- FEDERAL INCOME TAX -->
<!-- Uses rule parameters for brackets and credits -->
<!-- ============================================================ -->
<record id="hr_fed_tax" model="hr.salary.rule">
<field name="name">Federal Income Tax</field>
<field name="code">FED_TAX</field>
<field name="sequence">160</field>
<field name="struct_id" ref="hr_payroll_structure_canada"/>
<field name="category_id" ref="hr_payroll.DED"/>
<field name="condition_select">none</field>
<field name="amount_select">code</field>
<field name="appears_on_payslip">True</field>
<field name="amount_python_compute">
# Federal Income Tax - Using Rule Parameters
PAY_PERIODS = 24
brackets = payslip._rule_parameter('ca_fed_brackets')
BPA = payslip._rule_parameter('ca_fed_bpa')
CEA = payslip._rule_parameter('ca_fed_cea')
CPP_MAX = payslip._rule_parameter('ca_cpp_max')
EI_MAX = payslip._rule_parameter('ca_ei_max')
gross = categories['GROSS']
annual = gross * PAY_PERIODS
# Calculate tax using brackets
tax = 0
prev_threshold = 0
for threshold, rate in brackets:
if annual &lt;= threshold:
tax += (annual - prev_threshold) * rate
break
else:
tax += (threshold - prev_threshold) * rate
prev_threshold = threshold
# Basic personal amount credit
tax_credit = BPA * brackets[0][1] # Lowest rate
# CPP/EI credits
cpp_credit = min(abs(CPP_EE) * PAY_PERIODS if CPP_EE else 0, CPP_MAX) * brackets[0][1]
ei_credit = min(abs(EI_EE) * PAY_PERIODS if EI_EE else 0, EI_MAX) * brackets[0][1]
# Canada Employment Amount credit
cea_credit = CEA * brackets[0][1]
annual_tax = max(0, tax - tax_credit - cpp_credit - ei_credit - cea_credit)
result = -(annual_tax / PAY_PERIODS)
</field>
</record>
<!-- ============================================================ -->
<!-- PROVINCIAL INCOME TAX (ONTARIO) -->
<!-- Uses rule parameters for brackets and credits -->
<!-- ============================================================ -->
<record id="hr_prov_tax" model="hr.salary.rule">
<field name="name">Provincial Income Tax</field>
<field name="code">PROV_TAX</field>
<field name="sequence">161</field>
<field name="struct_id" ref="hr_payroll_structure_canada"/>
<field name="category_id" ref="hr_payroll.DED"/>
<field name="condition_select">none</field>
<field name="amount_select">code</field>
<field name="appears_on_payslip">True</field>
<field name="amount_python_compute">
# Ontario Provincial Tax - Using Rule Parameters
PAY_PERIODS = 24
brackets = payslip._rule_parameter('ca_on_brackets')
BPA_ON = payslip._rule_parameter('ca_on_bpa')
CPP_MAX = payslip._rule_parameter('ca_cpp_max')
EI_MAX = payslip._rule_parameter('ca_ei_max')
gross = categories['GROSS']
annual = gross * PAY_PERIODS
# Calculate tax using brackets
tax = 0
prev_threshold = 0
for threshold, rate in brackets:
if annual &lt;= threshold:
tax += (annual - prev_threshold) * rate
break
else:
tax += (threshold - prev_threshold) * rate
prev_threshold = threshold
# Ontario Basic Personal Amount credit
tax_credit = BPA_ON * brackets[0][1] # Lowest rate
# CPP/EI credits at lowest rate
cpp_credit = min(abs(CPP_EE) * PAY_PERIODS if CPP_EE else 0, CPP_MAX) * brackets[0][1]
ei_credit = min(abs(EI_EE) * PAY_PERIODS if EI_EE else 0, EI_MAX) * brackets[0][1]
annual_tax = max(0, tax - tax_credit - cpp_credit - ei_credit)
result = -(annual_tax / PAY_PERIODS)
</field>
</record>
<!-- ============================================================ -->
<!-- VACATION PAY - 4% of Earnings -->
<!-- ============================================================ -->
<record id="hr_vacation_pay" model="hr.salary.rule">
<field name="name">Vacation Pay</field>
<field name="code">VAC_PAY</field>
<field name="sequence">170</field>
<field name="struct_id" ref="hr_payroll_structure_canada"/>
<field name="category_id" ref="hr_payroll.ALW"/>
<field name="condition_select">none</field>
<field name="amount_select">code</field>
<field name="appears_on_payslip">True</field>
<field name="amount_python_compute">
# Vacation Pay - Using Rule Parameter
VAC_RATE = payslip._rule_parameter('ca_vacation_rate')
result = categories['BASIC'] * VAC_RATE
</field>
</record>
</data>
</odoo>