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.register → account.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)¶
OdooServiceFactoryresolves service definitions byservices.<name>.*keys and wires the XML‑RPC call context (server/db/user/session).OdooEntity*Serviceclasses (Search, Read, SearchRead, Write, Document) encapsulate common patterns (search_read,read,create,write, customaction_*).OdooEntitysubclasses 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 like → ilike for Odoo.
3) Quote (sale.order) and Lines (sale.order.line)¶
Configured services and entities:
- Read/search quote: services.getQuote.* → sale.order → OdooEntitySearchReadService → OdooQuote
- 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
setResultFieldson reads/searches. - Many2one mapping: when writing, set the integer ID. When reading, use
index=0for ID,index=1for name (already handled in entities). - Date fields: ensure server/client formats align with
odoo.format.dateandodoo.format.datetimein properties. - Actions vs writes: functional transitions (confirm order, post invoice, create payments) use
OdooClientServiceorOdooDocumentServicewith the configuredaction_*rather than directwrite. - Payments: prefer
account.payment.registerwizard (createPayment+confirmPayment) for Odoo 13+; the legacyregisterPaymenton 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,updateLead→crm.lead→OdooLead - Customer:
searchCustomers/findCustomer,getCustomers,createCustomer,updateCustomer→res.partner→OdooCustomer - Quote:
getQuote,createQuote,updateQuote,deleteQuote,confirmQuote,printQuote→sale.order→OdooQuote - Quote Lines:
getQuoteLine,createQuoteLine,updateQuoteLine,deleteQuoteLine→sale.order.line→OdooQuoteLine - Invoice:
getInvoice,createInvoice,updateInvoice,deleteInvoice,validateInvoice,printInvoice→account.move→OdooInvoice - Payment (wizard):
createPayment,confirmPayment,getInvoicePayment→account.payment.register/account.payment→OdooPaymentReg/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.