Skip to content

Managing documents within Odoo plugin

Odoo Document Flow in tqodoo: Lead → Customer → Quote → Invoice → Payment

This document explains, in practical developer terms, how the Odoo integration module (tqodoo) implements the end‑to‑end commercial chain: - Create a lead (crm.lead) - Create or find a customer (res.partner) - Create/manage a quote (sale.order and sale.order.line) - Confirm quote and create an invoice (account.move and account.move.line) - Register and confirm a payment (account.payment.registeraccount.payment)

It maps concrete services, entities, and actions configured in config/odoo-client.properties, and shows idiomatic usage of the tqodoo client services.

References: - Implementation overview: Writerside/topics/Implementation-Specification.md - Service configuration: config/odoo-client.properties

Key Concepts Recap (tqodoo)

  • OdooServiceFactory resolves service definitions by services.<name>.* keys and wires the XML‑RPC call context (server/db/user/session).
  • OdooEntity*Service classes (Search, Read, SearchRead, Write, Document) encapsulate common patterns (search_read, read, create, write, custom action_*).
  • OdooEntity subclasses model Odoo records and provide reflection‑based mapping between Java properties and Odoo field names via @TlinqEntityField.

1) Lead (crm.lead)

Configured services and entity: - Read/search: services.getLead.* → model crm.lead → class OdooEntitySearchReadService → entity com.perun.tlinq.client.odoo.entity.OdooLead - Create: services.createLead.* → model crm.lead → class OdooEntityWriteService - Update: services.updateLead.* → model crm.lead → class OdooEntityWriteService

Entity: tqodoo/.../entity/OdooLead.java - Maps common custom fields like x_origin, x_dest, x_startdate, x_enddate, x_numadlts, x_numchld, email_from, phone, etc. - partner_id (many2one) is mapped via tuple indices: index 0 → integer ID, index 1 → display name.

Typical usage

OdooServiceFactory f = OdooServiceFactory.getInstance();
String sess = f.getSessionId(userSessionToken);

// Create a lead
OdooEntityWriteService leadWrite = (OdooEntityWriteService) f.createService("createLead", sess);
OdooLead lead = new OdooLead();
lead.setLeadName("Website request: 5D Dubai");
lead.setOrigin("DXB");
lead.setDestination("AUH");
lead.setAdults(2);
lead.setChildren(1);
lead.setContactEmail("john@example.com");
Integer leadId = leadWrite.create(lead);

// Find lead(s)
OdooEntitySearchReadService leadSearch = f.createSearchReadService("getLead", sess);
leadSearch.addCriterion("id", "=", leadId);
leadSearch.setResultFields("id", "name", "partner_id", "email_from");
List<OdooLead> leads = leadSearch.search();

Developer practice tips - Set partner_id only if a customer already exists; otherwise create/find a customer first (see next section) and then update the lead (updateLead). - Use search_read with a tight field list (setResultFields) for performance.


2) Customer (res.partner)

Configured services and entity: - Search: services.searchCustomers.* and services.findCustomer.* → model res.partner → class OdooEntitySearchReadService → entity OdooCustomer - Read by IDs: services.getCustomers.* → class OdooReadCustomersService - Create/Update: services.createCustomer.* / services.updateCustomer.* → class OdooEntityWriteService

Typical usage

// Find by email or name
OdooEntitySearchReadService custSearch = f.createSearchReadService("findCustomer", sess);
custSearch.addCriterion("email", "=", "john@example.com");
custSearch.setLimit(1);
List<OdooCustomer> found = custSearch.search();

Integer partnerId;
if (found.isEmpty()) {
  OdooEntityWriteService custWrite = (OdooEntityWriteService) f.createService("createCustomer", sess);
  OdooCustomer c = new OdooCustomer();
  c.setName("John Smith");
  c.setEmail("john@example.com");
  partnerId = custWrite.create(c);
} else {
  partnerId = found.get(0).getId();
}

