<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Aaron Jones</title>
    <description>The latest articles on Forem by Aaron Jones (@aaron_jones_d34b57d1b44ba).</description>
    <link>https://forem.com/aaron_jones_d34b57d1b44ba</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3652982%2F357e8a47-6b7a-4c09-b8f4-6c0f23b9e090.png</url>
      <title>Forem: Aaron Jones</title>
      <link>https://forem.com/aaron_jones_d34b57d1b44ba</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/aaron_jones_d34b57d1b44ba"/>
    <language>en</language>
    <item>
      <title>Odoo Pos Development Company for Seamless Payment Gateway Integration</title>
      <dc:creator>Aaron Jones</dc:creator>
      <pubDate>Wed, 11 Feb 2026 08:55:12 +0000</pubDate>
      <link>https://forem.com/aaron_jones_d34b57d1b44ba/odoo-pos-development-company-for-seamless-payment-gateway-integration-3g9m</link>
      <guid>https://forem.com/aaron_jones_d34b57d1b44ba/odoo-pos-development-company-for-seamless-payment-gateway-integration-3g9m</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbcotouy97o2qb9usb8re.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbcotouy97o2qb9usb8re.jpeg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In today’s retail world, customers expect checkout to be quick, smooth, and secure—whether they pay by card, UPI, wallet, QR, or contactless tap. That’s why choosing the right Odoo Pos Development Company matters so much when you want seamless payment gateway integration inside your Odoo POS. A strong payment setup is not only about “accepting payments”—it’s about reducing failed transactions, speeding up billing, preventing reconciliation errors, and giving your staff a simple workflow that works perfectly during peak hours.&lt;/p&gt;

&lt;p&gt;As an experienced Odoo Pos Development Company, we help businesses connect Odoo POS with trusted payment gateways and payment terminals, ensuring every transaction is fast, verified, and properly recorded. Whether you run a supermarket, restaurant, fashion store, electronics showroom, pharmacy, or multi-branch retail chain, payment integration can directly impact customer satisfaction and daily operations. Long queues, card declines, manual entry, and mismatched sales reports are all problems that come from poor integration. Our goal is to remove these friction points and deliver a checkout experience that feels effortless.&lt;/p&gt;

&lt;p&gt;Why seamless payment gateway integration is essential in Odoo POS&lt;br&gt;
Odoo POS is powerful, but payment workflows can vary widely depending on your country, business type, and chosen gateway. Many businesses face common issues like partial payments not syncing correctly, refunds not matching original transactions, or payment confirmation delays that slow down the counter. With the right integration approach, your staff can complete sales in seconds, and your back office can track everything without extra manual work.&lt;/p&gt;

&lt;p&gt;A professional Odoo Pos Development Company ensures that your payment flow is accurate from end to end:&lt;/p&gt;

&lt;p&gt;Payment authorization happens smoothly at checkout&lt;/p&gt;

&lt;p&gt;Payment status updates correctly in Odoo POS&lt;/p&gt;

&lt;p&gt;Transactions sync to Odoo accounting/finance (when required)&lt;/p&gt;

&lt;p&gt;Refunds, cancellations, and chargebacks follow proper logic&lt;/p&gt;

&lt;p&gt;Reports and reconciliation stay clean and reliable&lt;/p&gt;

&lt;p&gt;What we deliver as an Odoo Pos Development Company&lt;br&gt;
We don’t just “connect a gateway.” We build a complete payment experience designed around real retail operations. Our team starts by understanding your current checkout journey: how you bill customers, what payment methods you accept, what devices you use, and how you manage settlements.&lt;/p&gt;

&lt;p&gt;Then, we implement:&lt;br&gt;
1) Gateway integration built for speed and stability&lt;br&gt;
We integrate payment gateways in a way that reduces failure rates and handles slow network scenarios. If a payment is delayed, Odoo POS should not freeze or confuse your cashier. We design flows that keep the billing process stable and clear.&lt;/p&gt;

&lt;p&gt;2) Secure payment handling and compliance-friendly setup&lt;br&gt;
Security is non-negotiable. As a trusted Odoo Pos Development Company, we follow best practices for tokenization, secure API handling, and safe storage rules. This protects customer payment data and reduces your risk.&lt;/p&gt;

&lt;p&gt;3) Multi-payment and split-payment support&lt;br&gt;
Modern retail often needs split payments: part cash, part card, part wallet, or multiple cards. We configure and customize Odoo POS to support split payments without breaking reporting.&lt;/p&gt;

&lt;p&gt;4) Refunds, voids, and payment reversal logic&lt;br&gt;
Refunds are where many systems fail. We ensure refunds are linked to original payments, handled properly by the gateway, and recorded correctly in Odoo. The result: fewer disputes and easier reconciliation.&lt;/p&gt;

&lt;p&gt;5) Multi-store and multi-currency readiness&lt;br&gt;
If you operate multiple stores, payment rules and devices can differ by location. We create a structured setup that works across branches, supports multi-currency when needed, and keeps management reports consistent.&lt;/p&gt;

&lt;p&gt;Real business benefits you can expect&lt;br&gt;
When your payment gateway integration is done right, you’ll see improvements immediately:&lt;/p&gt;

&lt;p&gt;Faster checkout: shorter queues, higher customer satisfaction&lt;/p&gt;

&lt;p&gt;Fewer payment errors: reduced manual intervention and confusion&lt;/p&gt;

&lt;p&gt;Cleaner reporting: accurate sales records, fewer mismatches&lt;/p&gt;

&lt;p&gt;Easier reconciliation: settlements align with Odoo sales data&lt;/p&gt;

&lt;p&gt;Better staff efficiency: less training time, fewer billing mistakes&lt;/p&gt;

&lt;p&gt;That’s the difference a specialized Odoo Pos Development Company brings—your POS becomes a reliable revenue engine instead of a daily troubleshooting task.&lt;/p&gt;

&lt;p&gt;Our process: from planning to go-live&lt;br&gt;
We keep the implementation structured and business-friendly:&lt;/p&gt;

&lt;p&gt;Discovery &amp;amp; planning: identify gateways, terminals, payment types, and current issues&lt;br&gt;
Development &amp;amp; integration: configure gateway APIs, POS payment methods, device mapping&lt;br&gt;
Testing: test success payments, failures, refunds, offline scenarios, sync behavior&lt;br&gt;
Deployment: safe launch with minimal disruption to store operations&lt;br&gt;
Support: monitoring, improvements, and updates as gateway rules evolve&lt;/p&gt;

&lt;p&gt;Why businesses choose our Odoo POS payment integration services&lt;br&gt;
Many companies can install Odoo, but a true Odoo Pos Development Company understands the real pressure of billing counters: high rush hours, impatient customers, staff turnover, network issues, and device problems. We build integrations that work in real life—not just in a demo.&lt;/p&gt;

&lt;p&gt;doo Links :-&lt;a href="https://sdlccorp.com/services/odoo/odoo-pos-development-company/" rel="noopener noreferrer"&gt;https://sdlccorp.com/services/odoo/odoo-pos-development-company/&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Odoo Pos Development Services for Custom Buttons and One-Tap Actions</title>
      <dc:creator>Aaron Jones</dc:creator>
      <pubDate>Tue, 03 Feb 2026 11:50:03 +0000</pubDate>
      <link>https://forem.com/aaron_jones_d34b57d1b44ba/odoo-pos-development-services-for-custom-buttons-and-one-tap-actions-1c4n</link>
      <guid>https://forem.com/aaron_jones_d34b57d1b44ba/odoo-pos-development-services-for-custom-buttons-and-one-tap-actions-1c4n</guid>
      <description>&lt;p&gt;`&lt;/p&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.postimg.cc%2FV6skSYb7%2FChat-GPT-Image-Feb-3-2026-05-15-09-PM.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.postimg.cc%2FV6skSYb7%2FChat-GPT-Image-Feb-3-2026-05-15-09-PM.jpg" alt="" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a busy store, the difference between a smooth checkout and a long queue is often just &lt;strong&gt;one tap&lt;/strong&gt;. Cashiers don’t have time to search through menus, open multiple screens, or remember complex steps—especially during rush hours. That’s why businesses now prefer &lt;a href="https://sdlccorp.com/services/odoo/odoo-pos-development-company/" rel="noopener noreferrer"&gt;&lt;strong&gt;Odoo Pos Development Services&lt;/strong&gt;&lt;/a&gt; that focus on speed, simplicity, and smart customization. When your POS has &lt;strong&gt;custom buttons&lt;/strong&gt; and &lt;strong&gt;one-tap actions&lt;/strong&gt;, billing becomes faster, mistakes reduce, and customer experience improves instantly.&lt;/p&gt;

&lt;p&gt;Most stores and restaurants have repeat tasks that happen every day: applying a common discount, adding a popular add-on, selecting a delivery option, applying loyalty points, printing a duplicate receipt, or switching between payment modes. If staff has to do these actions manually in multiple steps, it slows the counter and increases the chance of errors. With professional &lt;strong&gt;Odoo Pos Development Services&lt;/strong&gt;, these repeated actions can be turned into clean, visible buttons on the POS screen—so your team can complete a task in a single click.&lt;/p&gt;

&lt;p&gt;Custom buttons are not just “nice to have.” They are a practical tool to align Odoo POS with your business workflow. Every store has its own style of operations. A grocery store needs quick quantity updates and fast barcode billing. A fashion outlet needs size/variant selection and quick exchanges. A café needs one-tap combos, modifiers, and table orders. A salon might need appointment billing and package redemption. With &lt;strong&gt;Odoo Pos Development Services&lt;/strong&gt;, you can create custom buttons and shortcuts specifically for your industry, so the POS feels natural for your staff.&lt;/p&gt;

&lt;p&gt;One of the biggest benefits is &lt;strong&gt;faster training&lt;/strong&gt;. New employees often struggle because they don’t know where options are hidden. When you build a POS with clear custom actions—like “10% Discount,” “Buy 1 Get 1,” “Happy Hour Offer,” “Membership Redeem,” “Round Off,” or “Free Delivery”—training becomes much simpler. Instead of memorizing steps, staff learns by using simple buttons. With &lt;strong&gt;Odoo Pos Development Services&lt;/strong&gt;, businesses can design a UI that supports real-world staff behavior, not just software logic.&lt;/p&gt;

&lt;p&gt;Custom buttons also help to &lt;strong&gt;control discounting and prevent misuse&lt;/strong&gt;. In many stores, discounts are the easiest way to lose revenue if not monitored properly. With a customized Odoo POS, you can create discount buttons that follow rules—like maximum limit, specific products only, or manager approval for higher discounts. Through &lt;strong&gt;Odoo Pos Development Services&lt;/strong&gt;, you can set permission-based buttons where a cashier can apply small offers, while large discounts require supervisor access. This keeps operations fast while protecting profit margins.&lt;/p&gt;

&lt;p&gt;One-tap actions are equally valuable for payment and customer handling. Many customers want quick payment options like UPI, card, wallet, cash, or split payments. Instead of navigating multiple screens, your POS can show one-tap payment tiles. Similarly, loyalty features become more effective when they are simple. With &lt;strong&gt;Odoo Pos Development Services&lt;/strong&gt;, you can create a one-tap action like “Apply Points,” “Redeem Reward,” or “Add Customer” so your team can use customer programs without slowing the line. This increases membership usage and improves repeat sales.&lt;/p&gt;

&lt;p&gt;Another important area is &lt;strong&gt;returns and exchanges&lt;/strong&gt;. These processes can become confusing for staff when the POS flow is complex. One wrong click can create accounting mismatches. With &lt;strong&gt;Odoo Pos Development Services&lt;/strong&gt;, you can add one-tap buttons like “Exchange Mode,” “Return with Reason,” or “Reprint Receipt,” making the process controlled and easy. If your store deals with frequent returns (fashion, electronics, lifestyle), this customization is a real time-saver.&lt;/p&gt;

&lt;p&gt;Custom buttons also improve &lt;strong&gt;promotion execution&lt;/strong&gt;. Many businesses run seasonal offers, weekend deals, festival discounts, combo pricing, or limited-time coupons. But in practice, offers fail when staff forgets to apply them or applies them wrongly. With &lt;strong&gt;Odoo Pos Development Services&lt;/strong&gt;, you can add promotional buttons directly on the billing screen—so offers are applied consistently and correctly. For example: “Diwali Offer 15%,” “Lunch Combo,” “Student Discount,” or “Buy 2 Save ₹100.” These buttons ensure your promotions work the way you planned.&lt;/p&gt;

&lt;p&gt;For multi-branch businesses, custom actions also bring &lt;strong&gt;standardization&lt;/strong&gt;. One branch might apply discounts differently than another, leading to inconsistent reporting. With &lt;strong&gt;Odoo Pos Development Services&lt;/strong&gt;, you can implement uniform buttons and workflows across stores, ensuring the same process everywhere. This makes it easier for staff transfers, reduces confusion, and keeps management reporting clean.&lt;/p&gt;

&lt;p&gt;Beyond speed, one-tap actions also improve the overall &lt;strong&gt;customer experience&lt;/strong&gt;. When billing is fast and smooth, customers feel the store is professional. Queues reduce, checkout feels modern, and staff confidence increases. Even small changes—like one-tap “Send Digital Receipt,” “Print Gift Receipt,” or “Add Note”—can make your customer service stand out. With &lt;strong&gt;Odoo Pos Development Services&lt;/strong&gt;, you can bring these improvements into your daily operations without complicating the system.&lt;/p&gt;



&lt;p&gt;In short, custom buttons and one-tap actions are about making your POS work like your best cashier—quick, accurate, and stress-free. With &lt;strong&gt;Odoo Pos Development Services&lt;/strong&gt;, you can transform Odoo POS into a faster interface, reduce operational errors, support staff training, and improve store control. Whether you run a retail chain, a restaurant, a café, or a service counter, these small one-tap improvements create a big difference in daily performance.&lt;br&gt;&lt;br&gt;Odoo Links:-&lt;a href="https://sdlccorp.com/services/odoo/odoo-pos-development-company/" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://sdlccorp.com/services/odoo/odoo-pos-development-company/" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://sdlccorp.com/services/odoo/odoo-pos-development-company/" rel="noopener noreferrer"&gt;https://sdlccorp.com/services/odoo/odoo-pos-development-company/&lt;/a&gt;&lt;/p&gt;`

