[IMP] l10n_be_hr_payroll: Add 'Individual account' report
Purpose ======= Add the possibility to generate the 'Compte Individuel' (or 'Individual Account') based on the belgian payslip for a specific year. This report aggregates the payslip lines by month, by quarter and for the entire year. closes odoo/odoo#30487
This commit is contained in:
committed by
Yannick Tivisse
parent
8bc2d85b1b
commit
fcc49c788f
@@ -57,7 +57,7 @@ class HrPayrollStructure(models.Model):
|
||||
all_rules = []
|
||||
for struct in self:
|
||||
all_rules += struct.rule_ids._recursive_search_of_rules()
|
||||
return all_rules
|
||||
return list(set(all_rules))
|
||||
|
||||
@api.multi
|
||||
def _get_parent_structure(self):
|
||||
|
||||
@@ -2,3 +2,5 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import report
|
||||
from . import wizard
|
||||
|
||||
@@ -25,7 +25,11 @@ Belgian Payroll Rules.
|
||||
'views/reports.xml',
|
||||
'views/l10n_be_hr_payroll_view.xml',
|
||||
'data/l10n_be_hr_payroll_data.xml',
|
||||
'data/report_paperformat.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'wizard/l10n_be_individual_account_wizard_views.xml',
|
||||
'report/hr_individual_account_reports.xml',
|
||||
'report/hr_individual_account_templates.xml',
|
||||
],
|
||||
'demo':[
|
||||
'data/l10n_be_hr_payroll_demo.xml'
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="paperformat_individual_account" model="report.paperformat">
|
||||
<field name="name">Individual Account</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="format">custom</field>
|
||||
<field name="page_height">297</field>
|
||||
<field name="page_width">210</field>
|
||||
<field name="orientation">Portrait</field>
|
||||
<field name="margin_top">30</field>
|
||||
<field name="margin_bottom">23</field>
|
||||
<field name="margin_left">5</field>
|
||||
<field name="margin_right">5</field>
|
||||
<field name="header_line" eval="False"/>
|
||||
<field name="header_spacing">20</field>
|
||||
<field name="dpi">90</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import hr_individual_account_reports
|
||||
@@ -0,0 +1,88 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from odoo import api, models, _
|
||||
from odoo.fields import Datetime
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class IndividualAccountReport(models.AbstractModel):
|
||||
_name = 'report.l10n_be_hr_payroll.report_individual_account'
|
||||
_description = 'Individual Account Report'
|
||||
|
||||
def _get_report_data(self, data):
|
||||
year = data['year']
|
||||
employees = self.env['hr.employee'].browse(data['employee_ids'])
|
||||
|
||||
payslips = self.env['hr.payslip'].search([
|
||||
('employee_id', 'in', employees.ids),
|
||||
('state', '=', 'done'),
|
||||
('date_from', '>=', Datetime.now().replace(month=1, day=1, year=year)),
|
||||
('date_from', '<=', Datetime.now().replace(month=12, day=31, year=year)),
|
||||
'|',
|
||||
('struct_id.country_id', '=', False),
|
||||
('struct_id.country_id', '=', self.env.ref('base.be').id),
|
||||
])
|
||||
lines = payslips.mapped('line_ids')
|
||||
payslip_rules = [(rule.code, rule.sequence) for rule in lines.mapped('salary_rule_id')]
|
||||
payslip_rules = sorted(payslip_rules, key=lambda x: x[1])
|
||||
worked_days = payslips.mapped('worked_days_line_ids')
|
||||
|
||||
result = {
|
||||
employee: {
|
||||
'rules': OrderedDict(
|
||||
(rule[0], {
|
||||
'year': {'name': False, 'total': 0},
|
||||
'month': {m: {'name': False, 'total': 0} for m in range(12)},
|
||||
'quarter': {q: {'name': False, 'total': 0} for q in range(4)}
|
||||
}) for rule in payslip_rules),
|
||||
'worked_days': {
|
||||
code: {
|
||||
'year': {'name': False, 'number_of_days': 0, 'number_of_hours': 0},
|
||||
'month': {m: {'name': False, 'number_of_days': 0, 'number_of_hours': 0} for m in range(12)},
|
||||
'quarter': {q: {'name': False, 'number_of_days': 0, 'number_of_hours': 0} for q in range(4)}
|
||||
} for code in worked_days.mapped('code')
|
||||
}
|
||||
} for employee in employees
|
||||
}
|
||||
|
||||
for line in lines:
|
||||
rule = result[line.employee_id]['rules'][line.salary_rule_id.code]
|
||||
month = line.slip_id.date_from.month - 1
|
||||
rule['month'][month]['name'] = line.name
|
||||
rule['month'][month]['total'] += line.total
|
||||
rule['quarter'][(month) // 3]['name'] = line.name
|
||||
rule['quarter'][(month) // 3]['total'] += line.total
|
||||
rule['year']['name'] = line.name
|
||||
rule['year']['total'] += line.total
|
||||
|
||||
rule['month'][month]['total'] = round(rule['month'][month]['total'], 2)
|
||||
rule['quarter'][(month) // 3]['total'] = round(rule['quarter'][(month) // 3]['total'], 2)
|
||||
rule['year']['total'] = round(rule['year']['total'], 2)
|
||||
|
||||
for worked_day in worked_days:
|
||||
work = result[worked_day.payslip_id.employee_id]['worked_days'][worked_day.code]
|
||||
month = worked_day.payslip_id.date_from.month - 1
|
||||
|
||||
work['month'][month]['name'] = worked_day.name
|
||||
work['month'][month]['number_of_days'] += worked_day.number_of_days
|
||||
work['month'][month]['number_of_hours'] += worked_day.number_of_hours
|
||||
work['quarter'][(month) // 3]['name'] = worked_day.name
|
||||
work['quarter'][(month) // 3]['number_of_days'] += worked_day.number_of_days
|
||||
work['quarter'][(month) // 3]['number_of_hours'] += worked_day.number_of_hours
|
||||
work['year']['name'] = worked_day.name
|
||||
work['year']['number_of_days'] += worked_day.number_of_days
|
||||
work['year']['number_of_hours'] += worked_day.number_of_hours
|
||||
|
||||
return {
|
||||
'year': year,
|
||||
'employee_data': result
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _get_report_values(self, docids, data=None):
|
||||
if self.env.user.company_id.country_id != self.env.ref('base.be'):
|
||||
raise UserError(_("You must be logged into a Belgian company to print the individual account."))
|
||||
return {'report_data': self._get_report_data(data)}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<report
|
||||
id="action_report_individual_account"
|
||||
model="hr.employee"
|
||||
string="Individual Account"
|
||||
report_type="qweb-pdf"
|
||||
name="l10n_be_hr_payroll.report_individual_account"
|
||||
file="l10n_be_hr_payroll.report_individual_account"
|
||||
print_report_name="(object._get_report_base_filename())"
|
||||
/>
|
||||
<record id="action_report_individual_account" model="ir.actions.report">
|
||||
<field name="paperformat_id" ref="l10n_be_hr_payroll.paperformat_individual_account"/>
|
||||
<field name="binding_model_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,240 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<template id="report_individual_account">
|
||||
<t t-call="web.html_container">
|
||||
<t t-call="web.external_layout">
|
||||
<t t-foreach="report_data['employee_data']" t-as="employee">
|
||||
<div class="page">
|
||||
<h2>
|
||||
<span>Individual Account Report for year </span><t t-esc="report_data['year']"/>
|
||||
</h2>
|
||||
<br/>
|
||||
<tbody>
|
||||
<table class="table table-sm table-bordered">
|
||||
<tr>
|
||||
<td><strong>Company Information</strong></td>
|
||||
<td>
|
||||
<div><span t-field="employee.company_id.name"/></div>
|
||||
<div><span t-field="employee.company_id.street"/></div>
|
||||
<div><span t-field="employee.company_id.street2" t-if="employee.company_id.street2"/></div>
|
||||
<div>
|
||||
<span t-field="employee.company_id.city"/>
|
||||
<span t-field="employee.company_id.state_id"/>
|
||||
<span t-field="employee.company_id.zip"/>
|
||||
</div>
|
||||
<div><span t-field="employee.company_id.country_id"/></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="table table-sm table-bordered">
|
||||
<tr>
|
||||
<td><strong>Name</strong></td>
|
||||
<td><span t-field="employee.name"/></td>
|
||||
<td><strong>Designation</strong></td>
|
||||
<td><span t-field="employee.job_id"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Address</strong></td>
|
||||
<td colspan="3">
|
||||
<div t-field="employee.address_home_id"
|
||||
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Email</strong></td>
|
||||
<td><span t-field="employee.work_email"/></td>
|
||||
<td><strong>Identification No</strong></td>
|
||||
<td><span t-field="employee.identification_id"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Bank Account</strong></td>
|
||||
<td><span t-field="employee.bank_account_id"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Registration Number</strong></td>
|
||||
<td><span t-field="employee.registration_number"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead><tr>
|
||||
<th class="text-center">Name</th>
|
||||
<th class="text-center">JAN</th>
|
||||
<th class="text-center">FEB</th>
|
||||
<th class="text-center">MARS</th>
|
||||
<th class="text-center">APR</th>
|
||||
<th class="text-center">MAY</th>
|
||||
<th class="text-center">JUNE</th>
|
||||
<th class="text-center">JUL</th>
|
||||
<th class="text-center">AUG</th>
|
||||
<th class="text-center">SEP</th>
|
||||
<th class="text-center">OCT</th>
|
||||
<th class="text-center">NOV</th>
|
||||
<th class="text-center">DEC</th>
|
||||
</tr></thead>
|
||||
<tr>
|
||||
<td><strong>Worked Days</strong></td>
|
||||
<td colspan="12"/>
|
||||
</tr>
|
||||
<t t-foreach="report_data['employee_data'][employee]['worked_days']" t-as="worked_day">
|
||||
<t t-set="value" t-value="report_data['employee_data'][employee]['worked_days'][worked_day]['month']"/>
|
||||
<t t-set="name" t-value="False"/>
|
||||
|
||||
<t t-foreach="[0,1,2,3,4,5,6,7,8,9,10,11]" t-as="c">
|
||||
<t t-if="value[c]['name'] != False">
|
||||
<t t-set="name" t-value="value[c]['name']"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="name != False">
|
||||
<tr>
|
||||
<td t-esc="name"/>
|
||||
<t t-foreach="[0,1,2,3,4,5,6,7,8,9,10,11]" t-as="m">
|
||||
<t t-if="value[m]['number_of_days'] != 0">
|
||||
<td class="text-left"><t t-esc="value[m]['number_of_days']"/></td>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<td class="text-center">/</td>
|
||||
</t>
|
||||
</t>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center"><span>hours</span></td>
|
||||
<t t-foreach="[0,1,2,3,4,5,6,7,8,9,10,11]" t-as="m">
|
||||
<t t-if="value[m]['number_of_days'] != 0">
|
||||
<td class="text-left"><t t-esc="value[m]['number_of_hours']"/></td>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<td class="text-center">/</td>
|
||||
</t>
|
||||
</t>
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
<tr>
|
||||
<td><strong>Salary Computation</strong></td>
|
||||
<td colspan="12"/>
|
||||
</tr>
|
||||
<tr t-foreach="report_data['employee_data'][employee]['rules']" t-as="rule">
|
||||
<t t-set="value" t-value="report_data['employee_data'][employee]['rules'][rule]['month']"/>
|
||||
<t t-set="name" t-value="False"/>
|
||||
|
||||
<t t-foreach="[0,1,2,3,4,5,6,7,8,9,10,11]" t-as="c">
|
||||
<t t-if="value[c]['name'] != False">
|
||||
<t t-set="name" t-value="value[c]['name']"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="name != False">
|
||||
<td t-esc="name"/>
|
||||
<t t-foreach="[0,1,2,3,4,5,6,7,8,9,10,11]" t-as="m">
|
||||
<t t-if="value[m]['total'] != 0">
|
||||
<td class="text-left"><t t-esc="value[m]['total']"/></td>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<td class="text-center">/</td>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="page-break-before:always;"></p>
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead><tr>
|
||||
<th class="text-center">Name</th>
|
||||
<th class="text-center">Quarter 1</th>
|
||||
<th class="text-center">Quarter 2</th>
|
||||
<th class="text-center">Quarter 3</th>
|
||||
<th class="text-center">Quarter 4</th>
|
||||
<th class="text-center">Total Year</th>
|
||||
</tr></thead>
|
||||
<tr>
|
||||
<td><strong>Worked Days</strong></td>
|
||||
<td colspan="5"/>
|
||||
</tr>
|
||||
<t t-foreach="report_data['employee_data'][employee]['worked_days']" t-as="worked_day">
|
||||
<t t-set="value_q" t-value="report_data['employee_data'][employee]['worked_days'][worked_day]['quarter']"/>
|
||||
<t t-set="value_y" t-value="report_data['employee_data'][employee]['worked_days'][worked_day]['year']"/>
|
||||
<t t-set="name_t" t-value="False"/>
|
||||
<t t-foreach="[0,1,2,3]" t-as="c">
|
||||
<t t-if="value_q[c]['name'] != False">
|
||||
<t t-set="name_t" t-value="value_q[c]['name']"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="name_t != False">
|
||||
<tr>
|
||||
<td t-esc="name_t"/>
|
||||
<t t-foreach="[0,1,2,3]" t-as="q">
|
||||
<t t-if="value_q[q]['number_of_days'] != 0">
|
||||
<td class="text-left"><t t-esc="value_q[q]['number_of_days']"/></td>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<td class="text-center">/</td>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="value_y['number_of_days'] != 0">
|
||||
<td class="text-left"><t t-esc="value_y['number_of_days']"/></td>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<td class="text-center">/</td>
|
||||
</t>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center"><span>hours</span></td>
|
||||
<t t-foreach="[0,1,2,3]" t-as="q">
|
||||
<t t-if="value_q[q]['number_of_days'] != 0">
|
||||
<td class="text-left"><t t-esc="value_q[q]['number_of_hours']"/></td>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<td class="text-center">/</td>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="value_y['number_of_hours'] != 0">
|
||||
<td class="text-left"><t t-esc="value_y['number_of_hours']"/></td>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<td class="text-center">/</td>
|
||||
</t>
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<tr>
|
||||
<td><strong>Salary Computation</strong></td>
|
||||
<td colspan="5"/>
|
||||
</tr>
|
||||
<tr t-foreach="report_data['employee_data'][employee]['rules']" t-as="rule">
|
||||
<t t-set="value_q" t-value="report_data['employee_data'][employee]['rules'][rule]['quarter']"/>
|
||||
<t t-set="value_y" t-value="report_data['employee_data'][employee]['rules'][rule]['year']"/>
|
||||
<t t-set="name_t" t-value="False"/>
|
||||
<t t-foreach="[0,1,2,3]" t-as="c">
|
||||
<t t-if="value_q[c]['name'] != False">
|
||||
<t t-set="name_t" t-value="value_q[c]['name']"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="name_t != False">
|
||||
<td t-esc="name_t"/>
|
||||
<t t-foreach="[0,1,2,3]" t-as="q">
|
||||
<t t-if="value_q[q]['total'] != 0">
|
||||
<td class="text-left"><t t-esc="value_q[q]['total']"/></td>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<td class="text-center">/</td>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="value_y['total'] != 0">
|
||||
<td class="text-left"><t t-esc="value_y['total']"/></td>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<td class="text-center">/</td>
|
||||
</t>
|
||||
</t>
|
||||
</tr>
|
||||
</table>
|
||||
</tbody>
|
||||
</div>
|
||||
<p style="page-break-before:always;"></p>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import l10n_be_individual_account_wizard
|
||||
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import datetime
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class L10nBeIndividualAccountWizard(models.TransientModel):
|
||||
_name = 'l10n_be.individual.account.wizard'
|
||||
_description = 'HR Individual Account Report By Employee'
|
||||
|
||||
def _get_selection(self):
|
||||
current_year = datetime.datetime.now().year
|
||||
return [(str(i), i) for i in range(1990, current_year + 1)]
|
||||
|
||||
year = fields.Selection(
|
||||
selection='_get_selection', string='Year', required=True,
|
||||
default=lambda x: str(datetime.datetime.now().year - 1))
|
||||
|
||||
def print_report(self):
|
||||
self.ensure_one()
|
||||
active_ids = self.env.context.get('active_ids', [])
|
||||
data = {
|
||||
'employee_ids': active_ids,
|
||||
'year': int(self.year)
|
||||
}
|
||||
return self.env.ref('l10n_be_hr_payroll.action_report_individual_account').report_action(active_ids, data=data)
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="l10n_be_individual_account_wizard_view_form" model="ir.ui.view">
|
||||
<field name="name">l10n_be.individual.account.wizard.form</field>
|
||||
<field name="model">l10n_be.individual.account.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Individual Accounts">
|
||||
<group>
|
||||
<group>
|
||||
<field name="year"/>
|
||||
</group>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="print_report" string="Print" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="l10n_be_individual_account_wizard_action" model="ir.actions.act_window">
|
||||
<field name="name">Individual Accounts</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">l10n_be.individual.account.wizard</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="binding_model_id" ref="hr.model_hr_employee"/>
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -39,7 +39,7 @@
|
||||
<field name="payslip_id" ref="hr_payslip_admin"/>
|
||||
</record>
|
||||
<record id="hr_line_admin2" model="hr.payslip.worked_days">
|
||||
<field name="name">Sick keave</field>
|
||||
<field name="name">Sick leave</field>
|
||||
<field name="code">LEAVE200</field>
|
||||
<field name="number_of_days">2</field>
|
||||
<field name="number_of_hours">16</field>
|
||||
|
||||
Reference in New Issue
Block a user