[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:
sed-odoo
2019-02-19 16:35:17 +00:00
committed by Yannick Tivisse
parent 8bc2d85b1b
commit fcc49c788f
12 changed files with 439 additions and 2 deletions
+1 -1
View File
@@ -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
View File
@@ -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>