</description>
    </item>
    <item>
      <title>Odoo POS Development for Customer Display and Digital Receipts</title>
      <dc:creator>Aaron Jones</dc:creator>
      <pubDate>Thu, 29 Jan 2026 11:16:21 +0000</pubDate>
      <link>https://forem.com/aaron_jones_d34b57d1b44ba/odoo-pos-development-for-customer-display-and-digital-receipts-59fg</link>
      <guid>https://forem.com/aaron_jones_d34b57d1b44ba/odoo-pos-development-for-customer-display-and-digital-receipts-59fg</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffettvlubd01sxgybafhm.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffettvlubd01sxgybafhm.jpeg" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;`&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;

&lt;p&gt;In today’s retail world, customers expect transparency, speed, and a smooth checkout experience. They don’t just want a bill at the end — they want to see what’s being scanned, confirm prices, check discounts, and receive a receipt they can easily store on their phone. That’s why &lt;a href="https://sdlccorp.com/services/odoo/odoo-pos-development-company/" rel="noopener noreferrer"&gt;&lt;strong&gt;Odoo Pos Development&lt;/strong&gt; &lt;/a&gt;for customer display and digital receipts has become a powerful upgrade for modern stores, cafes, pharmacies, salons, and multi-branch retail chains.&lt;/p&gt;

&lt;p&gt;A well-designed customer display builds trust instantly. When the buyer can see each item added to the cart, the quantity, taxes, and total amount in real time, there’s less confusion and fewer billing disputes. And when you combine that with digital receipts, you reduce printing costs, speed up checkout, and create an easy way to reconnect with customers later — without being pushy.&lt;/p&gt;

&lt;h2&gt;Why Customer Display Matters at Checkout&lt;/h2&gt;

&lt;p&gt;Many stores still rely on a cashier-facing screen only. The customer stands on the other side and waits, unsure what is being billed. This creates small but frequent issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;“This price looks wrong.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;“I didn’t take that item.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;“Did you apply the discount?”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;“Why is GST higher?”&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With &lt;strong&gt;Odoo Pos Development&lt;/strong&gt;, you can enable a dedicated customer display that shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Item name + product image (optional)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Quantity and unit price&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Discounts applied per item or overall&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Taxes/GST breakdown&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Final payable total&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Payment status (cash/card/UPI)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Loyalty points earned (if enabled)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a confident checkout experience, especially during peak hours when staff are working fast and customers want clarity.&lt;/p&gt;

&lt;h2&gt;Customer Display Options You Can Build&lt;/h2&gt;

&lt;p&gt;Different businesses need different setups. &lt;strong&gt;Odoo Pos Development&lt;/strong&gt; allows multiple display options depending on your store environment:&lt;/p&gt;

&lt;h3&gt;1) Second Screen Customer Display&lt;/h3&gt;

&lt;p&gt;If your billing counter has a second monitor, the POS can mirror a clean customer-friendly view on that display. It can show a large total amount, big product names, and clear discount highlights — perfect for retail counters.&lt;/p&gt;

&lt;h3&gt;2) Tablet-Based Customer Display&lt;/h3&gt;

&lt;p&gt;For smaller stores, a tablet can act as the customer display. It’s cost-effective, easy to mount, and works great for cafes, quick service counters, and kiosks.&lt;/p&gt;

&lt;h3&gt;3) QR-Based Live Cart View&lt;/h3&gt;

&lt;p&gt;For modern setups, you can show a QR code on the POS. The customer scans it and sees the cart live on their phone. This reduces hardware dependency and works well for pop-up shops or events.&lt;/p&gt;

&lt;p&gt;All these formats are possible with smart &lt;strong&gt;Odoo Pos Development&lt;/strong&gt;, tailored to your budget and workflow.&lt;/p&gt;

&lt;h2&gt;Digital Receipts: Faster, Cleaner, More Professional&lt;/h2&gt;

&lt;p&gt;Paper receipts are still common — but they come with problems: printer jams, ink costs, paper waste, and fading print. Digital receipts solve all of that. With &lt;strong&gt;Odoo Pos Development&lt;/strong&gt;, you can offer digital receipts through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;SMS receipt link&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Email receipt PDF&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;WhatsApp receipt message (based on integration)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;QR code receipt download on-screen&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Customer portal receipt history (for loyalty customers)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes checkout quicker because the cashier doesn’t need to wait for printing. Customers also prefer it because they can easily find the receipt later for warranty, exchange, or expense claims.&lt;/p&gt;

&lt;h2&gt;The Business Benefits Beyond Just Billing&lt;/h2&gt;

&lt;p&gt;Customer display and digital receipts are not only about convenience — they improve operations and branding.&lt;/p&gt;

&lt;h3&gt;Reduced Billing Mistakes&lt;/h3&gt;

&lt;p&gt;When customers can see the cart live, they catch errors instantly. This lowers refunds, exchanges, and manual corrections.&lt;/p&gt;

&lt;h3&gt;Lower Operational Costs&lt;/h3&gt;

&lt;p&gt;Digital receipts cut printer maintenance, paper rolls, ink, and hardware downtime.&lt;/p&gt;

&lt;h3&gt;Better Brand Image&lt;/h3&gt;

&lt;p&gt;A clean customer screen and professional receipt design make your store look modern. With &lt;strong&gt;Odoo Pos Development&lt;/strong&gt;, you can customize the receipt template to include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Brand logo and colors&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Store address and GST number&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Return/exchange policy&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Social media links&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Thank-you message and offers&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Stronger Customer Data (with consent)&lt;/h3&gt;

&lt;p&gt;Digital receipts can encourage customers to share phone/email voluntarily. That helps you build a clean customer database for loyalty programs and repeat sales — without forcing anyone.&lt;/p&gt;

&lt;h2&gt;Loyalty, Feedback, and Repeat Sales Made Easy&lt;/h2&gt;

&lt;p&gt;Once you have digital receipts, you can take the experience further with &lt;strong&gt;Odoo Pos Development&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Add loyalty points summary on the customer display&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Show “You saved ₹___ today” to increase satisfaction&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Include feedback links in receipts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Send next-visit coupon codes via digital receipt&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable membership benefits and targeted offers&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns the receipt into a soft marketing tool — useful, not spammy.&lt;/p&gt;

&lt;h2&gt;Secure, Reliable, and Store-Friendly Implementation&lt;/h2&gt;

&lt;p&gt;A good customer display system must be stable and secure. In &lt;strong&gt;Odoo Pos Development&lt;/strong&gt;, you can implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Role-based settings (cashier vs manager)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Masked customer phone/email on screen for privacy&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Offline mode support with queued digital receipts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sync rules so receipts are sent automatically once internet returns&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;That means even if the store network drops, billing continues smoothly and receipts don’t get lost.&lt;br&gt;&lt;br&gt;Odoo Links:-&lt;a href="https://sdlccorp.com/services/odoo/odoo-pos-development-company/" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://sdlccorp.com/services/odoo/odoo-pos-development-company/" rel="noopener noreferrer"&gt;https://sdlccorp.com/services/odoo/odoo-pos-development-company/&lt;/a&gt;&lt;/p&gt;`

