How to create a Qweb report in odoo
Qweb is a template engine or reporting engine which can be used to create reports. Odoo uses Qweb for generating reports. Qweb provides several tools for creating a report. By using Qweb, we can manipulate the data very easily.
This blog will provide insight on how we can create a custom PDF Report in Odoo 14?

How Qweb Reports Work in Odoo?
1: Under report add new file named “report.xml”
In “report.xml” add :
syntax as follws:
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<report id="report_id"
model="module.name"
string="report related name"
report_type="qweb-pdf"
name="custom_module_name.body_template_id"
file="custom_module_name.body_template_id"/>
-> Here name and file should be custom addon name . body template id from corresponding xml file
In practical case:
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<report id="estimate_invoice_report_id"
model="estimate.contract"
string="Estimate Contract"
report_type="qweb-pdf"
name="building_contract.estimate_order_body_format"
file="building_contract.estimate_order_body_format"/>
Defining Report Templates:
Templates are designed in an HTML format. The structure will be written inside of <template> tags. We can use other templates inside our template using t-call property.
2: You can create an xml file inside the report directory
syntax as follows:
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<template id="report_header_template_id">
<t t-call="web.html_container">
<t t-if="not o" t-set="o" t-value="doc"/>
<t t-if="not company">
<!-- Multicompany -->
<t t-if="company_id">
<t t-set="company" t-value="company_id"/>
</t>
<t t-elif="o and 'company_id' in o">
<t t-set="company" t-value="o.company_id.sudo()"/>
</t>
<t t-else="else">
<t t-set="company" t-value="res_company"/>
</t>
</t>
<div class="header" t-att-style="report_header_style">
<div class="row">
</div>
</div>
<div class="col-9 text-right" t-field="company.report_header" name="moto">
<div t-field="company.partner_id" t-options="{"widget": "contact", "fields": ["address", "name"], "no_marker": true}"/>
</div>
<div class="article" t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id" t-att-data-oe-lang="o and o.env.context.get('lang')">
<t t-raw="0"/>
</div>
<div class="footer o_background_footer">
<div>
<div t-field="company.report_footer"/>
<div t-if="report_type == 'pdf'" class="text-muted">
<hr style="width:100%;" color="red"/>
<div class="row">
<div class="col-6" style="text-align:right;">
Page:
<span class="page"/>
/
<span class="topage"/>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<template id="report_order_body_format">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="module_name.report_body_template_id">
<div class="page">
<table style="border:1px solid black;width:100%">
<tr>
<td>P Type</td>
<td><t t-esc="o.p_type.name"/></td>
<td>Location</td>
<td><t t-if="o.location"/></td>
</tr>
<tr>
<td>Address</td>
<td><t t-esc="o.address"/> </td>
<td>Currency</td>
<td><t t-esc="o.currency"/> </td>
</tr>
<tr>
<td>Furnishing</td>
<td><t t-esc="o.furnishing"/> </td>
<td>Area of building</td>
<td><t t-esc="o.area_of_building"/> </td>
</tr>
<tr>
<td>Expexted Date</td>
<td><t t-esc="o.expected_date"/> </td>
<td>Area of building</td>
<td><t t-esc="o.area_of_building"/> </td>
</tr>
<tr>
<td>Created Date</td>
<td><t t-esc="o.created_date"/> </td>
<td>Budget</td>
<td><t t-esc="o.budget"/> </td>
</tr>
</table>
<table style="border:1px solid black;width:100%">
<tr>
<td style="border:1px solid black">Product</td>
<td style="border:1px solid black">Quantity</td>
<td style="border:1px solid black">Unit Price</td>
<td style="border:1px solid black">Total Price</td>
</tr>
<t t-foreach="o.order_line" t-as="one_to_many">
<tr>
<td style="border:1px solid black"><t t-esc="one_to_many.product_id.name"/> </td>
<td style="border:1px solid black"><t t-esc="one_to_many.quantity"/></td>
<td style="border:1px solid black"><t t-esc="one_to_many.unit_price"/></td>
<td style="border:1px solid black"><t t-esc="one_to_many.total_price"/></td>
</tr>
</t>
</table>
</div>
</t>
</t>
</t>
</template>
</odoo
-> here you have to change header template_id, body template_id and under body template_id change this one also <t t-call=”module_name.header_template_id”>
In practical Case:
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<template id="estimate_order_header_format">
<t t-call="web.html_container">
<t t-if="not o" t-set="o" t-value="doc"/>
<t t-if="not company">
<!-- Multicompany -->
<t t-if="company_id">
<t t-set="company" t-value="company_id"/>
</t>
<t t-elif="o and 'company_id' in o">
<t t-set="company" t-value="o.company_id.sudo()"/>
</t>
<t t-else="else">
<t t-set="company" t-value="res_company"/>
</t>
</t>
<div class="header" t-att-style="report_header_style">
<div class="row">
</div>
</div>
<div class="col-9 text-right" t-field="company.report_header" name="moto">
<div t-field="company.partner_id" t-options="{"widget": "contact", "fields": ["address", "name"], "no_marker": true}"/>
</div>
<div class="article" t-att-data-oe-model="o and o._name" t-att-data-oe-id="o and o.id" t-att-data-oe-lang="o and o.env.context.get('lang')">
<t t-raw="0"/>
</div>
<div class="footer o_background_footer">
<div>
<div t-field="company.report_footer"/>
<div t-if="report_type == 'pdf'" class="text-muted">
<hr style="width:100%;" color="red"/>
<div class="row">
<div class="col-6" style="text-align:right;">
Page:
<span class="page"/>
/
<span class="topage"/>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<template id="estimate_order_body_format">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="building_contract.estimate_order_header_format">
<div class="page">
<table style="border:1px solid black;width:100%">
<tr>
<td>P Type</td>
<td><t t-esc="o.p_type.name"/></td>
<td>Location</td>
<td><t t-if="o.location"/></td>
</tr>
<tr>
<td>Address</td>
<td><t t-esc="o.address"/> </td>
<td>Currency</td>
<td><t t-esc="o.currency"/> </td>
</tr>
<tr>
<td>Furnishing</td>
<td><t t-esc="o.furnishing"/> </td>
<td>Area of building</td>
<td><t t-esc="o.area_of_building"/> </td>
</tr>
<tr>
<td>Expexted Date</td>
<td><t t-esc="o.expected_date"/> </td>
<td>Area of building</td>
<td><t t-esc="o.area_of_building"/> </td>
</tr>
<tr>
<td>Created Date</td>
<td><t t-esc="o.created_date"/> </td>
<td>Budget</td>
<td><t t-esc="o.budget"/> </td>
</tr>
</table>
<table style="border:1px solid black;width:100%">
<tr>
<td style="border:1px solid black">Sn></td>
<td style="border:1px solid black">Product</td>
<td style="border:1px solid black">Quantity</td>
<td style="border:1px solid black">Unit Price</td>
<td style="border:1px solid black">Total Price</td>
</tr>
<t t-set="i" t-value="1"/>
<t t-foreach="o.order_line" t-as="one_to_many">
<tr>
<td style="border:1px solid black"><t t-esc="1"></td>
<td style="border:1px solid black"><t t-esc="one_to_many.product_id.name"/> </td>
<td style="border:1px solid black"><t t-esc="one_to_many.quantity"/></td>
<td style="border:1px solid black"><t t-esc="one_to_many.unit_price"/></td>
<td style="border:1px solid black"><t t-esc="one_to_many.total_price"/></td>
</tr>
<t t-set="i" t-value="i+1"/>
</t>
</table>
</div>
</t>
</t>
</t>
</template>
</odoo>
<t t-set="i" t-value="1"/> These code is used to display the sequence number in reports, you have
<t t-set="i" t-value="i+1"/> 20 products it diaplays in 1,2,3, like that :
Print of estimate order
To Use This one Click here
To know more about us