[IMP] hr_expense: expense report
This pr aim to improve the pdf of the expense report. we have changed the design of the header of the report, the table and also added an annex with the different attachments. For the attachments, when it's a pdf it's simply merge to the actual pdf but when it's an image we use a new template to display the name of the expense link to that image. closes odoo/odoo#133611 Task: 3443413 Signed-off-by: Olivier Colson (oco) <oco@odoo.com>
This commit is contained in:
@@ -66,6 +66,9 @@ This module also uses analytic accounting and is compatible with the invoice on
|
||||
'web.assets_tests': [
|
||||
'hr_expense/static/tests/tours/expense_upload_tours.js',
|
||||
],
|
||||
'web.report_assets_common': [
|
||||
'hr_expense/static/src/scss/hr_expense.scss',
|
||||
],
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
||||
@@ -12,3 +12,4 @@ from . import res_config_settings
|
||||
from . import account_journal_dashboard
|
||||
from . import res_company
|
||||
from . import analytic
|
||||
from . import ir_actions_report
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import io
|
||||
from odoo import models
|
||||
from odoo.tools import pdf
|
||||
from odoo.tools.pdf import OdooPdfFileReader, OdooPdfFileWriter
|
||||
|
||||
|
||||
class IrActionsReport(models.Model):
|
||||
_inherit = 'ir.actions.report'
|
||||
|
||||
def _render_qweb_pdf_prepare_streams(self, report_ref, data, res_ids=None):
|
||||
# OVERRIDE
|
||||
res = super()._render_qweb_pdf_prepare_streams(report_ref, data, res_ids)
|
||||
if not res_ids:
|
||||
return res
|
||||
report = self._get_report(report_ref)
|
||||
if report.report_name == 'hr_expense.report_expense_sheet':
|
||||
expense_sheets = self.env['hr.expense.sheet'].browse(res_ids)
|
||||
for expense_sheet in expense_sheets:
|
||||
# Will contains the expense report
|
||||
stream_list = []
|
||||
stream = res[expense_sheet.id]['stream']
|
||||
stream_list.append(stream)
|
||||
attachments = self.env['ir.attachment'].search([('res_id', 'in', expense_sheet.expense_line_ids.ids), ('res_model', '=', 'hr.expense')])
|
||||
expense_report = OdooPdfFileReader(stream, strict=False)
|
||||
output_pdf = OdooPdfFileWriter()
|
||||
output_pdf.appendPagesFromReader(expense_report)
|
||||
for attachment in attachments:
|
||||
if attachment.mimetype == 'application/pdf':
|
||||
attachment_stream = pdf.to_pdf_stream(attachment)
|
||||
else:
|
||||
# In case the attachment is not a pdf we will create a new PDF from the template "report_expense_sheet_img"
|
||||
# And then append to the stream. By doing so, the attachment is put on a new page with the name of the expense
|
||||
# associated to the attachment
|
||||
data['attachment'] = attachment
|
||||
attachment_prep_stream = self._render_qweb_pdf_prepare_streams('hr_expense.report_expense_sheet_img', data, res_ids=res_ids)
|
||||
attachment_stream = attachment_prep_stream[expense_sheet.id]['stream']
|
||||
attachment_reader = OdooPdfFileReader(attachment_stream, strict=False)
|
||||
output_pdf.appendPagesFromReader(attachment_reader)
|
||||
stream_list.append(attachment_stream)
|
||||
|
||||
new_pdf_stream = io.BytesIO()
|
||||
output_pdf.write(new_pdf_stream)
|
||||
res[expense_sheet.id]['stream'] = new_pdf_stream
|
||||
|
||||
for stream in stream_list:
|
||||
stream.close()
|
||||
return res
|
||||
@@ -4,101 +4,102 @@
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page">
|
||||
<div class="page o_content_pdf">
|
||||
<h2>Expenses Report</h2>
|
||||
|
||||
<div class="row mt32 mb32">
|
||||
<div class="col-2">
|
||||
<strong>Employee:</strong>
|
||||
<p t-field="o.employee_id.name"/>
|
||||
<h3><t t-out="o.name"/></h3>
|
||||
<div class="row o_header">
|
||||
<div class="col-6">
|
||||
<div class="row">
|
||||
<div class="col-3 fw-bold"><span>Employee:</span> </div>
|
||||
<div class="col-9 text-muted"><span t-field="o.employee_id.name"/></div>
|
||||
</div>
|
||||
<div class="row" t-if="o.accounting_date">
|
||||
<div class="col-3 fw-bold"><span>Date:</span></div>
|
||||
<div class="col-9 text-muted"><span t-field="o.accounting_date"/></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<strong>Date:</strong>
|
||||
<p t-field="o.accounting_date"/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<strong>Description:</strong>
|
||||
<p t-field="o.name"/>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<strong>Validated By:</strong>
|
||||
<p t-field="o.user_id"/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<strong>Payment By:</strong>
|
||||
<p t-field="o.payment_mode"/>
|
||||
<div class="col-6">
|
||||
<div class="row" t-if="o.user_id.name">
|
||||
<div class="col-3 fw-bold"><span>Manager:</span></div>
|
||||
<div class="col-9 text-muted"><span t-field="o.user_id"/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3 fw-bold"><span>Paid by:</span></div>
|
||||
<div class="col-9 text-muted"><span t-field="o.payment_mode"/></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-sm">
|
||||
<table class="o_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Name</th>
|
||||
<th class="text-center">Ref.</th>
|
||||
<th>Unit Price</th>
|
||||
<th>Taxes</th>
|
||||
<th class="text-center">Qty</th>
|
||||
<th class="text-end">Price</th>
|
||||
<th t-if="o.is_multiple_currency" class="text-end">Price in Company Currency</th>
|
||||
<th class="text-start">Date</th>
|
||||
<th class="text-start">Name</th>
|
||||
<th class="text-end">Unit Price</th>
|
||||
<th class="text-end">Quantity</th>
|
||||
<th class="text-end">Taxes</th>
|
||||
<t t-set="foreign_currencies" t-value="o.expense_line_ids.currency_id - o.company_currency_id"/>
|
||||
<th t-if="foreign_currencies" class="text-end">Subtotal in currency</th>
|
||||
<th class="text-end">Subtotal</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="o.expense_line_ids" t-as="line">
|
||||
<td><span t-field="line.date"/></td>
|
||||
<td>
|
||||
<span t-field="line.name"/>
|
||||
<span t-field="line.description"/><br/>
|
||||
<td class="text-start"><span t-field="line.date"/></td>
|
||||
<td t-att-class="'text-start' + (' o_overflow' if len(line.name) > 30 else '')">
|
||||
<span t-field="line.name"/><br/>
|
||||
<span>
|
||||
<div t-foreach="line.analytic_distribution" t-as="distribution">
|
||||
<t t-esc="line.env['account.analytic.account'].browse(int(distribution))[0].name"/>: <t t-esc="line.analytic_distribution.get(distribution)"/>
|
||||
<t t-out="line.env['account.analytic.account'].browse(int(distribution))[0].name"/>: <t t-out="line.analytic_distribution.get(distribution)"/>
|
||||
</div>
|
||||
</span>
|
||||
</td>
|
||||
<td style="text-center">
|
||||
<span t-field="line.reference"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-field="line.unit_amount"/>
|
||||
</td>
|
||||
<td>
|
||||
<td class="text-end"><span t-field="line.unit_amount"/></td>
|
||||
<td class="text-end"><span t-field="line.quantity"/></td>
|
||||
<td class="text-end">
|
||||
<t t-foreach="line.tax_ids" t-as="tax">
|
||||
<t t-if="tax.description">
|
||||
<span t-field="tax.description"/>
|
||||
</t>
|
||||
<t t-if="not tax.description">
|
||||
<span t-field="tax.name"/>
|
||||
</t>
|
||||
<t t-if="tax.description"><span t-field="tax.description"/></t>
|
||||
<t t-if="not tax.description"><span t-field="tax.name"/></t>
|
||||
</t>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span t-field="line.quantity"/>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span t-field="line.total_amount"
|
||||
t-options='{"widget": "monetary", "display_currency": line.currency_id}'/>
|
||||
</td>
|
||||
<td t-if="o.is_multiple_currency" class="text-end">
|
||||
<span t-field="line.total_amount_company"/>
|
||||
</td>
|
||||
<td t-if="foreign_currencies" class="text-end"><span t-field="line.total_amount" t-options='{"widget": "monetary", "display_currency": line.currency_id}'/></td>
|
||||
<td class="text-end"><span t-field="line.total_amount_company" t-options='{"widget": "monetary", "display_currency": o.company_currency_id}'/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="row justify-content-end">
|
||||
<div class="row justify-content-end o_total">
|
||||
<div class="col-4">
|
||||
<table class="table table-sm">
|
||||
<tr class="border-black">
|
||||
<td><strong>Total</strong></td>
|
||||
<td class="text-end">
|
||||
<span t-field="o.total_amount"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<table class="o_subtable">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Untaxed Amount</td>
|
||||
<td class="text-end"><span t-field="o.untaxed_amount" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Taxes</td>
|
||||
<td class="text-end"><span t-field="o.total_amount_taxes" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/></td>
|
||||
</tr>
|
||||
<tr class="fw-bold">
|
||||
<td>Total</td>
|
||||
<td class="text-end"><span t-field="o.total_amount" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<p>Certified honest and conform,<br/>(Date and signature).<br/><br/></p>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="report_expense_sheet_img">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<t t-call="web.basic_layout">
|
||||
<div t-if="attachment and attachment.mimetype != 'application/pdf'">
|
||||
<h3> <t t-out="attachment.res_name"/> </h3>
|
||||
<img t-att-src="attachment.image_src" class="o_attachment_pdf"/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
@@ -116,4 +117,11 @@
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
|
||||
<record id="action_report_expense_sheet_img" model="ir.actions.report">
|
||||
<field name="name">Expense Report Image</field>
|
||||
<field name="model">hr.expense.sheet</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">hr_expense.report_expense_sheet_img</field>
|
||||
<field name="report_file">hr_expense.report_expense_sheet_img</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -47,3 +47,83 @@
|
||||
.o_expense_categories td[name="description"] p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/**************
|
||||
PDF EXPORT
|
||||
***************/
|
||||
.o_content_pdf {
|
||||
.o_header {
|
||||
vertical-align: middle;
|
||||
.col-6 {
|
||||
.row:first-child{
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.row:last-child{
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_table {
|
||||
width: 100%;
|
||||
thead, tbody, tfoot, tr, td, th {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/*****************
|
||||
Header of the main table
|
||||
*****************/
|
||||
thead > tr > th {
|
||||
padding: 10px 5px 10px 5px;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
border-bottom: 1px solid black;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
tbody > tr {
|
||||
white-space: nowrap;
|
||||
td:not(:last-child) {
|
||||
padding: 5px;
|
||||
}
|
||||
td:last-child {
|
||||
padding: 5px 5px 20px 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_total {
|
||||
page-break-inside: avoid;
|
||||
position:relative;
|
||||
.o_subtable {
|
||||
width: 100%;
|
||||
page-break-inside: avoid;
|
||||
position:relative;
|
||||
tbody, tr, td{
|
||||
border-style: none;
|
||||
padding: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
tr:first-child {
|
||||
border-top: 1px solid black;
|
||||
}
|
||||
|
||||
tr:last-child {
|
||||
border-top:1px solid black;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_end_page {
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.o_attachment_pdf {
|
||||
max-height: 700px;
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
.o_overflow {
|
||||
white-space: normal !important;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user