Developer practice tips - Many2one fields: set the integer ID (e.g., country_id, user_id), not the display name. The entity mapper handles reading tuple (id, name) via index. - Normalize text filters using ilike rather than like. The OdooEntitySearchReadService already normalizes likeilike for Odoo.


3) Quote (sale.order) and Lines (sale.order.line)

Configured services and entities: - Read/search quote: services.getQuote.*sale.orderOdooEntitySearchReadServiceOdooQuote - Create/update/delete quote: services.createQuote.* / services.updateQuote.* / services.deleteQuote.*OdooEntityWriteService - Confirm quote: services.confirmQuote.*OdooClientService action action_confirm - Print quote: services.printQuote.*OdooDocumentService (QWeb report rendering) - Quote lines: services.getQuoteLine.*, services.createQuoteLine.*, services.updateQuoteLine.*, services.deleteQuoteLine.* → model sale.order.line → entity OdooQuoteLine

Entity: tqodoo/.../entity/OdooQuote.java - Important fields: partner_id, amount_total, currency_id, order_line, invoice_ids, state. - Custom PGW fields: x_paymentref, x_pgwref, x_pgwurl, x_extcartid, x_pgwstatus.

Typical usage

// Create a quote
OdooEntityWriteService qWrite = (OdooEntityWriteService) f.createService("createQuote", sess);
OdooQuote q = new OdooQuote();
q.setCustomerId(partnerId);
q.setCurrencyId(1); // e.g., 1 = AED, check your environment
Integer quoteId = qWrite.create(q);

// Add a line
OdooEntityWriteService qlWrite = (OdooEntityWriteService) f.createService("createQuoteLine", sess);
OdooQuoteLine line = new OdooQuoteLine();
line.setOrderId(quoteId);
line.setProductId(variantId);
line.setName("Dubai City Tour");
line.setPriceUnit(120.0);
line.setProductUomQty(2.0);
Integer lineId = qlWrite.create(line);

// Confirm the quote (becomes a Sales Order)
OdooClientService confirm = (OdooClientService) f.createService("confirmQuote", sess);
confirm.addMethodParameter(quoteId);
Boolean ok = confirm.invoke(Boolean.class);

Developer practice tips - Create the sale.order first, then create sale.order.line with order_id referencing the new quote ID. - After action_confirm, stock rules or delivery processes may trigger depending on the product setup. Ensure test data aligns with intended flows.


4) Invoice (account.move) from Quote

Configured services and entities: - Create invoice from quote: services.invoiceQuote.* on sale.order action action_invoice_create (older Odoo API; your DB must support it) — produces account.move in modern versions with proper configuration. - Direct invoice ops: services.getInvoice.*, services.createInvoice.*, services.updateInvoice.*, services.deleteInvoice.* on model account.move → entity OdooInvoice - Validate (post) invoice: services.validateInvoice.* on account.move action action_post - Print invoice: services.printInvoice.* via OdooDocumentService - Lines: services.getInvoiceLine.*, services.createInvoiceLine.*, etc. on account.move.line → entity OdooInvoiceLine

Entity: tqodoo/.../entity/OdooInvoice.java - Key amounts: amount_total, amount_tax, amount_untaxed, amount_residual (due), invoice_has_outstanding (open). - Relational: partner_id, line_ids, currency_id.

Typical usage

// Option A: From quote (if your Odoo exposes action_invoice_create)
OdooClientService invFromQuote = (OdooClientService) f.createService("invoiceQuote", sess);
invFromQuote.addMethodParameter(quoteId);
Object created = invFromQuote.invoke(Object.class); // often returns new invoice ID(s)

// Option B: Create an invoice directly
OdooEntityWriteService invWrite = (OdooEntityWriteService) f.createService("createInvoice", sess);
OdooInvoice inv = new OdooInvoice();
inv.setCustomerId(partnerId);
inv.setCurrencyId(1);
Integer invoiceId = invWrite.create(inv);

