Hello,
I am trying to calculate salary based on production quantity in Odoo Payroll, but I am getting the following error in the payslip:
Wrong python code defined for: - Employee: Mitchell Admin - Contract: Contract For Mitchell Admin - Payslip: Salary Slip - Mitchell Admin - November 2025 - Salary rule: Production Pay (PROD_PAY) - Error: AttributeError("'dict' object has no attribute 'PROD'") while evaluating result = 0 qty = inputs.PROD.amount if inputs.PROD else 0 rate = contract.production_rate or 0.0 result = qty * rate
The input type with code PROD is already created under Payroll → Configuration → Salary Inputs.
But inside the salary rule Python code, inputs.PROD always throws the error:
'dict' object has no attribute 'PROD'
It seems like Odoo cannot find my input in the payslip, even though the input type exists.
What I need to know
Why does the salary rule Python code treat inputs as a dict with no attributes?
How should I correctly access input values in Odoo 18 payroll rules?
Do I need to add inputs somewhere else (contract? worked days? payslip lines?) before Odoo can detect them?
Thanks in advance!
@api.onchange('employee_id', 'contract_id', 'date_from', 'date_to')
def _onchange_employee_fill_inputs(self):
for slip in self:
inputs = []
# The module name is assumed to be 'production_payroll'
# The XML record IDs are: 'input_type_prod', 'input_type_kpi', etc.
# --- Production Quantity ---
prod_qty = getattr(slip.employee_id, 'monthly_production', 0.0)
prod_type = self.env.ref('production_payroll.input_type_prod', raise_if_not_found=False)
if prod_type:
inputs.append((0, 0, {
'name': 'Production Quantity',
'code': 'PROD',
'amount': prod_qty,
'input_type_id': prod_type.id,
}))
# --- KPI Points ---
kpi_points = getattr(slip.employee_id, 'kpi_points', 0.0)
kpi_type = self.env.ref('production_payroll.input_type_kpi', raise_if_not_found=False)
if kpi_type:
inputs.append((0, 0, {
'name': 'KPI Points',
'code': 'KPI',
'amount': kpi_points,
'input_type_id': kpi_type.id,
}))
# --- Penalty Points ---
penalty_points = getattr(slip.employee_id, 'penalty_points', 0.0)
penalty_type = self.env.ref('production_payroll.input_type_pen', raise_if_not_found=False)
if penalty_type:
inputs.append((0, 0, {
'name': 'Penalty Points',
'code': 'PEN',
'amount': penalty_points,
'input_type_id': penalty_type.id,
}))
# --- Overtime Hours ---
overtime_hours = getattr(slip.employee_id, 'overtime_hours', 0.0)
ot_type = self.env.ref('production_payroll.input_type_ot', raise_if_not_found=False)
if ot_type:
inputs.append((0, 0, {
'name': 'Overtime Hours',
'code': 'OT',
'amount': overtime_hours,
'input_type_id': ot_type.id,
}))
# --- Attendance Days ---
days_present = getattr(slip.employee_id, 'days_present', 0.0)
att_type = self.env.ref('production_payroll.input_type_att', raise_if_not_found=False)
if att_type:
inputs.append((0, 0, {
'name': 'Days Present',
'code': 'ATT',
'amount': days_present,
'input_type_id': att_type.id,
}))
slip.input_line_ids = inputs<odoo> <!-- Create a structure type --> <record id="structure_type_factory" model="hr.payroll.structure.type"> <field name="name">Factory Worker</field> </record> <!-- Create a salary structure attached to the type --> <record id="structure_factory_worker" model="hr.payroll.structure"> <field name="name">Factory Worker Structure</field> <field name="type_id" ref="structure_type_factory"/> <field name="note">Structure for production workers</field> </record> </odoo>
<odoo> <!-- BASIC salary rule (use contract wage) --> <record id="rule_basic" model="hr.salary.rule"> <field name="name">Basic Salary</field> <field name="sequence">1</field> <field name="code">BASIC</field> <field name="condition_select">python</field> <field name="condition_python">result = contract.wage and contract.wage > 0</field> <field name="amount_select">code</field> <field name="amount_python_compute">result = contract.wage</field> <field name="struct_id" ref="structure_factory_worker"/> <field name="category_id" ref="hr_payroll.GROSS"/> </record> <!-- Production pay rule --> <record id="rule_production" model="hr.salary.rule"> <field name="name">Production Pay</field> <field name="sequence">2</field> <field name="code">PROD_PAY</field> <field name="condition_select">python</field> <field name="condition_python">result = True</field> <field name="amount_select">code</field> <field name="amount_python_compute"> result = 0 qty = inputs.PROD.amount if inputs.PROD else 0 rate = contract.production_rate or 0.0 result = qty * rate </field> <field name="struct_id" ref="structure_factory_worker"/> <field name="category_id" ref="hr_payroll.GROSS"/> </record> <!-- KPI bonus --> <record id="rule_kpi" model="hr.salary.rule"> <field name="name">KPI Bonus</field> <field name="sequence">3</field> <field name="code">KPI_BONUS</field> <field name="condition_select">python</field> <field name="condition_python">result = inputs.KPI and inputs.KPI.amount > 0</field> <field name="amount_select">code</field> <field name="amount_python_compute"> result = 0 kpi = inputs.KPI.amount if inputs.KPI else 0 base = contract.kpi_base_amount or 0.0 result = (kpi / 100.0) * base </field> <field name="struct_id" ref="structure_factory_worker"/> <field name="category_id" ref="hr_payroll.GROSS"/> </record> <!-- Penalty deduction --> <record id="rule_penalty" model="hr.salary.rule"> <field name="name">Penalty</field> <field name="sequence">4</field> <field name="code">PENALTY</field> <field name="condition_select">python</field> <field name="condition_python">result = inputs.PEN and inputs.PEN.amount > 0</field> <field name="amount_select">code</field> <field name="amount_python_compute"> result = 0 pen = inputs.PEN.amount if inputs.PEN else 0 rate = contract.penalty_rate or 0.0 # penalty is a deduction -> return negative value result = -1 * (pen * rate) </field> <field name="struct_id" ref="structure_factory_worker"/> <field name="category_id" ref="hr_payroll.NET"/> </record> <!-- Overtime rule --> <record id="rule_overtime" model="hr.salary.rule"> <field name="name">Overtime Pay</field> <field name="sequence">5</field> <field name="code">OT_PAY</field> <field name="condition_select">python</field> <field name="condition_python">result = inputs.OT and inputs.OT.amount > 0</field> <field name="amount_select">code</field> <field name="amount_python_compute"> result = 0 ot = inputs.OT.amount if inputs.OT else 0 hourly = contract.hourly_rate or 0.0 mult = contract.overtime_multiplier or 1.5 result = ot * hourly * mult </field> <field name="struct_id" ref="structure_factory_worker"/> <field name="category_id" ref="hr_payroll.GROSS"/> </record> <!-- Attendance bonus --> <record id="rule_attendance" model="hr.salary.rule"> <field name="name">Attendance Bonus</field> <field name="sequence">6</field> <field name="code">ATT_BONUS</field> <field name="condition_select">python</field> <field name="condition_python">result = inputs.ATT and inputs.ATT.amount > 0</field> <field name="amount_select">code</field> <field name="amount_python_compute"> result = 0 days_present = inputs.ATT.amount if inputs.ATT else 0.0 # Calculate actual days in payslip period if available, otherwise default to 30 days_in_period = 30.0 # If payslip dates provided, compute exact days in period (simple approximation) try: if payslip.date_from and payslip.date_to: # +1 to include both endpoints days_in_period = (payslip.date_to - payslip.date_from).days + 1 except: days_in_period = 30.0 base = contract.attendance_base or 0.0 # proportion of base paid depending on presence result = (days_present / float(days_in_period)) * base </field> <field name="struct_id" ref="structure_factory_worker"/> <field name="category_id" ref="hr_payroll.GROSS"/> </record> </odoo>
<odoo> <record id="input_type_prod" model="hr.payslip.input.type"> <field name="code">PROD</field> <field name="name">Production Quantity</field> </record> <record id="input_type_kpi" model="hr.payslip.input.type"> <field name="code">KPI</field> <field name="name">KPI Points</field> </record> <record id="input_type_pen" model="hr.payslip.input.type"> <field name="code">PEN</field> <field name="name">Penalty Points</field> </record> <record id="input_type_ot" model="hr.payslip.input.type"> <field name="code">OT</field> <field name="name">Overtime Hours</field> </record> <record id="input_type_att" model="hr.payslip.input.type"> <field name="code">ATT</field> <field name="name">Attendance Days Present</field> </record> </odoo>
I hope you are doing well,
The error occurs because inputs is a BrowsableObject that only contains input lines that already exist on the payslip. If no input with code PROD exists, accessing inputs.PROD fails.
Hope this information helps you.
Thanks & Regards,
Kunjan Patel
Please revert my comment back to an answer – it was converted by mistake.