</description>
    </item>
    <item>
      <title>Hire Odoo Developers Who Can Customize Without Breaking Core</title>
      <dc:creator>Aaron Jones</dc:creator>
      <pubDate>Tue, 27 Jan 2026 07:11:57 +0000</pubDate>
      <link>https://forem.com/aaron_jones_d34b57d1b44ba/hire-odoo-developers-who-can-customize-without-breaking-core-14mp</link>
      <guid>https://forem.com/aaron_jones_d34b57d1b44ba/hire-odoo-developers-who-can-customize-without-breaking-core-14mp</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn5jg0vqwzbyty3jeyxc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn5jg0vqwzbyty3jeyxc.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Odoo is loved for one big reason: it’s flexible. You can start small with a few modules and grow into a full ERP that runs sales, inventory, accounting, HR, procurement, manufacturing, and customer support in one place. But that same flexibility can become a trap when customization is done the wrong way. One rushed change inside core files, one “quick fix” with messy overrides, and suddenly upgrades are painful, features break, performance drops, and your team loses trust in the system. If you want Odoo customized cleanly, safely, and future-ready, you need to Hire Odoo Developers who can customize without breaking core.&lt;/p&gt;

&lt;p&gt;The goal isn’t just to “make Odoo match your process.” The goal is to do it in a way that protects stability, keeps upgrades smooth, and avoids technical debt. When you Hire Odoo Developers with real Odoo engineering experience, they build solutions that feel custom—while still respecting Odoo’s architecture.&lt;/p&gt;

&lt;p&gt;Why “breaking core” happens (and how the right team prevents it)&lt;br&gt;
Many implementation problems happen when developers treat Odoo like a normal web app and start modifying default modules directly. It might work today, but it creates a hidden time bomb. The moment you update Odoo or install another module, conflicts show up. Reports break, menus disappear, permissions get messy, or workflows stop working.&lt;/p&gt;

&lt;p&gt;When you Hire Odoo Developers who understand best practices, they avoid touching the core codebase. Instead, they extend Odoo the correct way—using custom modules, inheritance patterns, clean overrides, and proper dependency management. That means you get the flexibility you want without losing the reliability you need.&lt;/p&gt;

&lt;p&gt;What safe customization looks like in real life&lt;br&gt;
Businesses don’t customize Odoo “for fun.” They customize it to remove friction and improve speed. Here are common needs—and how specialist developers handle them safely:&lt;/p&gt;

&lt;p&gt;1) Custom fields and business rules (without chaos)&lt;br&gt;
You may need extra fields for quotation approvals, vendor terms, product specs, GST details, or HR compliance. A skilled team adds these fields through clean module extensions, validates inputs, and ensures they appear only where needed—so the UI stays simple.&lt;/p&gt;

&lt;p&gt;2) Streamlined screens and fewer clicks&lt;br&gt;
Many teams feel ERP is slow because pages are cluttered. When you Hire Odoo Developers, they can simplify forms, hide unused elements by role, and create smart defaults—without deleting or hacking core views.&lt;/p&gt;

&lt;p&gt;3) Approval workflows that match your company&lt;br&gt;
You might need multi-level approvals for purchases, discounts, refunds, or vendor onboarding. Odoo specialists implement approvals using activities, automated actions, and controlled states—keeping everything traceable and upgrade-safe.&lt;/p&gt;

&lt;p&gt;4) Custom reports and dashboards leadership can trust&lt;br&gt;
You don’t want manual reporting at month end. Specialist developers create clean reporting models, pivot views, and dashboards built on real data—so finance and operations make decisions confidently.&lt;/p&gt;

&lt;p&gt;5) Integrations without breaking your ERP&lt;br&gt;
Payment gateways, shipping providers, WhatsApp/email tools, external websites, BI tools, or third-party CRMs—integrations should be stable and well-logged. When you Hire Odoo Developers, they build integrations with error handling, retries, clear logs, and secure access patterns, not fragile scripts.&lt;/p&gt;

&lt;p&gt;The signs you should Hire Odoo Developers (and not “general developers”)&lt;br&gt;
Odoo is a framework with its own structure. A general developer might know Python, but Odoo requires deeper understanding of ORM patterns, module design, security rules, views, and upgrade behavior. If you want customization without breaking core, look for these qualities:&lt;/p&gt;

&lt;p&gt;They build custom modules, not direct edits to standard modules&lt;/p&gt;

&lt;p&gt;They follow clean inheritance patterns and avoid fragile overrides&lt;/p&gt;

&lt;p&gt;They understand access control (groups, record rules) and security&lt;/p&gt;

&lt;p&gt;They write maintainable code with documentation and version control&lt;/p&gt;

&lt;p&gt;They test upgrades and avoid changes that block future updates&lt;/p&gt;

&lt;p&gt;They optimize performance, especially in large databases&lt;/p&gt;

&lt;p&gt;This is why it’s worth it to Hire Odoo Developers who specialize—because their work lasts.&lt;/p&gt;

&lt;p&gt;How a “core-safe” Odoo project should be delivered&lt;br&gt;
A professional team doesn’t jump straight into development. They follow a structured approach that reduces risk:&lt;/p&gt;

&lt;p&gt;Step 1: Process mapping and scope control&lt;br&gt;
They identify what should be configuration vs customization. Not everything needs code. The best systems keep customization minimal and meaningful.&lt;/p&gt;

&lt;p&gt;Step 2: Build on staging first&lt;br&gt;
They set up a proper development flow: staging environment, Git versioning, module structure, and a rollback plan. This protects your live business.&lt;/p&gt;

&lt;p&gt;Step 3: Implement clean extensions&lt;br&gt;
New features are delivered as separate modules with clear dependencies. This prevents core modifications and keeps maintenance simple.&lt;/p&gt;

&lt;p&gt;Step 4: Testing with real scenarios&lt;br&gt;
They test actual business cases: purchase approvals, returns, invoicing, partial deliveries, taxes, and edge cases—so the system behaves correctly under pressure.&lt;/p&gt;

&lt;p&gt;Step 5: Documentation + handover&lt;br&gt;
You receive clear documentation: what was changed, why it was changed, and how to maintain it. This keeps you free from vendor lock-in.&lt;/p&gt;

&lt;p&gt;What you gain when you Hire Odoo Developers who protect core&lt;br&gt;
When customization is done right, your business gets the best of both worlds: Odoo’s stability + your process fit.&lt;/p&gt;

&lt;p&gt;Upgrades become smoother and less risky&lt;/p&gt;

&lt;p&gt;Performance stays fast as your data grows&lt;/p&gt;

&lt;p&gt;Your team works inside one clean system, not workarounds&lt;/p&gt;

&lt;p&gt;Reporting becomes accurate and real-time&lt;/p&gt;

&lt;p&gt;New requirements can be added without breaking existing features&lt;/p&gt;

&lt;p&gt;If your plan is to scale with Odoo for years, don’t gamble with shortcuts. Hire Odoo Developers who can customize without breaking core, and you’ll get an ERP that stays stable, upgrade-ready, and easy for your team to use every day.&lt;/p&gt;

&lt;p&gt;Odoo Links:-&lt;a href="https://sdlccorp.com/services/odoo-services/hire-odoo-developer/" rel="noopener noreferrer"&gt;https://sdlccorp.com/services/odoo-services/hire-odoo-developer/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;br&gt;
 &lt;br&gt;
 &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Data Inconsistency Due to Missing Constraints (Odoo)</title>
      <dc:creator>Aaron Jones</dc:creator>
      <pubDate>Tue, 13 Jan 2026 09:46:26 +0000</pubDate>
      <link>https://forem.com/aaron_jones_d34b57d1b44ba/computed-fields-causing-infinite-recomputations-489c</link>
      <guid>https://forem.com/aaron_jones_d34b57d1b44ba/computed-fields-causing-infinite-recomputations-489c</guid>
      <description>&lt;p&gt;&lt;strong&gt;Why This Problem Happens (Root Cause)&lt;/strong&gt;&lt;br&gt;
In Odoo, data inconsistency occurs when:&lt;/p&gt;