// Validate (post) the invoice so it becomes payable
OdooClientService post = (OdooClientService) f.createService("validateInvoice", sess);
post.addMethodParameter(invoiceId);
Boolean posted = post.invoke(Boolean.class);

Developer practice tips - In Odoo 13+, customer invoices are account.move with move_type='out_invoice'. Ensure your environment defaults or set via the entity before create. - Always call action_post to make the invoice official before registering payments.


5) Payment Registration and Confirmation

Configured services and entities: - Register payment (wizard): - services.createPayment.* → model account.payment.register → entity OdooPaymentReg - services.confirmPayment.* → action action_create_payments (executes the wizard, creates account.payment and reconciles) - Legacy helper (DB‑specific): services.registerPayment.* on account.move action action_invoice_open (pre‑13 behavior). Prefer the wizard services above for modern DBs. - Read payments: services.getInvoicePayment.* → model account.payment → entity OdooPayment

Entity: tqodoo/.../entity/OdooPaymentReg.java - Provides sane defaults and constants for payment type, journals, and methods (e.g., INBOUND_PAYMENT, BANK_JOURNAL, PMT_IN_BANK_MANUAL).

Typical usage (wizard)

// Build payment register request
OdooEntityWriteService payRegCreate = (OdooEntityWriteService) f.createService("createPayment", sess);
OdooPaymentReg pr = new OdooPaymentReg();
pr.setCustomerId(partnerId);
pr.setAmount(240.00);
pr.setCurrencyId(1);
pr.setCommunication("SO" + quoteId + " / INV" + invoiceId);

Integer prId = payRegCreate.create(pr);

// Execute wizard to create actual account.payment and reconcile
OdooClientService payConfirm = (OdooClientService) f.createService("confirmPayment", sess);
payConfirm.addMethodParameter(prId);
Object result = payConfirm.invoke(Object.class);

// Optionally read back payments linked to the invoice
OdooEntitySearchReadService pmtSearch = f.createSearchReadService("getInvoicePayment", sess);
pmtSearch.addCriterion("partner_id", "=", partnerId);
pmtSearch.setResultFields("id", "amount", "payment_date", "journal_id");
List<OdooPayment> pmts = pmtSearch.search();

Developer practice tips - Use journals and methods that exist in your Odoo DB. OdooPaymentReg exposes sample IDs, but these are environment‑specific. - To tie a payment to a specific invoice, ensure the invoice is posted and the wizard context refers to that invoice; depending on DB setup, you may need to set additional fields (e.g., line_ids, can_group_payments) or invoke the wizard through a document service that sets active_model/active_ids. See OdooDocumentService for patterns using contextual kwargs.


6) End‑to‑End Example (Putting It All Together)

OdooServiceFactory f = OdooServiceFactory.getInstance();
String sess = f.getSessionId(userSessionToken);

// 1. Customer ensure
Integer partnerId = ensureCustomerByEmail(f, sess, "john@example.com", "John Smith");

// 2. Lead create
OdooEntityWriteService leadWrite = (OdooEntityWriteService) f.createService("createLead", sess);
OdooLead lead = new OdooLead();
lead.setLeadName("Dubai package inquiry");
lead.setCustomerId(partnerId);
Integer leadId = leadWrite.create(lead);

// 3. Quote + line
OdooEntityWriteService qWrite = (OdooEntityWriteService) f.createService("createQuote", sess);
OdooQuote q = new OdooQuote();
q.setCustomerId(partnerId);
q.setCurrencyId(1);
Integer quoteId = qWrite.create(q);

OdooEntityWriteService qlWrite = (OdooEntityWriteService) f.createService("createQuoteLine", sess);
OdooQuoteLine line = new OdooQuoteLine();
line.setOrderId(quoteId);
line.setProductId(variantId);
line.setName("Dubai City Tour");
line.setProductUomQty(2.0);
line.setPriceUnit(120.0);
qlWrite.create(line);

