Enable Dark Mode!
how-to-integrate-odoo-reports-pdfs-into-mobo-apps.jpg
By: Muhammed Shehzad K

How to Integrate Odoo Reports (PDFs) into Mobo Apps

Technical Flutter mobo

One of Odoo's most powerful features is its QWeb Reporting Engine, which generates pixel-perfect PDF documents for Invoices, Sales Orders, Delivery Slips, and more. When building a mobile app for Odoo using Flutter, you face a critical architectural decision:

Client-Side Generation

Re-create the document layout directly in Flutter code using packages like pdf.

Pros

  • Works offline
  • Total control over rendering

Cons

  • Double maintenance
  • Every Odoo report change (tax laws, branding, layouts) requires Flutter updates.

Server-Side Fetching

Download the exact PDF generated by Odoo.

Pros

  • Single source of truth
  • Mobile and Web PDFs are always identical.

Cons

  • Requires online connectivity

For official business documents (Invoices, Quotations, Picking Slips), Server-Side Fetching is the gold standard. This guide offers a comprehensive exploration of implementing this robust pattern in Flutter.

Prerequisites

Odoo Instance

  • Odoo v14, v15, v16, or v17+
  • Community or Enterprise edition

Flutter Project Dependencies

dependencies:
  http: ^1.2.0          # Raw HTTP requests
  path_provider: ^2.1.2 # Access temporary storage
  open_file: ^3.3.2     # Open PDF in native viewer
  share_plus: ^9.0.0    # Share via WhatsApp / Email

Core Concepts

1. The Anatomy of a Report URL

Odoo exposes a dedicated HTTP endpoint for downloading PDF reports.

{base_url}/report/pdf/{report_xml_id}/{document_ids}

Parameters explained:

  • base_url – Your Odoo server URL (e.g., https://my-odoo-instance.com)
  • report_xml_id – The technical identifier of the report
  • document_ids – A single ID (123) or multiple IDs (123,124) merged into one PDF

Common Report XML IDs

  • Invoice: account.report_invoice_with_payments
  • Sales Order: sale.report_saleorder
  • Delivery Slip: stock.report_deliveryslip

2. The Authentication Challenge

Downloading a report is a standard HTTP GET request, not a JSON-RPC call.

If you simply call:

http.get(url)

Odoo treats the request as anonymous and returns:

  • An HTML login page, or
  • A 404 / access error

Required Technique

Manually inject the session ID into the Cookie header:

Cookie: session_id=YOUR_SESSION_ID

This step is critical.

Implementation: The Robust Way

We’ll build a production-grade PdfReportService that handles:

  • Authentication
  • URL sanitation
  • File I/O
  • Content validation
  • Viewing and sharing

Step 1: Design Logic

The service must:

  • Accept Odoo connection details
  • Construct a valid report URL
  • Download binary data
  • Verify the response is a PDF
  • Save it securely
  • Open or share it

Step 2: Production-Ready Service Class

import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:open_file/open_file.dart';
import 'package:share_plus/share_plus.dart';
class PdfReportService {
  /// Downloads a report from Odoo and returns the local File
  Future<File> downloadReport({
    required String baseUrl,
    required String sessionId,
    required String reportXmlId,
    required int docId,
    required String fileName,
  }) async {
    final cleanBaseUrl = baseUrl.endsWith('/')
        ? baseUrl.substring(0, baseUrl.length - 1)
        : baseUrl;
    final url = Uri.parse('$cleanBaseUrl/report/pdf/$reportXmlId/$docId');
    try {
      final response = await http.get(
        url,
        headers: {
          'Cookie': 'session_id=$sessionId', // CRITICAL
        },
      ).timeout(const Duration(seconds: 30));
      if (response.statusCode != 200) {
        throw Exception(
          'Failed to download report. Status: ${response.statusCode}'
        );
      }
      final contentType = response.headers['content-type'];
      if (contentType != null && !contentType.contains('application/pdf')) {
        throw Exception('Expected PDF, got $contentType');
      }
      final bytes = response.bodyBytes;
      if (bytes.isEmpty) {
        throw Exception('Received empty PDF from server');
      }
      final tempDir = await getTemporaryDirectory();
      final safeName = fileName.replaceAll(RegExp(r'[^a-zA-Z0-9_.-]'), '_');
      final file = File('${tempDir.path}/$safeName.pdf');
      await file.writeAsBytes(bytes, flush: true);
      return file;
    } catch (e) {
      print('downloadReport error: $e');
      rethrow;
    }
  }
  /// Opens the PDF using the native viewer
  Future<void> openFile(File file) async {
    final result = await OpenFile.open(file.path);
    if (result.type != ResultType.done) {
      throw Exception('Could not open file: ${result.message}');
    }
  }
}

Step 3: Finding the Report XML ID

  1. Enable Developer Mode in Odoo
  2. Navigate to Settings > Technical > Reports
  3. Search for the document ( Invoice, Sales Order, etc.)
  4. Copy the XML ID

Frequently Used IDs

  • sale.report_saleorder
  • account.report_invoice_with_payments
  • stock.report_picking
  • stock.report_deliveryslip

Handling Advanced Scenarios

1. Fallback Mechanism for Invoices

Different localizations install different invoice reports. Hardcoding one ID can cause failures.

Future<void> printInvoice(int invoiceId) async {
  const candidates = [
    'account.report_invoice_with_payments',
    'account.report_invoice',
    'account.report_invoice_document',
  ];
  File? pdfFile;
  for (final reportId in candidates) {
    try {
      pdfFile = await myService.downloadReport(
        reportXmlId: reportId,
        docId: invoiceId,
      );
      break;
    } catch (_) {
      continue;
    }
  }
  if (pdfFile != null) {
    await myService.openFile(pdfFile);
  } else {
    showError('Could not generate invoice PDF');
  }
}

2. Sharing via WhatsApp or Email

Future<void> shareFile(File file, String subject) async {
  final xFile = XFile(file.path);
  await Share.shareXFiles(
    [xFile],
    subject: subject,
    text: 'Please find attached: $subject',
  );
}

3. HTML Instead of PDF (Most Common Bug)

Symptom

  • Status code 200
  • File opens as a blank page or HTML.

Cause

  • Expired or invalid session_id

Fix

  • Check content-type
  • If text/html, trigger re-login
  • Refresh session and retry.

Server-side PDF fetching is the backbone of any serious Flutter–Odoo ERP application. By leveraging Odoo’s QWeb engine and carefully handling cookie-based authentication, you ensure that mobile users receive documents that perfectly match backend expectations.

With a robust download, validation, and fallback strategy in place, your Flutter app delivers a professional, enterprise-grade document experience.

For Flutter developers focused on building advanced business applications, the Mobo mobile application offers a dependable integration framework that complements this approach. With secure session handling, real-time data synchronization, and resilient error management, Mobo supports the development of scalable, enterprise-level mobile solutions that align seamlessly with Odoo-powered systems.

To read more about How to Authenticate Mobo Apps with Odoo: Login, Sessions & Token Security Explained, refer to our blog How to Authenticate Mobo Apps with Odoo: Login, Sessions & Token Security Explained.


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



0
Comments



Leave a comment



whatsapp_icon
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, KINFRA Techno Park
Kakkanchery, 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