Initial commit
This commit is contained in:
358
fusion_payroll/data/hr_salary_rules.xml
Normal file
358
fusion_payroll/data/hr_salary_rules.xml
Normal file
@@ -0,0 +1,358 @@
|
||||
<?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 <= 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 <= 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 <= 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 <= 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>
|
||||
Reference in New Issue
Block a user