Enable Dark Mode!
how-to-create-a-report-from-a-pos-session-in-odoo-19.jpg
By: Ahammed Harshad P

How to Create a Report from a POS Session in Odoo 19

Technical Odoo 19 POS

Running a retail business means you need quick access to sales data. Custom reports from your point of sale sessions help you track what's selling, when it's selling, and how your team is performing. In Odoo 19, building these reports takes a different approach than earlier versions due to the updated component architecture.

Let's walk through creating a custom POS report that generates when you click a button on the Receipt Screen.

Adding Your Report Button to the Receipt Screen

First up, we need a button that users can actually click. In Odoo 19, we'll place this on the Receipt Screen using component patching instead of the older inheritance methods.

Create your XML file  at static/src/xml/pos_report_button.xml:

<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
    <t t-name="point_of_sale.ReceiptScreen" 
       t-inherit="point_of_sale.ReceiptScreen" 
       t-inherit-mode="extension">
        <xpath expr="//div[hasclass('actions')]" position="inside">
           <button class="btn btn-primary pos-order-report-btn" 
              t-on-click="generatePosReport">
              <i class="fa fa-file-pdf-o"/> Generate Report
           </button>
        </xpath>
    </t>
</templates>

Now add some styling in static/src/css/pos_report_button.css:

.pos-order-report-btn {
    padding: 12px 24px;
    margin: 8px;
    background: #007bff;
    color: white;
    border-radius: 4px;
    border: none;
    cursor: pointer;
    font-weight: 500;
    transition: background 0.3s ease;
}
.pos-order-report-btn:hover {
    background: #0056b3;
}
.pos-order-report-btn i {
    margin-right: 8px;
}

Updating Your Module Manifest

Your __manifest__.py needs to know about these new files. Add them to the assets section:

{
    'name': 'POS Order Report',
    'version': '19.0.1.0.0',
    'category': 'Point of Sale',
    'depends': ['point_of_sale'],
    'assets': {
        'point_of_sale._assets_pos': [
            'pos_order_report/static/src/xml/pos_report_button.xml',
            'pos_order_report/static/src/css/pos_report_button.css',
            'pos_order_report/static/src/js/receipt_screen.js',
        ],
    },
    'installable': True,
}

Wiring Up the Button with JavaScript

Here's where Odoo 19 differs significantly from version 16. We use the patch mechanism to extend components.

Create static/src/js/receipt_screen.js:

/** @odoo-module */
import { ReceiptScreen } from "@point_of_sale/app/screens/receipt_screen/receipt_screen";
import { patch } from "@web/core/utils/patch";
import { useService } from "@web/core/utils/hooks";
patch(ReceiptScreen.prototype, {
    setup() {
        super.setup();
        this.orm = useService("orm");
        this.actionService = useService("action");
        this.popup = useService("popup");
    },
    async generatePosReport() {
        const currentOrder = this.pos.getOrder();
        
        if (!currentOrder) {
            this.popup.add("ErrorPopup", {
                title: "No Order",
                body: "There is no current order to generate a report for.",
            });
            return;
        }
        
        const orderName = currentOrder.name;
        
        console.log('Generating report for order:', orderName);
        
        try {
            const result = await this.orm.call(
                'pos.order.wizard',
                'generate_report',
                [[], orderName]
            );
            
            this.actionService.doAction(result);
            
            console.log('Report generated successfully');
        } catch (error) {
            console.error('Report generation failed:', error);
            this.popup.add("ErrorPopup", {
                title: "Report Error",
                body: "Could not generate the report. Please try again.\\n\\nError: " + error.message,
            });
        }
    }
});

Notice how we're using this.orm.call() instead of the old RPC method, and this.actionService.doAction() instead of the legacy action manager.

Building the Backend Report Logic

Now let's handle the server side. Create models/pos_order_wizard.py:

from odoo import models, api
class PosOrderWizard(models.TransientModel):
    _name = 'pos.order.wizard'
    _description = "POS Order Report Wizard"
    @api.model
    def generate_report(self, order_name):
        """Generate PDF report for POS order"""
        return self.env.ref(
            'pos_order_report.action_pos_order_report'
        ).report_action(self, data={'order_name': order_name})

Don't forget to add this to your models/__init__.py:

from . import pos_order_wizard

Defining the Report Action

Create report/pos_order_report.xml to register your report:

<?xml version="1.0" encoding="UTF-8"?>
<odoo>
    <record id="action_pos_order_report" model="ir.actions.report">
        <field name="name">POS Order Report</field>
        <field name="model">pos.order.wizard</field>
        <field name="report_type">qweb-pdf</field>
        <field name="report_name">pos_order_report.pos_order_template</field>
        <field name="report_file">pos_order_report.pos_order_template</field>
        <field name="print_report_name">'POS_Order_%s' % (object.id)</field>
        <field name="binding_model_id" ref="model_pos_order_wizard"/>
    </record>
</odoo>

Creating Your Report Template

Design what the actual report looks like in report/pos_order_template.xml:

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="pos_order_template">
        <t t-call="web.html_container">
            <t t-call="web.external_layout">
                <div class="page">
                    <h2 class="text-center">POS Order Report</h2>
                    <p class="text-center text-muted">
                        Generated on <span t-esc="context_timestamp(datetime.datetime.now()).strftime('%Y-%m-%d')"/>
                    </p>
                    
                    <div class="mt-4">
                        <h4>Order Information</h4>
                        <p><strong>Order Name:</strong> <span t-esc="data.get('order_name', 'N/A')"/></p>
                        <p><strong>Report Type:</strong> <span t-esc="data.get('report_type', 'PDF')"/></p>
                    </div>
                    
                    <table class="table table-sm mt-4">
                        <thead>
                            <tr>
                                <th>Product</th>
                                <th>Quantity</th>
                                <th>Price</th>
                                <th>Total</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr>
                                <td colspan="4" class="text-center text-muted">
                                    Order details here
                                </td>
                            </tr>
                        </tbody>
                    </table>
                    
                    <div class="mt-5 text-center">
                        <p class="text-muted">
                            This is a sample report template. You can customize it to include
                            detailed order lines, payment information, and more.
                        </p>
                    </div>
                </div>
            </t>
        </t>
    </template>
</odoo>

Manifest Configuration

Update your __manifest__.py:

{
    'name': 'POS Order Report',
    'version': '19.0.1.0.0',
    'depends': ['point_of_sale'],
    'data': [
        'security/ir.model.access.csv',
        'report/pos_order_report.xml',
        'report/pos_order_template.xml',
    ],
    'assets': {
        'point_of_sale._assets_pos': [
            'pos_order_report/static/src/xml/pos_report_button.xml',
            'pos_order_report/static/src/css/pos_report_button.css',
            'pos_order_report/static/src/js/receipt_screen.js',
        ],
    },
}

Finally, after rendering the report, this is a demo report that has been generated to showcase the final output and layout.

How to Create a Report from a POS Session in Odoo 19-cybrosys

Wrapping It Up

With these pieces in place, your POS users get a simple but powerful workflow: validate an order, land on the Receipt Screen, click “Generate Report,” and instantly receive a PDF summarizing the sale. You stay fully aligned with Odoo 19’s frontend architecture by using patching, services, and camelCase APIs, while keeping the backend clean and focused on a single transient wizard. From here, you can extend the template to pull in real order lines, totals, payments, and anything else your reporting needs demand.

To read more about Overview of Reporting in Odoo 18 Point of Sale, refer to our blog Overview of Reporting in Odoo 18 Point of Sale.


If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



Recent Posts

whatsapp_icon
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635

location

Kochi

Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.

location

Bangalore

Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message