&lt;p&gt;❌ No SQL constraints exist at database level&lt;br&gt;
❌ No Python constraints exist at ORM level&lt;br&gt;
❌ Imports, RPC calls, or automated scripts bypass UI validations&lt;br&gt;
❌ Multiple users create records concurrently&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Duplicate records&lt;/li&gt;
&lt;li&gt;Invalid field values&lt;/li&gt;
&lt;li&gt;Broken business logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Identify What Must Be Unique or Valid&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before coding, clearly define:&lt;br&gt;
&lt;strong&gt;Rule of thumb&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SQL constraint&lt;/strong&gt; → absolute rule (never allowed)&lt;br&gt;
&lt;strong&gt;Python constraint&lt;/strong&gt; → business logic rule&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Add SQL Constraints (Database-Level Protection)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Use Case: Prevent Duplicate Employee Codes&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;❌ Problem (No constraint)&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;employee_code = fields.Char()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Multiple records can have same code.&lt;br&gt;
Solution:&lt;code&gt;_sql_constraints&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class HrEmployee(models.Model):
    _inherit = 'hr.employee'

    employee_code = fields.Char(required=True)

    _sql_constraints = [
        (
            'employee_code_unique',
            'unique(employee_code)',
            'Employee Code must be unique.'
        )
    ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why SQL Constraints Matter&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Enforced at database level&lt;br&gt;
Works for:&lt;br&gt;
Imports&lt;br&gt;
RPC calls&lt;br&gt;
Background jobs&lt;br&gt;
Fast &amp;amp; reliable&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Add Python Constraints (Business Logic Validation)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Use Case: Start Date must be before End Date&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Problem&lt;/strong&gt;&lt;br&gt;
Users can save invalid date ranges.&lt;/p&gt;

&lt;p&gt;Solution: &lt;code&gt;@api.constrains&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from odoo import models, fields, api
from odoo.exceptions import ValidationError

class ProjectContract(models.Model):
    _name = 'project.contract'

    start_date = fields.Date(required=True)
    end_date = fields.Date(required=True)

    @api.constrains('start_date', 'end_date')
    def _check_date_range(self):
        for record in self:
            if record.start_date and record.end_date:
                if record.start_date &amp;gt; record.end_date:
                    raise ValidationError(
                        "Start Date must be before End Date."
                    )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why Python Constraints Matter&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allows complex logic&lt;/li&gt;
&lt;li&gt;Clear error messages&lt;/li&gt;
&lt;li&gt;Business-rule focused&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Prevent Duplicate Records Based on Multiple Fields&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Use Case: Same Customer + Product not allowed&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;SQL Constraint (Multi-column)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_sql_constraints = [
    (
        'customer_product_unique',
        'unique(customer_id, product_id)',
        'This customer already has this product.'
    )
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5: Validate Data During Create &amp;amp; Write (Extra Safety)&lt;/strong&gt;&lt;br&gt;
Sometimes constraints alone aren’t enough.&lt;br&gt;
&lt;strong&gt;Example: Prevent Negative Amounts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.model
def create(self, vals):
    if vals.get('amount', 0) &amp;lt; 0:
        raise ValidationError("Amount cannot be negative.")
    return super().create(vals)

def write(self, vals):
    if 'amount' in vals and vals['amount'] &amp;lt; 0:
        raise ValidationError("Amount cannot be negative.")
    return super().write(vals)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6: Handle Existing Duplicate Data (Very Important)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Adding constraints will FAIL if bad data already exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step-by-step cleanup approach:&lt;/strong&gt;&lt;br&gt;
1 Detect duplicates&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT employee_code, COUNT(*)
FROM hr_employee
GROUP BY employee_code
HAVING COUNT(*) &amp;gt; 1;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2 Fix manually or via script&lt;br&gt;
3 Then add constraints&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7: Combine SQL + Python Constraints (Best Practice)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Scenario&lt;/strong&gt;                                 &lt;strong&gt;Solution&lt;/strong&gt;&lt;br&gt;
Absolute uniqueness                        SQL constraint&lt;br&gt;
Business rules                              Python constraint&lt;br&gt;
User-friendly errors                       Python constraint&lt;br&gt;
Performance                            SQL constraint&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use BOTH whenever possible&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Final Best-Practice Checklist&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;_sql_constraints&lt;/code&gt; for critical rules&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;@api.constrains&lt;/code&gt; for logic validation&lt;/li&gt;
&lt;li&gt;Never rely only on UI validation&lt;/li&gt;
&lt;li&gt;Clean existing data before adding constraints&lt;/li&gt;
&lt;li&gt;Test imports &amp;amp; automated jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real-World Impact After Fix&lt;br&gt;
🚫No duplicate records&lt;br&gt;
⚡Faster validations&lt;br&gt;
🔒Strong data integrity&lt;br&gt;
🧠Stable business logic&lt;br&gt;
&lt;a href="https://sdlccorp.com/services/odoo-services/odoo-development-company/" rel="noopener noreferrer"&gt;odoo development company&lt;/a&gt; &lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>python</category>
      <category>sql</category>
    </item>
    <item>
      <title>Computed Fields Causing Infinite Recomputations(Odoo)</title>
      <dc:creator>Aaron Jones</dc:creator>
      <pubDate>Fri, 09 Jan 2026 05:00:43 +0000</pubDate>
      <link>https://forem.com/aaron_jones_d34b57d1b44ba/computed-fields-causing-infinite-recomputationsodoo-4ck5</link>
      <guid>https://forem.com/aaron_jones_d34b57d1b44ba/computed-fields-causing-infinite-recomputationsodoo-4ck5</guid>
      <description>&lt;p&gt;&lt;strong&gt;Problem Statement:&lt;/strong&gt; Poorly designed computed fields trigger unnecessary recomputations or infinite loops due to incorrect dependency declarations (@api.depends), leading to performance issues and unstable behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why This Problem Happens in Odoo&lt;/strong&gt;&lt;br&gt;
In Odoo, computed fields are recalculated whenever fields listed in &lt;code&gt;@api.depends&lt;/code&gt; change.&lt;/p&gt;

&lt;p&gt;Infinite or excessive recomputation occurs when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A computed field depends on itself&lt;/li&gt;
&lt;li&gt;The compute method writes to other fields&lt;/li&gt;
&lt;li&gt;ORM &lt;code&gt;write()&lt;/code&gt; is used inside compute&lt;/li&gt;
&lt;li&gt;Dependencies are too broad or incorrect&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;store=True&lt;/code&gt; is misused&lt;/li&gt;
&lt;li&gt;Parent ↔ child fields depend on each other&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow forms&lt;/li&gt;
&lt;li&gt;High CPU usage&lt;/li&gt;
&lt;li&gt;UI hangs&lt;/li&gt;
&lt;li&gt;Random crashes in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 1 Identify the Problematic Computed Field&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Common Symptoms&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Form view keeps loading&lt;/li&gt;
&lt;li&gt;Server CPU spikes&lt;/li&gt;
&lt;li&gt;Logs show repeated “Computing field…”&lt;/li&gt;
&lt;li&gt;Issue disappears when field is removed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enable logs:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--log-level=debug&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Look for:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Computing field &amp;lt;field_name&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;repeating continuously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 Identify Common WRONG Patterns&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;WRONG 1: Field Depends on Itself&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('total')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Causes infinite recomputation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WRONG 2: Writing Other Fields Inside Compute&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('price', 'qty')
def _compute_amount(self):
    for rec in self:
        rec.amount = rec.price * rec.qty
        rec.discount = rec.amount * 0.1  #  BAD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each write triggers recomputation again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WRONG 3: Using &lt;code&gt;write()&lt;/code&gt; Inside Compute&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('line_ids.amount')
def _compute_total(self):
    for rec in self:
        rec.write({'total': sum(rec.line_ids.mapped('amount'))})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Causes recursion + DB writes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 Define Correct and Minimal Dependencies&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Correct Dependency Declaration&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rules:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Depend only on source fields&lt;/li&gt;
&lt;li&gt;Never include the computed field itself&lt;/li&gt;
&lt;li&gt;Avoid &lt;code&gt;@api.depends('*')&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 4 Keep Compute Methods PURE (No Side Effects)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Correct Pattern&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total = fields.Float(compute='_compute_total', store=True)

@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty if rec.price and rec.qty else 0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;No writes&lt;/li&gt;
&lt;li&gt;No ORM calls&lt;/li&gt;
&lt;li&gt;Safe and predictable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 5 Split Logic into Multiple Computed Fields&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;GOOD Design&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amount = fields.Float(compute='_compute_amount', store=True)
discount = fields.Float(compute='_compute_discount', store=True)

@api.depends('price', 'qty')
def _compute_amount(self):
    for rec in self:
        rec.amount = rec.price * rec.qty

@api.depends('amount')
def _compute_discount(self):
    for rec in self:
        rec.discount = rec.amount * 0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;No circular dependency&lt;/li&gt;
&lt;li&gt;Clean separation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 6 Use store=True Only When Necessary&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;BAD&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total = fields.Float(compute='_compute_total', store=True)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Field is UI-only&lt;/li&gt;
&lt;li&gt;Not searched or grouped&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;GOOD&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total = fields.Float(compute='_compute_total')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;store=True&lt;/code&gt; only when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Used in search domains&lt;/li&gt;
&lt;li&gt;Used in reporting / group by&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 7 Use @api.onchange for UI Logic (NOT compute)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;WRONG&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('qty')
def _compute_price(self):
    for rec in self:
        rec.price = rec.qty * 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;RIGHT&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.onchange('qty')
def _onchange_qty(self):
    self.price = self.qty * 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;No recomputation&lt;/li&gt;
&lt;li&gt;UI-only behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 8 Fix Parent–Child Dependency Loops&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;BAD (Hidden Loop)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('line_ids.total')
def _compute_total(self):
    for rec in self:
        rec.total = sum(rec.line_ids.mapped('total'))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;line.total&lt;/code&gt; depends on parent → infinite loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SAFE VERSION&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('line_ids.price', 'line_ids.qty')
def _compute_total(self):
    for rec in self:
        rec.total = sum(
            line.price * line.qty for line in rec.line_ids
        )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Direct dependency&lt;/li&gt;
&lt;li&gt;No recursion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 9 Use Constraints Instead of Computed Fields for Validation&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;BAD&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('qty')
def _compute_check(self):
    if self.qty &amp;lt; 0:
        raise ValidationError("Invalid")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GOOD&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.constrains('qty')
def _check_qty(self):
    for rec in self:
        if rec.qty &amp;lt; 0:
            raise ValidationError("Quantity cannot be negative")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;No recompute&lt;/li&gt;
&lt;li&gt;Correct validation layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 10 Final Safe Template (Best Practice)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total = fields.Float(
    compute='_compute_total',
    store=True,
)

@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = (rec.price or 0.0) * (rec.qty or 0.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pure compute&lt;/li&gt;
&lt;li&gt;Correct dependencies&lt;/li&gt;
&lt;li&gt;Production-safe&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://sdlccorp.com/services/odoo-services/odoo-development-company/" rel="noopener noreferrer"&gt;Odoo Development&lt;/a&gt;, infinite recomputation issues are almost always caused by impure computed fields fields that write data, depend on themselves, or mix business logic with calculation logic. The fix is strict discipline: pure compute methods, minimal dependencies, no ORM writes, and correct separation of UI logic and validation. When computed fields are treated as read-only calculations, Odoo’s ORM remains fast, stable, and predictable even at scale.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>performance</category>
      <category>python</category>
    </item>
    <item>
      <title>Computed Fields Causing Infinite Recomputations (odoo)</title>
      <dc:creator>Aaron Jones</dc:creator>
      <pubDate>Wed, 07 Jan 2026 08:41:21 +0000</pubDate>
      <link>https://forem.com/aaron_jones_d34b57d1b44ba/computed-fields-causing-infinite-recomputations-odoo-1556</link>
      <guid>https://forem.com/aaron_jones_d34b57d1b44ba/computed-fields-causing-infinite-recomputations-odoo-1556</guid>
      <description>&lt;p&gt;&lt;strong&gt;Problem Statement:&lt;/strong&gt; Poorly designed computed fields trigger unnecessary recomputations or infinite loops due to incorrect dependency declarations (@api.depends), leading to performance issues and unstable behavior&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 Identify the problematic computed field&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Typical symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Form view never finishes loading&lt;/li&gt;
&lt;li&gt;CPU spikes when opening records&lt;/li&gt;
&lt;li&gt;Logs show repeated recomputation&lt;/li&gt;
&lt;li&gt;Server freezes after record creation/update&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enable logs:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--log-level=debug&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Look for repeating:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Computing field x_field_name&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 Identify common WRONG patterns (most bugs come from here)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;WRONG: Computed field depends on itself&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('total_amount')
def _compute_total_amount(self):
    for rec in self:
        rec.total_amount = rec.price * rec.qty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This causes infinite recomputation&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WRONG: Writing to other fields inside compute&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty
        rec.subtotal = rec.total * 0.9  # BAD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WRONG: Using write() inside compute&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('line_ids.amount')
def _compute_amount(self):
    for rec in self:
        rec.write({'total': sum(rec.line_ids.mapped('amount'))})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Triggers recursive recompute + database writes&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 Define correct dependencies ONLY&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Correct dependency declaration&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rules:&lt;/strong&gt;&lt;br&gt;
Depend only on source fields&lt;br&gt;
Never depend on computed field itself&lt;br&gt;
Avoid &lt;code&gt;@api.depends('*')&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 Never modify other fields inside compute&lt;/strong&gt;&lt;br&gt;
Correct approach: separate computed fields&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total = fields.Float(compute='_compute_total', store=True)
subtotal = fields.Float(compute='_compute_subtotal', store=True)

@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty

@api.depends('total')
def _compute_subtotal(self):
    for rec in self:
        rec.subtotal = rec.total * 0.9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Clean&lt;/li&gt;
&lt;li&gt;Predictable&lt;/li&gt;
&lt;li&gt;No loops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 5 Use store=True ONLY when required&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;BAD&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total = fields.Float(compute='_compute_total', store=True)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Field is rarely searched&lt;/li&gt;
&lt;li&gt;Used only in UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;GOOD&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total = fields.Float(compute='_compute_total')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;store=True&lt;/code&gt; increases recomputation cost&lt;br&gt;
Use only when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Searching&lt;/li&gt;
&lt;li&gt;Grouping&lt;/li&gt;
&lt;li&gt;Reporting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 6 Avoid ORM writes in compute (use onchange instead)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;WRONG (compute)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('qty')
def _compute_price(self):
    for rec in self:
        rec.price = rec.qty * 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;RIGHT (onchange)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.onchange('qty')
def _onchange_qty(self):
    self.price = self.qty * 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UI-only logic&lt;br&gt;
No recomputation loops&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7 Use @api.depends_context when needed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If compute depends on company, language, or user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends_context('company')
@api.depends('amount')
def _compute_tax(self):
    for rec in self:
        rec.tax = rec.amount * rec.company_id.tax_rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prevents unnecessary recomputation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8 Fix One2many / Many2one loops&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;COMMON LOOP&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('line_ids.total')
def _compute_total(self):
    for rec in self:
        rec.total = sum(rec.line_ids.mapped('total'))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;line.total&lt;/code&gt; depends back on parent → loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SAFE VERSION&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('line_ids.price', 'line_ids.qty')
def _compute_total(self):
    for rec in self:
        rec.total = sum(
            line.price * line.qty for line in rec.line_ids
        )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 9 Use SQL constraints instead of compute when possible&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;BAD compute for validation&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.depends('qty')
def _compute_check(self):
    if self.qty &amp;lt; 0:
        raise ValidationError("Invalid")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GOOD constraint&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.constrains('qty')
def _check_qty(self):
    for rec in self:
        if rec.qty &amp;lt; 0:
            raise ValidationError("Quantity cannot be negative")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 10 Final Safe Computed Field Template (Best Practice)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total = fields.Float(
    compute='_compute_total',
    store=True,
)

@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty if rec.price and rec.qty else 0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;No writes&lt;/li&gt;
&lt;li&gt;No recursion&lt;/li&gt;
&lt;li&gt;Correct dependencies&lt;/li&gt;
&lt;li&gt;Safe for production&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Odoo, infinite recomputation issues are almost always caused by impure computed fields fields that write data, depend on themselves, or have poorly defined dependencies. The fix is simple but strict: keep compute methods pure, declare minimal dependencies, avoid ORM writes, and separate UI logic from stored logic. When computed fields are treated as read-only calculations instead of business logic containers, performance stabilizes, recomputation stops, and your &lt;a href="https://sdlccorp.com/services/odoo-services/odoo-development-company/" rel="noopener noreferrer"&gt;Odoo development  &lt;/a&gt;system becomes predictable and scalable.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>performance</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Odoo Cron Jobs Failing Silently: How I Debugged and Fixed Background Tasks in Production</title>
      <dc:creator>Aaron Jones</dc:creator>
      <pubDate>Tue, 30 Dec 2025 11:43:05 +0000</pubDate>
      <link>https://forem.com/aaron_jones_d34b57d1b44ba/odoo-cron-jobs-failing-silently-how-i-debugged-and-fixed-background-tasks-in-production-31na</link>
      <guid>https://forem.com/aaron_jones_d34b57d1b44ba/odoo-cron-jobs-failing-silently-how-i-debugged-and-fixed-background-tasks-in-production-31na</guid>
      <description>&lt;p&gt;I recently ran into this issue in a production system, and after debugging it end-to-end, I realized that Odoo cron failures are usually silent by design unless we handle them explicitly.&lt;/p&gt;

&lt;p&gt;This post walks through what actually goes wrong, why it happens, and how to fix it properly with code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Real Problem&lt;/strong&gt;&lt;br&gt;
Odoo cron jobs run in the background under the system user. When an exception occurs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Odoo may rollback the transaction&lt;/li&gt;
&lt;li&gt;The job execution stops&lt;/li&gt;
&lt;li&gt;The error is not surfaced in the UI&lt;/li&gt;
&lt;li&gt;The cron keeps rescheduling without doing any useful work
This makes cron failures hard to detect and easy to ignore, especially in large systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 1: First Check If the Cron Is Even Running&lt;/strong&gt;&lt;br&gt;
Before touching code, verify the basics.&lt;/p&gt;

&lt;p&gt;Go to:&lt;br&gt;
Settings → Technical → Automation → Scheduled Actions&lt;/p&gt;

&lt;p&gt;Check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the cron Active?&lt;/li&gt;
&lt;li&gt;Is the Next Execution Date updating?&lt;/li&gt;
&lt;li&gt;Is numbercall exhausted?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the next execution date never changes, the cron is not running at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Enable Cron-Specific Logging (Often Missed)&lt;/strong&gt;&lt;br&gt;
By default, cron logs are easy to miss.&lt;/p&gt;

&lt;p&gt;Update your odoo.conf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;log_level = info
log_handler = :INFO,odoo.addons.base.models.ir_cron:DEBUG

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the server.&lt;br&gt;
Now cron activity and failures will actually appear in logs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Why Crons Fail Silently (The Core Reason)&lt;/strong&gt;&lt;br&gt;
Most cron methods are written like normal business logic.&lt;/p&gt;

&lt;p&gt;That’s the mistake.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typical problematic code&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def run_daily_job(self):
    orders = self.env['sale.order'].search([])
    for order in orders:
        order.process()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If process() fails even once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The entire cron crashes&lt;/li&gt;
&lt;li&gt;No error is visible unless logging is perfect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Fix It With Proper Error Handling (Mandatory)&lt;/strong&gt;&lt;br&gt;
Always wrap cron logic in try–except.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import logging
from odoo import models, api

_logger = logging.getLogger(__name__)

class SaleCron(models.Model):
    _inherit = 'sale.order'

    @api.model
    def run_daily_job(self):
        try:
            orders = self.search([])
            for order in orders:
                order.process()

            _logger.info("Sale cron executed successfully")

        except Exception as e:
            _logger.error(
                "Sale cron failed: %s", str(e), exc_info=True
            )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prevents silent failures&lt;/li&gt;
&lt;li&gt;Preserves stack traces&lt;/li&gt;
&lt;li&gt;Makes debugging possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Prevent One Bad Record From Killing the Cron&lt;/strong&gt;&lt;br&gt;
A very common issue is one corrupted record stopping everything.&lt;/p&gt;

&lt;p&gt;Use database savepoints.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.model
def run_daily_job(self):
    orders = self.search([])
    for order in orders:
        try:
            with self.env.cr.savepoint():
                order.process()
        except Exception as e:
            _logger.error(
                "Failed for order %s: %s", order.name, str(e)
            )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One record fails → logged&lt;/li&gt;
&lt;li&gt;Remaining records still process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 6: Avoid User-Dependent Code in Crons&lt;/strong&gt;&lt;br&gt;
Crons do not run as the logged-in user.&lt;/p&gt;

&lt;p&gt;This often breaks code like:&lt;br&gt;
&lt;code&gt;self.env.user.email&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Instead, explicitly fetch a known user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;admin = self.env.ref('base.user_admin')
email = admin.partner_id.email

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never assume UI user context in background jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7: Make Sure the Cron Doesn’t Auto-Stop&lt;/strong&gt;&lt;br&gt;
If numbercall reaches 0, the cron never runs again.&lt;/p&gt;

&lt;p&gt;Correct XML definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;record id="ir_cron_daily_sale_job" model="ir.cron"&amp;gt;
    &amp;lt;field name="name"&amp;gt;Daily Sale Processing&amp;lt;/field&amp;gt;
    &amp;lt;field name="model_id" ref="sale.model_sale_order"/&amp;gt;
    &amp;lt;field name="state"&amp;gt;code&amp;lt;/field&amp;gt;
    &amp;lt;field name="code"&amp;gt;model.run_daily_job()&amp;lt;/field&amp;gt;
    &amp;lt;field name="interval_number"&amp;gt;1&amp;lt;/field&amp;gt;
    &amp;lt;field name="interval_type"&amp;gt;days&amp;lt;/field&amp;gt;
    &amp;lt;field name="numbercall"&amp;gt;-1&amp;lt;/field&amp;gt;
    &amp;lt;field name="active"&amp;gt;True&amp;lt;/field&amp;gt;
&amp;lt;/record&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-1 ensures the cron runs forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8: Add Email Alerts for Production Safety&lt;/strong&gt;&lt;br&gt;
Logs are not enough in production.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def notify_admin(self, error):
    mail = self.env['mail.mail'].create({
        'subject': 'Odoo Cron Job Failed',
        'body_html': f'&amp;lt;p&amp;gt;{error}&amp;lt;/p&amp;gt;',
        'email_to': 'admin@company.com',
    })
    mail.send()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trigger this inside the except block.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 9: Always Test Crons Manually&lt;/strong&gt;&lt;br&gt;
Before trusting a cron:&lt;br&gt;
&lt;code&gt;odoo shell -d your_database&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;env['sale.order'].run_daily_job()&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
If it fails here, it will fail in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 10: Watch for Database Locks (Hidden Cron Killers)&lt;/strong&gt;&lt;br&gt;
Long-running queries and locks can block cron execution.&lt;br&gt;
&lt;code&gt;SELECT * FROM pg_stat_activity WHERE state = 'active';&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Slow ORM queries often look harmless but can freeze background jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;br&gt;
Odoo cron jobs don’t fail loudly by default.&lt;br&gt;
They fail quietly, repeatedly, and dangerously.&lt;/p&gt;

&lt;p&gt;The fix is not complex, but it must be intentional:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always handle exceptions&lt;/li&gt;
&lt;li&gt;Log aggressively&lt;/li&gt;
&lt;li&gt;Use savepoints&lt;/li&gt;
&lt;li&gt;Avoid user assumptions&lt;/li&gt;
&lt;li&gt;Monitor cron health&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once these patterns are in place, cron jobs become predictable, observable, and safe for production.&lt;/p&gt;

</description>
      <category>odoo</category>
      <category>debugging</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Integration Failures and API Callout Issues in Odoo</title>
      <dc:creator>Aaron Jones</dc:creator>
      <pubDate>Mon, 29 Dec 2025 12:28:09 +0000</pubDate>
      <link>https://forem.com/aaron_jones_d34b57d1b44ba/integration-failures-and-api-callout-issues-in-odoo-3pij</link>
      <guid>https://forem.com/aaron_jones_d34b57d1b44ba/integration-failures-and-api-callout-issues-in-odoo-3pij</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;API integrations are a critical part of any modern Odoo implementation. Odoo frequently connects with payment gateways, CRMs, shipping providers, accounting tools, and third-party services using REST or SOAP APIs.&lt;/p&gt;

&lt;p&gt;However, many Odoo developers face intermittent integration failures that are hard to reproduce and even harder to debug. These failures often lead to data sync issues, duplicate records, broken automation, and production incidents.&lt;/p&gt;

&lt;p&gt;In this article, we will break down real-world Odoo API integration problems, explain why they happen, and show production-ready solutions and prevention strategies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem Statement: Why Odoo API Integrations Fail&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Odoo external integrations commonly fail due to:&lt;/li&gt;
&lt;li&gt;Hardcoded API credentials and endpoints&lt;/li&gt;
&lt;li&gt;Missing or expired authentication tokens&lt;/li&gt;
&lt;li&gt;Unhandled HTTP errors and timeouts&lt;/li&gt;
&lt;li&gt;External API contract changes&lt;/li&gt;
&lt;li&gt;No retry or idempotency strategy&lt;/li&gt;
&lt;li&gt;Lack of monitoring and logging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These issues make integrations feel “random” when in reality they follow clear technical patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Fix the Root Cause — Authentication and Endpoint Configuration&lt;/strong&gt;&lt;br&gt;
Common Odoo Integration Mistake&lt;/p&gt;

&lt;p&gt;Hardcoding API URLs, tokens, or secrets directly inside Python files.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This leads to:&lt;/li&gt;
&lt;li&gt;Token expiration failures&lt;/li&gt;
&lt;li&gt;Sandbox vs production confusion&lt;/li&gt;
&lt;li&gt;Security risks&lt;/li&gt;
&lt;li&gt;Difficult maintenance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Recommended Odoo Best Practice&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Store all external integration configuration using System Parameters or a configuration model.&lt;/p&gt;

&lt;p&gt;Odoo Path:&lt;br&gt;
Settings → Technical → Parameters → System Parameters&lt;/p&gt;

&lt;p&gt;Typical parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;external_api.base_url&lt;/li&gt;
&lt;li&gt;external_api.access_token&lt;/li&gt;
&lt;li&gt;external_api.timeout
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;base_url = self.env['ir.config_parameter'].sudo().get_param('external_api.base_url')
token = self.env['ir.config_parameter'].sudo().get_param('external_api.access_token')

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;SEO takeaway: Proper authentication management prevents the most common Odoo API failures.&lt;/p&gt;

&lt;p&gt;Step 2: Build a Reusable Odoo API Service Layer&lt;br&gt;
The Problem&lt;/p&gt;

&lt;p&gt;Scattered API calls across models, cron jobs, and controllers cause:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Duplicate logic&lt;/li&gt;
&lt;li&gt;Inconsistent error handling&lt;/li&gt;
&lt;li&gt;Difficult debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Solution&lt;/strong&gt;&lt;br&gt;
Create a single reusable API service responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP status validation&lt;/li&gt;
&lt;li&gt;Timeout handling&lt;/li&gt;
&lt;li&gt;Safe JSON parsing&lt;/li&gt;
&lt;li&gt;Clear error messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example: Odoo API Callout Wrapper&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import requests
from odoo import models
from odoo.exceptions import UserError

class ExternalApiService(models.AbstractModel):
    _name = 'external.api.service'
    _description = 'External API Service Layer'

    def send_request(self, method, endpoint, payload=None, timeout=20):
        base_url = self.env['ir.config_parameter'].sudo().get_param('external_api.base_url')
        token = self.env['ir.config_parameter'].sudo().get_param('external_api.access_token')

        headers = {
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        }

        try:
            response = requests.request(
                method, f"{base_url}{endpoint}",
                json=payload, headers=headers, timeout=timeout
            )
        except requests.exceptions.RequestException as e:
            raise UserError(f"API call failed: {str(e)}")

        if response.status_code &amp;gt;= 400:
            raise UserError(f"API Error {response.status_code}: {response.text}")

        return response.json() if response.text else {}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this fixes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Predictable integration failures&lt;/li&gt;
&lt;li&gt;Centralized error handling&lt;/li&gt;
&lt;li&gt;Easier maintenance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Handle Timeouts and Transient Failures with Retries&lt;/strong&gt;&lt;br&gt;
Why Retries Are Needed&lt;/p&gt;

&lt;p&gt;External APIs may fail temporarily due to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Network latency&lt;/li&gt;
&lt;li&gt;Rate limiting (HTTP 429)&lt;/li&gt;
&lt;li&gt;Server errors (5xx)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Retrying synchronously blocks Odoo workers and reduces system performance.&lt;/p&gt;

&lt;p&gt;Correct Retry Strategy in Odoo&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use cron jobs or queue jobs&lt;/li&gt;
&lt;li&gt;Limit retry attempts&lt;/li&gt;
&lt;li&gt;Retry only transient errors
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def sync_with_retry(self, attempt=1):
    try:
        self.env['external.api.service'].send_request('POST', '/orders', {'id': self.id})
        self.sync_status = 'success'
    except Exception as e:
        if attempt &amp;lt; 3:
            self.with_delay().sync_with_retry(attempt + 1)
        else:
            self.sync_status = 'failed'
            self.sync_error = str(e)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Step 4: Prevent API Contract Breaks with Tolerant Parsing&lt;/strong&gt;&lt;br&gt;
Common Failure Scenario&lt;/p&gt;

&lt;p&gt;External APIs change response structure without notice.&lt;br&gt;
&lt;code&gt;status = response['status']  # breaks if missing&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Safer Parsing Pattern&lt;br&gt;
&lt;code&gt;status = response.get('status') if isinstance(response, dict) else None&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
This prevents production outages caused by minor API changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Prevent Duplicate Records Using Idempotency&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Retries can create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Duplicate invoices&lt;/li&gt;
&lt;li&gt;Multiple orders&lt;/li&gt;
&lt;li&gt;Inconsistent states&lt;/li&gt;
&lt;li&gt;Best Practice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Always send a stable external_id&lt;/p&gt;

&lt;p&gt;Ensure external systems perform upsert, not create&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payload = {
    'external_id': self.id,
    'order_name': self.name
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Idempotency is critical for reliable Odoo integrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6: Add Integration Logging and Monitoring&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why Logging Matters&lt;br&gt;
Without logs, “intermittent” issues cannot be measured or fixed.&lt;/p&gt;

&lt;p&gt;Create a Custom Integration Log Model&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class IntegrationLog(models.Model):
    _name = 'integration.log'

    operation = fields.Char()
    record_ref = fields.Char()
    status = fields.Selection([('success','Success'), ('error','Error')])
    message = fields.Text()
    payload = fields.Text()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use logs to track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Failure frequency&lt;/li&gt;
&lt;li&gt;Error types&lt;/li&gt;
&lt;li&gt;Affected records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 7: Write Reliable Tests Using Mocked Requests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why This Is Mandatory&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prevents deployment failures&lt;/li&gt;
&lt;li&gt;Makes CI/CD stable&lt;/li&gt;
&lt;li&gt;Ensures predictable behavior
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from unittest.mock import patch

@patch('requests.request')
def test_api_success(mock_request):
    mock_request.return_value.status_code = 200
    mock_request.return_value.json.return_value = {'status': 'ok'}

    service = self.env['external.api.service']
    response = service.send_request('GET', '/health')
    assert response['status'] == 'ok'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Odoo integration failures are rarely random. They are usually caused by weak authentication handling, missing retries, unsafe parsing, and lack of monitoring. When API logic is scattered and unstructured, even small external issues can break production systems.&lt;/p&gt;

&lt;p&gt;To build stable, scalable Odoo API integrations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Centralize authentication and endpoints&lt;/li&gt;
&lt;li&gt;Use a reusable API service layer&lt;/li&gt;
&lt;li&gt;Retry only transient failures asynchronously&lt;/li&gt;
&lt;li&gt;Parse responses defensively&lt;/li&gt;
&lt;li&gt;Prevent duplicates using idempotency&lt;/li&gt;
&lt;li&gt;Log and monitor every integration&lt;/li&gt;
&lt;li&gt;Test with mocked API responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Following these practices makes Odoo integrations reliable, debuggable, and production-ready, even when external APIs change unexpectedly.&lt;/p&gt;

</description>
      <category>odoo</category>
      <category>integration</category>
      <category>api</category>
      <category>architecture</category>
    </item>
    <item>
      <title>QWeb / View Inheritance Collisions (Odoo)</title>
      <dc:creator>Aaron Jones</dc:creator>
      <pubDate>Tue, 23 Dec 2025 09:02:45 +0000</pubDate>
      <link>https://forem.com/aaron_jones_d34b57d1b44ba/qweb-view-inheritance-collisions-odoo-40o4</link>
      <guid>https://forem.com/aaron_jones_d34b57d1b44ba/qweb-view-inheritance-collisions-odoo-40o4</guid>
      <description>&lt;p&gt;&lt;strong&gt;Problem Statement:&lt;/strong&gt;&lt;br&gt;
UI breaks or customizations disappear when multiple modules try to extend the same XML view without proper priority or XPath, causing view conflicts or missing UI elements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 Understand why the UI breaks&lt;/strong&gt;&lt;br&gt;
In Odoo, views are layered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base view (from core module)&lt;/li&gt;
&lt;li&gt;Inherited views (from multiple modules)&lt;/li&gt;
&lt;li&gt;Final compiled view (what the user actually sees)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;UI breaks when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;XPath is too generic&lt;/li&gt;
&lt;li&gt;Another module changes the structure&lt;/li&gt;
&lt;li&gt;View priority order is wrong&lt;/li&gt;
&lt;li&gt;A view replaces large blocks&lt;/li&gt;
&lt;li&gt;You inherit the wrong parent view&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2 Identify the exact conflicting views (mandatory)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;In Odoo UI&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable Developer Mode&lt;/li&gt;
&lt;li&gt;Open the broken form/tree/kanban&lt;/li&gt;
&lt;li&gt;Click Debug icon&lt;/li&gt;
&lt;li&gt;Click Edit View: Form / Tree&lt;/li&gt;
&lt;li&gt;Note:

&lt;ul&gt;
&lt;li&gt;Parent View External ID&lt;/li&gt;
&lt;li&gt;List of inherited views&lt;/li&gt;
&lt;li&gt;Which module modifies the same section
This tells you who you’re colliding with.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3 Fix the most common mistake: weak XPath&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Bad XPath (too generic)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//group" position="inside"&amp;gt;
    &amp;lt;field name="x_custom_field"/&amp;gt;
&amp;lt;/xpath&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This breaks if any module rearranges groups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good XPath (anchor to stable elements)&lt;/strong&gt;&lt;br&gt;
Target a specific field, page, or group.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//field[@name='partner_id']" position="after"&amp;gt;
    &amp;lt;field name="x_custom_field"/&amp;gt;
&amp;lt;/xpath&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or target a page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//page[@name='order_lines']" position="inside"&amp;gt;
    &amp;lt;group&amp;gt;
        &amp;lt;field name="x_custom_field"/&amp;gt;
    &amp;lt;/group&amp;gt;
&amp;lt;/xpath&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rule: Anchor to something unlikely to change&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 Use priority to control override order&lt;/strong&gt;&lt;br&gt;
When multiple modules modify the same node, priority decides who wins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: inherited view with priority&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;record id="sale_order_form_inherit_custom" model="ir.ui.view"&amp;gt;
    &amp;lt;field name="name"&amp;gt;sale.order.form.inherit.custom&amp;lt;/field&amp;gt;
    &amp;lt;field name="model"&amp;gt;sale.order&amp;lt;/field&amp;gt;
    &amp;lt;field name="inherit_id" ref="sale.view_order_form"/&amp;gt;
    &amp;lt;field name="priority" eval="90"/&amp;gt;
    &amp;lt;field name="arch" type="xml"&amp;gt;
        &amp;lt;xpath expr="//field[@name='partner_id']" position="after"&amp;gt;
            &amp;lt;field name="x_custom_field"/&amp;gt;
        &amp;lt;/xpath&amp;gt;
    &amp;lt;/field&amp;gt;
&amp;lt;/record&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Priority rules:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Default = 16&lt;/li&gt;
&lt;li&gt;Higher number → applied later&lt;/li&gt;
&lt;li&gt;Use 80–99 for override views&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 5 Never replace large blocks (major collision cause)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Dangerous (overwrites other modules)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//page[@name='other_info']" position="replace"&amp;gt;
    &amp;lt;page name="other_info"&amp;gt;
        ...
    &amp;lt;/page&amp;gt;
&amp;lt;/xpath&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes all changes from other modules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safe alternatives&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Modify attributes only&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//field[@name='client_order_ref']" position="attributes"&amp;gt;
    &amp;lt;attribute name="invisible"&amp;gt;1&amp;lt;/attribute&amp;gt;
&amp;lt;/xpath&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Insert small blocks&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//group[@name='sale_group']" position="inside"&amp;gt;
    &amp;lt;field name="x_notes"/&amp;gt;
&amp;lt;/xpath&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rule: Insert, don’t replace&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6 Inherit the actual modified view (advanced but critical)&lt;/strong&gt;&lt;br&gt;
If another module heavily modifies the base view, your XPath may not match anymore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;&amp;lt;field name="inherit_id" ref="sale.view_order_form"/&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Correct (inherit the modified view)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;field name="inherit_id" ref="module_a.sale_order_form_inherit"/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And update &lt;code&gt;__manifest__.py:&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;'depends': ['sale', 'module_a'],&lt;/code&gt;&lt;br&gt;
Now your XPath matches the real structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7 Debug the final compiled view (best practice)&lt;br&gt;
In Developer Mode:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Edit View&lt;/li&gt;
&lt;li&gt;Click View&lt;/li&gt;
&lt;li&gt;Click Inherited Views&lt;/li&gt;
&lt;li&gt;Use View Architecture
If:&lt;/li&gt;
&lt;li&gt;Your field exists in your XML&lt;/li&gt;
&lt;li&gt;But not in the compiled architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another view is overriding or removing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8 QWeb-specific collisions (reports &amp;amp; templates)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Bad QWeb XPath&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;t t-jquery="div" t-operation="append"&amp;gt;
    &amp;lt;span&amp;gt;Extra&amp;lt;/span&amp;gt;
&amp;lt;/t&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This may hit multiple nodes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good QWeb XPath&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;t t-jquery="div.o_invoice_footer" t-operation="append"&amp;gt;
    &amp;lt;span&amp;gt;Extra Footer Info&amp;lt;/span&amp;gt;
&amp;lt;/t&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example QWeb inherit&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template id="report_invoice_inherit_custom"
          inherit_id="account.report_invoice_document"
          priority="90"&amp;gt;
    &amp;lt;xpath expr="//div[@class='page']" position="inside"&amp;gt;
        &amp;lt;p&amp;gt;Custom Footer&amp;lt;/p&amp;gt;
    &amp;lt;/xpath&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 9 Rebuild &amp;amp; test cleanly&lt;/strong&gt;&lt;br&gt;
After fixing:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./odoo-bin -d your_db -u your_module --dev=xml --log-level=debug&lt;/code&gt;&lt;br&gt;
Watch for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Element '&amp;lt;xpath&amp;gt;' cannot be located&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;View validation error
&lt;/code&gt;
Hard refresh browser after upgrade.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 10 Collision-proof checklist (use every time)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;XPath anchored to field/page/group, not //group&lt;/li&gt;
&lt;li&gt;priority set intentionally&lt;/li&gt;
&lt;li&gt;No position="replace" unless unavoidable&lt;/li&gt;
&lt;li&gt;Inherit the actual modified view&lt;/li&gt;
&lt;li&gt;Test compiled view in Developer Mode&lt;/li&gt;
&lt;li&gt;Depend on modules you inherit from&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
QWeb and XML view inheritance collisions happen because multiple modules modify the same UI layer without coordination. Generic XPath expressions, incorrect inheritance targets, and missing priorities cause Odoo to apply views in unexpected ways leading to missing fields, broken layouts, or disappearing customizations. The reliable fix is to use precise XPath selectors, control execution order with view priority, avoid replacing large UI blocks, and always debug the final compiled view. Once these rules are followed, view conflicts become predictable, stable, and easy to maintain even in heavily customized Odoo environments.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>tutorial</category>
      <category>ui</category>
    </item>
    <item>
      <title>Performance Degradation Due to ORM Misuse in Odoo</title>
      <dc:creator>Aaron Jones</dc:creator>
      <pubDate>Tue, 16 Dec 2025 11:00:09 +0000</pubDate>
      <link>https://forem.com/aaron_jones_d34b57d1b44ba/performance-degradation-due-to-orm-misuse-3in7</link>
      <guid>https://forem.com/aaron_jones_d34b57d1b44ba/performance-degradation-due-to-orm-misuse-3in7</guid>
      <description>&lt;p&gt;Problem Statement: Slow page loads or timeouts occur because developers iterate record-by-record instead of using batch ORM methods (write, search, sudo), causing N+1 query issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 Confirm it’s an N+1 ORM problem (measure first)&lt;/strong&gt;&lt;br&gt;
Turn on SQL + performance logs (dev/test)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;log_level = debug_sql
log_sql = True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Odoo and reproduce the slow screen. If you see hundreds/thousands of repeated SELECTs (same table, different ids), it’s classic N+1.&lt;/p&gt;

&lt;p&gt;Quick profiling (optional)&lt;/p&gt;

&lt;p&gt;Run with profiling flags:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./odoo-bin -d your_db --dev=all --log-level=debug_sql&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 Identify the common ORM mistakes causing slowdowns&lt;/strong&gt;&lt;br&gt;
Most common slow patterns&lt;/p&gt;

&lt;p&gt;Looping records and doing search() inside the loop&lt;/p&gt;

&lt;p&gt;Calling write() inside the loop&lt;/p&gt;

&lt;p&gt;Calling sudo() repeatedly inside the loop&lt;/p&gt;

&lt;p&gt;Reading many fields when you need only a few&lt;/p&gt;

&lt;p&gt;Doing heavy compute inside @api.onchange&lt;/p&gt;

&lt;p&gt;Not using mapped(), filtered(), read_group()&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 Fix N+1 searches: one search instead of many&lt;/strong&gt;&lt;br&gt;
Bad (search inside loop)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for partner in partners:
    orders = self.env['sale.order'].search([('partner_id', '=', partner.id)])
    partner.order_count = len(orders)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs 1 query per partner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good (group query once using &lt;code&gt;read_group&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data = self.env['sale.order'].read_group(
    domain=[('partner_id', 'in', partners.ids)],
    fields=['partner_id'],
    groupby=['partner_id']
)

count_map = {d['partner_id'][0]: d['partner_id_count'] for d in data}

for partner in partners:
    partner.order_count = count_map.get(partner.id, 0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: one grouped query instead of N queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 Fix record-by-record write() calls: batch write&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Bad (write per record)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for rec in records:
    rec.write({'state': 'done'})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good (single write for all records)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;records.write({'state': 'done'})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One query instead of N.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 Avoid repeated sudo() in loops&lt;/strong&gt;&lt;br&gt;
Bad&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for rec in records:
    rec.sudo().write({'x_flag': True})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;records.sudo().write({'x_flag': True})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 6 Fetch only required fields (use search_read or read)&lt;/p&gt;

&lt;p&gt;If you don’t need full recordsets, avoid loading everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;partners = self.env['res.partner'].search([('customer_rank', '&amp;gt;', 0)])
for p in partners:
    print(p.name, p.email, p.phone)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This may prefetch lots of fields depending on usage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rows = self.env['res.partner'].search_read(
    domain=[('customer_rank', '&amp;gt;', 0)],
    fields=['name', 'email', 'phone'],
    limit=500
)
for r in rows:
    print(r['name'], r['email'], r['phone'])

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 7 Replace Python loops with ORM helpers (&lt;code&gt;mapped, filtered, sorted&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Bad (manual loops)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;emails = []
for p in partners:
    if p.email:
        emails.append(p.email)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;emails = partners.mapped('email')
emails = [e for e in emails if e]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 8 Optimize computed fields (store + depends + batch compute)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Computed fields can be the biggest hidden performance killer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad (non-stored compute recomputed frequently)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;order_total = fields.Float(compute="_compute_order_total")

def _compute_order_total(self):
    for rec in self:
        rec.order_total = sum(rec.order_line.mapped('price_total'))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be recomputed constantly when views load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good (store it and compute in batch)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;order_total = fields.Float(compute="_compute_order_total", store=True)

@api.depends('order_line.price_total')
def _compute_order_total(self):
    for rec in self:
        rec.order_total = sum(rec.order_line.mapped('price_total'))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stored computed fields reduce repeated recalculation on every page load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 9 Avoid heavy logic in @api.onchange (use constraints or compute)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Bad&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@api.onchange('partner_id')
def _onchange_partner(self):
    # heavy search on every field change
    self.warning = self.env['sale.order'].search_count([('partner_id', '=', self.partner_id.id)])

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Better&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Move heavy logic to:&lt;/p&gt;

&lt;p&gt;a button action&lt;/p&gt;

&lt;p&gt;a stored compute&lt;/p&gt;

&lt;p&gt;or show it via smart button computed once&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 10 Example “before vs after” real optimization (classic)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Scenario:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You are computing a count + total for each partner, but it’s slow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BEFORE (N+1)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for partner in partners:
    orders = self.env['sale.order'].search([('partner_id', '=', partner.id)])
    partner.order_count = len(orders)
    partner.order_amount_total = sum(orders.mapped('amount_total'))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;AFTER (2 queries total)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;order_data = self.env['sale.order'].read_group(
    domain=[('partner_id', 'in', partners.ids)],
    fields=['partner_id', 'amount_total:sum'],
    groupby=['partner_id']
)

stats = {
    d['partner_id'][0]: {
        'count': d['partner_id_count'],
        'sum': d['amount_total']
    }
    for d in order_data
}

for partner in partners:
    s = stats.get(partner.id, {'count': 0, 'sum': 0.0})
    partner.order_count = s['count']
    partner.order_amount_total = s['sum']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 11 General “Performance Rules” for Odoo ORM&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use this checklist as your permanent rule set:&lt;/p&gt;

&lt;p&gt;Never search() inside a loop → use read_group, mapped, or one search with in domain&lt;/p&gt;

&lt;p&gt;Never write() inside a loop → batch recordset.write()&lt;/p&gt;

&lt;p&gt;Avoid repeated sudo() → apply once on the recordset&lt;/p&gt;

&lt;p&gt;Prefer search_read() for lists where you only need few fields&lt;/p&gt;

&lt;p&gt;Use stored computed fields for values shown often in views&lt;/p&gt;

&lt;p&gt;Avoid heavy code in onchange (runs many times)&lt;/p&gt;

&lt;p&gt;Use SQL only when absolutely required (and still carefully)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Odoo performance issues from ORM misuse are usually caused by N+1 query patterns running searches, writes, or sudo operations inside record loops. These patterns quickly explode in production with large datasets, leading to slow pages and timeouts. The reliable fix is to rewrite logic using batch ORM operations, read_group() for grouped aggregations, mapped() for efficient field collection, search_read() for lightweight reads, and stored computed fields for frequently displayed values. Once these changes are applied and verified with SQL debug logs, you’ll see dramatic improvements in page load time and system responsiveness.&lt;/p&gt;

</description>
      <category>python</category>
      <category>database</category>
      <category>performance</category>
      <category>backend</category>
    </item>
    <item>
      <title>Odoo QWeb / View Inheritance Collisions</title>
      <dc:creator>Aaron Jones</dc:creator>
      <pubDate>Fri, 12 Dec 2025 11:12:42 +0000</pubDate>
      <link>https://forem.com/aaron_jones_d34b57d1b44ba/qwebview-inheritance-collisions-3ffj</link>
      <guid>https://forem.com/aaron_jones_d34b57d1b44ba/qwebview-inheritance-collisions-3ffj</guid>
      <description>&lt;p&gt;Problem Statement: UI breaks or customizations disappear when multiple modules try to extend the same XML view without proper priority or XPath, causing view conflicts or missing UI elements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 Confirm the exact view that is colliding&lt;/strong&gt;&lt;br&gt;
In Odoo UI (Developer Mode)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Turn on Developer Mode&lt;/li&gt;
&lt;li&gt;Go to the broken page (form/tree/kanban)&lt;/li&gt;
&lt;li&gt;Click the bug icon&lt;/li&gt;
&lt;li&gt;Select “Edit View: Form” (or Tree/Kanban)&lt;/li&gt;
&lt;li&gt;Note:
  o Original View External ID
  o Inherited Views list (you will often see multiple modules inheriting it)
This tells you which view(s) are stacking and causing collisions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 2 Open the view and find the failing XPath&lt;/strong&gt;&lt;br&gt;
Most collisions happen because your XPath is too generic or the structure changed by another module.&lt;br&gt;
Common error logs:&lt;br&gt;
• &lt;code&gt;Element '&amp;lt;xpath expr="..."&amp;gt;' cannot be located in parent view&lt;/code&gt;&lt;br&gt;
• &lt;code&gt;View validation error&lt;/code&gt;&lt;br&gt;
• UI missing fields/buttons/groups after module install&lt;br&gt;
To see the exact error:&lt;br&gt;
&lt;code&gt;./odoo-bin -d your_db -u your_module --log-level=debug&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Step 3 Fix XPath by making it more specific (most important solution)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Bad (too generic, breaks easily)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//group" position="inside"&amp;gt;
    &amp;lt;field name="x_custom_field"/&amp;gt;
&amp;lt;/xpath&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If another module rearranges groups, your XPath can hit the wrong place or not match at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good (target exact location)&lt;/strong&gt;&lt;br&gt;
Example: insert after a specific field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//field[@name='partner_id']" position="after"&amp;gt;
    &amp;lt;field name="x_custom_field"/&amp;gt;
&amp;lt;/xpath&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or target a specific page/tab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//page[@name='order_lines']" position="inside"&amp;gt;
    &amp;lt;group&amp;gt;
        &amp;lt;field name="x_custom_field"/&amp;gt;
    &amp;lt;/group&amp;gt;
&amp;lt;/xpath&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More stable because it anchors on a known element.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 Use priority to control view order&lt;/strong&gt;&lt;br&gt;
If multiple inherited views are modifying the same node (same button/group/page), order matters.&lt;br&gt;
&lt;strong&gt;Add a priority on your inherited view record&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;record id="sale_order_form_inherit_custom" model="ir.ui.view"&amp;gt;
    &amp;lt;field name="name"&amp;gt;sale.order.form.inherit.custom&amp;lt;/field&amp;gt;
    &amp;lt;field name="model"&amp;gt;sale.order&amp;lt;/field&amp;gt;
    &amp;lt;field name="inherit_id" ref="sale.view_order_form"/&amp;gt;
    &amp;lt;field name="priority" eval="90"/&amp;gt;  &amp;lt;!-- higher loads later --&amp;gt;
    &amp;lt;field name="arch" type="xml"&amp;gt;
        &amp;lt;xpath expr="//field[@name='partner_id']" position="after"&amp;gt;
            &amp;lt;field name="x_custom_field"/&amp;gt;
        &amp;lt;/xpath&amp;gt;
    &amp;lt;/field&amp;gt;
&amp;lt;/record&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt;&lt;br&gt;
• Higher priority → applied later → can override earlier changes.&lt;br&gt;
If another module has priority 99 and yours is 16, their changes may override yours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 Avoid replacing large blocks (use add/attributes instead)&lt;/strong&gt;&lt;br&gt;
Replacing full sections causes collisions.&lt;br&gt;
Dangerous (replaces whole page)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//page[@name='other_info']" position="replace"&amp;gt;
    &amp;lt;page name="other_info" string="Other Info"&amp;gt;
        ...
    &amp;lt;/page&amp;gt;
&amp;lt;/xpath&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If another module modifies that page, it may get wiped out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safer: use position="attributes" or insert smaller changes&lt;/strong&gt;&lt;br&gt;
Example: just hide/show something:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//field[@name='client_order_ref']" position="attributes"&amp;gt;
    &amp;lt;attribute name="invisible"&amp;gt;1&amp;lt;/attribute&amp;gt;
&amp;lt;/xpath&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: insert only your field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//group[@name='sale_group']" position="inside"&amp;gt;
    &amp;lt;field name="x_delivery_notes"/&amp;gt;
&amp;lt;/xpath&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6 If the other module changes structure, inherit their inherited view instead&lt;/strong&gt;&lt;br&gt;
Sometimes you inherit the base view &lt;code&gt;(sale.view_order_form)&lt;/code&gt;but the UI you see is already modified by another module.&lt;br&gt;
In that case, inherit the view that actually creates the final structure.&lt;br&gt;
Example:&lt;br&gt;
Module A inherits &lt;code&gt;sale.view_order_&lt;/code&gt;form and changes layout.&lt;br&gt;
Your module should inherit Module A’s view instead (if it’s guaranteed installed).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;field name="inherit_id" ref="module_a.sale_order_form_inherit_a"/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'depends': ['sale', 'module_a'],
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes your XPath match the structure you're actually extending.&lt;/p&gt;

&lt;p&gt;** Step 7 Debug the final compiled view (best troubleshooting)**&lt;br&gt;
In developer mode:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the form&lt;/li&gt;
&lt;li&gt;→ Edit View&lt;/li&gt;
&lt;li&gt;Click “View” then:
o check Inherited Views
o click “Manage Views”&lt;/li&gt;
&lt;li&gt;Use “Preview / View Architecture” to see the final compiled XML
If your field exists in your view but not in compiled architecture → it is being overridden/removed by another inherit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 8 Example: Two modules collide on same button&lt;br&gt;
Module A adds a button:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//header" position="inside"&amp;gt;
    &amp;lt;button name="action_confirm" type="object" string="Confirm"/&amp;gt;
&amp;lt;/xpath&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Module B tries to hide it but XPath doesn’t match&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//button[@name='action_confirm']" position="attributes"&amp;gt;
    &amp;lt;attribute name="invisible"&amp;gt;1&amp;lt;/attribute&amp;gt;
&amp;lt;/xpath&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Module A uses a different name or duplicates button, B may fail.&lt;br&gt;
Fix by anchoring using string + name + parent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;xpath expr="//header/button[@name='action_confirm' and @type='object']" position="attributes"&amp;gt;
    &amp;lt;attribute name="invisible"&amp;gt;1&amp;lt;/attribute&amp;gt;
&amp;lt;/xpath&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 9 Clean upgrade and clear cache&lt;/strong&gt;&lt;br&gt;
After changes:&lt;br&gt;
&lt;code&gt;./odoo-bin -d your_db -u your_module --dev=xml --log-level=debug&lt;/code&gt;&lt;br&gt;
Also hard refresh browser (Ctrl+Shift+R) if JS assets changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
QWeb/view inheritance collisions happen because multiple modules modify the same view, and Odoo applies them in a specific order. If your XPath is too generic, targets an element that another module moved, or your view loads earlier than competing views, your UI customizations can disappear or break.&lt;br&gt;
To fix it reliably:&lt;br&gt;
• Use precise XPath anchored to stable elements&lt;br&gt;
• Set proper priority so your changes apply at the correct stage&lt;br&gt;
• Prefer small inserts/attributes instead of replacing large blocks&lt;br&gt;
• Inherit the “real structure view” if another module heavily modifies the base view&lt;br&gt;
• Always debug the compiled final view in Developer Mode&lt;/p&gt;

</description>
      <category>help</category>
      <category>tutorial</category>
      <category>ui</category>
    </item>
  </channel>
</rss>