// 4. Confirm quote → (optionally) create invoice
OdooClientService confirm = (OdooClientService) f.createService("confirmQuote", sess);
confirm.addMethodParameter(quoteId);
confirm.invoke(Boolean.class);

OdooClientService invFromQuote = (OdooClientService) f.createService("invoiceQuote", sess);
invFromQuote.addMethodParameter(quoteId);
invFromQuote.invoke(Object.class);

// Read back invoices of the order
OdooEntitySearchReadService soRead = f.createSearchReadService("getQuote", sess);
soRead.addCriterion("id", "=", quoteId);
soRead.setResultFields("invoice_ids");
Integer[] invoiceIds = soRead.search().get(0).getInvoiceIds();
Integer invoiceId = invoiceIds != null && invoiceIds.length > 0 ? invoiceIds[0] : null;

// 5. Post invoice then register payment
OdooClientService post = (OdooClientService) f.createService("validateInvoice", sess);
post.addMethodParameter(invoiceId);
post.invoke(Boolean.class);

OdooEntityWriteService payRegCreate = (OdooEntityWriteService) f.createService("createPayment", sess);
OdooPaymentReg pr = new OdooPaymentReg();
pr.setCustomerId(partnerId);
pr.setAmount(240.0);
pr.setCurrencyId(1);
Integer prId = payRegCreate.create(pr);

OdooClientService payConfirm = (OdooClientService) f.createService("confirmPayment", sess);
payConfirm.addMethodParameter(prId);
payConfirm.invoke(Object.class);

Helper (example) used above

private static Integer ensureCustomerByEmail(OdooServiceFactory f, String sess, String email, String name) {
  OdooEntitySearchReadService s = f.createSearchReadService("findCustomer", sess);
  s.addCriterion("email", "=", email);
  s.setLimit(1);
  List<OdooCustomer> list = s.search();
  if (!list.isEmpty()) return list.get(0).getId();
  OdooEntityWriteService w = (OdooEntityWriteService) f.createService("createCustomer", sess);
  OdooCustomer c = new OdooCustomer(); c.setName(name); c.setEmail(email);
  return w.create(c);
}


7) Best Practices and Gotchas

  • Always minimize payload by setting setResultFields on reads/searches.
  • Many2one mapping: when writing, set the integer ID. When reading, use index=0 for ID, index=1 for name (already handled in entities).
  • Date fields: ensure server/client formats align with odoo.format.date and odoo.format.datetime in properties.
  • Actions vs writes: functional transitions (confirm order, post invoice, create payments) use OdooClientService or OdooDocumentService with the configured action_* rather than direct write.
  • Payments: prefer account.payment.register wizard (createPayment + confirmPayment) for Odoo 13+; the legacy registerPayment on invoice model may not be present in modern DBs.
  • Session: obtain an application session via OdooServiceFactory.getSessionId(sessionToken); ensure the current user has permissions on the models/actions invoked.

8) Service Name Quick Map (as configured)

  • Lead: getLead, createLead, updateLeadcrm.leadOdooLead
  • Customer: searchCustomers / findCustomer, getCustomers, createCustomer, updateCustomerres.partnerOdooCustomer
  • Quote: getQuote, createQuote, updateQuote, deleteQuote, confirmQuote, printQuotesale.orderOdooQuote
  • Quote Lines: getQuoteLine, createQuoteLine, updateQuoteLine, deleteQuoteLinesale.order.lineOdooQuoteLine
  • Invoice: getInvoice, createInvoice, updateInvoice, deleteInvoice, validateInvoice, printInvoiceaccount.moveOdooInvoice
  • Payment (wizard): createPayment, confirmPayment, getInvoicePaymentaccount.payment.register / account.paymentOdooPaymentReg / OdooPayment

This guide should let you implement the full document flow using tqodoo with minimal guesswork while staying aligned with the project’s configuration and entity mappings.