<?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>DEV Community: Samcorp</title>
    <description>The latest articles on DEV Community by Samcorp (@samcorp_388df23e8f0e61ab6).</description>
    <link>https://dev.to/samcorp_388df23e8f0e61ab6</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2447383%2Ff75605b3-e11a-48ef-ab55-78bb340aa7b0.png</url>
      <title>DEV Community: Samcorp</title>
      <link>https://dev.to/samcorp_388df23e8f0e61ab6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/samcorp_388df23e8f0e61ab6"/>
    <language>en</language>
    <item>
      <title>Writing Custom Modules That Survive Version Upgrades</title>
      <dc:creator>Samcorp</dc:creator>
      <pubDate>Wed, 19 Aug 2026 11:45:14 +0000</pubDate>
      <link>https://dev.to/samcorp_388df23e8f0e61ab6/writing-custom-modules-that-survive-version-upgrades-1b63</link>
      <guid>https://dev.to/samcorp_388df23e8f0e61ab6/writing-custom-modules-that-survive-version-upgrades-1b63</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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvrtk56w1zq8duq1e7mlw.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvrtk56w1zq8duq1e7mlw.png" alt="Writing Custom Modules That Survive Version Upgrades" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
A custom Odoo module can work perfectly today and still become expensive six months later.&lt;/p&gt;

&lt;p&gt;The real test is often not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Does this module work?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Will we still understand and maintain this module when the next Odoo version arrives?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is where good &lt;strong&gt;Odoo custom module development&lt;/strong&gt; starts to look different from simply making a feature work.&lt;/p&gt;

&lt;p&gt;Odoo is designed to be extended through modules, inheritance, views, ORM methods, and frontend components. But every unnecessary dependency on internal implementation details increases the amount of work required during an upgrade.&lt;/p&gt;

&lt;p&gt;Here are the practices that make custom modules much easier to carry forward.&lt;/p&gt;


&lt;h2&gt;
  
  
  1. Never Solve Customization by Editing Odoo Core
&lt;/h2&gt;

&lt;p&gt;The fastest-looking solution is often the most expensive later.&lt;/p&gt;

&lt;p&gt;Imagine needing to change sales order confirmation.&lt;/p&gt;

&lt;p&gt;Editing the original Odoo method might work immediately.&lt;/p&gt;

&lt;p&gt;But now your implementation depends on maintaining a modified copy of standard Odoo code.&lt;/p&gt;

&lt;p&gt;When the standard method changes in a future release, you have to manually determine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What changed in Odoo?
+
What did we change?
+
Which changes should survive?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, extend the existing model.&lt;/p&gt;

&lt;p&gt;This inheritance-first approach is also central to &lt;a href="https://sdlccorp.com/services/odoo-services/odoo-customization-services/" rel="noopener noreferrer"&gt;upgrade-safe Odoo customization&lt;/a&gt;, where extensions are kept separate from core functionality so future version changes are easier to manage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;odoo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SaleOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;_inherit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sale.order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;action_confirm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;action_confirm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_run_custom_confirmation_logic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_run_custom_confirmation_logic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Custom business logic
&lt;/span&gt;        &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is not the exact example.&lt;/p&gt;

&lt;p&gt;It is the structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Standard Odoo
      ↓
Inheritance
      ↓
Small custom extension
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Copied Odoo code
      ↓
Modified copy
      ↓
Future merge problem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Odoo explicitly provides model and view inheritance as mechanisms for extending existing functionality.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Keep Overrides Small
&lt;/h2&gt;

&lt;p&gt;Even when inheritance is used correctly, an override can still become difficult to upgrade.&lt;/p&gt;

&lt;p&gt;Consider a 250-line override of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;action_confirm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Odoo changes that method in the next version, understanding the difference becomes painful.&lt;/p&gt;

&lt;p&gt;A better pattern is to keep the override thin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;action_confirm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;action_confirm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_create_external_reference&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then place the actual custom behavior inside methods your module owns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_create_external_reference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now an upgrade review asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does &lt;code&gt;action_confirm()&lt;/code&gt; still exist and is this extension point still appropriate?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which parts of these 250 lines came from Odoo three versions ago?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That dramatically reduces upgrade review time.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Declare Dependencies Explicitly
&lt;/h2&gt;

&lt;p&gt;The module manifest is more important than it looks.&lt;/p&gt;

&lt;p&gt;A typical module might contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Custom Sales Workflow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;depends&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sale&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stock&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;security/ir.model.access.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;views/sale_order_views.xml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Odoo uses module dependencies to determine which modules must be installed and updated before another module. Its current module documentation specifically describes the manifest as the place where module metadata and dependencies are declared.&lt;/p&gt;

&lt;p&gt;Avoid depending on modules simply because:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"They are installed anyway."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your code imports functionality from another module, make that relationship visible.&lt;/p&gt;

&lt;p&gt;Explicit dependencies make future failures easier to diagnose.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Avoid Deep, Fragile XPath Selectors
&lt;/h2&gt;

&lt;p&gt;Views are one of the most common places where upgrades expose fragile customization.&lt;/p&gt;

&lt;p&gt;This kind of selector should make you nervous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;xpath&lt;/span&gt; &lt;span class="na"&gt;expr=&lt;/span&gt;&lt;span class="s"&gt;"//form/sheet/group/group[2]/div[3]"&lt;/span&gt; &lt;span class="na"&gt;position=&lt;/span&gt;&lt;span class="s"&gt;"inside"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because it depends heavily on the exact structure of the parent view.&lt;/p&gt;

&lt;p&gt;If Odoo rearranges those containers, the XPath may stop matching.&lt;/p&gt;

&lt;p&gt;Whenever possible, target something more meaningful.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;field&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"client_order_ref"&lt;/span&gt; &lt;span class="na"&gt;position=&lt;/span&gt;&lt;span class="s"&gt;"after"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;field&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"integration_reference"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/field&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use a carefully targeted XPath tied to a stable element.&lt;/p&gt;

&lt;p&gt;The goal is to depend on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Business meaning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DOM position
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Odoo's view inheritance mechanism is specifically designed to apply extension views over parent views using inherited records and targeted selectors.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Don't Copy Entire Standard Views
&lt;/h2&gt;

&lt;p&gt;Another common shortcut is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy the original form view.&lt;/li&gt;
&lt;li&gt;Modify it.&lt;/li&gt;
&lt;li&gt;Replace the standard version.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That gives you control.&lt;/p&gt;

&lt;p&gt;It also gives you responsibility for maintaining everything that Odoo changes in that view.&lt;/p&gt;

&lt;p&gt;Suppose the next version adds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New buttons
New fields
New widgets
Improved visibility conditions
Security changes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your copied view does not automatically inherit those improvements.&lt;/p&gt;

&lt;p&gt;A smaller inherited view usually creates a much cleaner upgrade path.&lt;/p&gt;

&lt;p&gt;Think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Standard view
+ 10 lines of customization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;600-line copied standard view
+ customization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Smaller customization surfaces generally mean smaller upgrade surfaces.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Use the ORM Unless You Have a Strong Reason Not To
&lt;/h2&gt;

&lt;p&gt;Raw SQL sometimes has valid uses.&lt;/p&gt;

&lt;p&gt;But it should not be the default for ordinary business logic.&lt;/p&gt;

&lt;p&gt;Compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    UPDATE sale_order
       SET integration_reference = %s
     WHERE id = %s
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;integration_reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reference&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ORM communicates intent much more clearly and stays inside Odoo's model layer.&lt;/p&gt;

&lt;p&gt;It also avoids bypassing behavior that may exist around fields, models, access control, caching, and related framework functionality.&lt;/p&gt;

&lt;p&gt;For upgrade-safe &lt;strong&gt;Odoo custom module development&lt;/strong&gt;, keeping business logic at the framework level wherever practical makes future changes easier to reason about.&lt;/p&gt;

&lt;p&gt;Raw SQL should generally be isolated to cases where you actually need it—for example, certain migration or performance-sensitive operations—and then covered carefully by tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Stop Hardcoding Database IDs
&lt;/h2&gt;

&lt;p&gt;This is fragile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;group_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those IDs belong to a particular database.&lt;/p&gt;

&lt;p&gt;A restored database, fresh environment, test database, or upgraded system may not have the same numbers.&lt;/p&gt;

&lt;p&gt;Prefer XML/external IDs when referring to known records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;review_group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_module.group_order_reviewer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the code references the logical record instead of whatever database ID it happened to receive.&lt;/p&gt;

&lt;p&gt;This matters especially when the same module needs to run across:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer DB
Testing DB
Staging DB
Production DB
Upgraded DB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  8. Keep Business Logic Out of Views and Controllers
&lt;/h2&gt;

&lt;p&gt;A module becomes much easier to maintain when the responsibility of each layer is clear.&lt;/p&gt;

&lt;p&gt;A useful structure is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Model
    → Business logic

Controller
    → HTTP/API boundary

View
    → Presentation

Security
    → Permissions

Data
    → Configuration/reference records
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an important calculation exists only inside a controller, another workflow may not be able to reuse it.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomController&lt;/span&gt;&lt;span class="p"&gt;(...):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;submit_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# 80 lines of business logic
&lt;/span&gt;        &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;prefer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SaleOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_custom_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomController&lt;/span&gt;&lt;span class="p"&gt;(...):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;submit_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_custom_process&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now tests, cron jobs, UI actions, imports, and API endpoints can share the same business logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Treat Frontend Customization as an Upgrade Boundary Too
&lt;/h2&gt;

&lt;p&gt;Backend Python is not the only source of upgrade problems.&lt;/p&gt;

&lt;p&gt;Custom JavaScript can become expensive when it relies heavily on internal component structure.&lt;/p&gt;

&lt;p&gt;Modern Odoo's frontend uses Owl and framework-level components and services.&lt;/p&gt;

&lt;p&gt;Before patching frontend behavior, ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can this be done through an existing extension point?

Can a registry be extended?

Can a component be inherited?

Can the change remain isolated?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The more deeply custom JavaScript reaches into private implementation details, the more likely it is to require attention when the web client evolves.&lt;/p&gt;

&lt;p&gt;A useful rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Patch the smallest surface necessary.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  10. Give Every Important Customization a Test
&lt;/h2&gt;

&lt;p&gt;An upgrade should not depend entirely on someone clicking through every screen and saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Looks okay."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Odoo provides module testing support based on Python's testing infrastructure, and its documentation recommends defining tests alongside the module that introduces the functionality.&lt;/p&gt;

&lt;p&gt;Suppose your module adds a rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Orders above a certain amount require approval.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Test the business rule directly.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_large_order_requires_approval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sale.order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="c1"&gt;# test data
&lt;/span&gt;    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requires_approval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then test the opposite condition too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_small_order_does_not_require_approval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tests turn an Odoo upgrade from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install new version
↓
Click around
↓
Hope nothing broke
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install new version
↓
Update custom modules
↓
Run tests
↓
Investigate failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a much stronger upgrade workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Test Behavior, Not Odoo's Implementation
&lt;/h2&gt;

&lt;p&gt;Tests themselves can become upgrade liabilities.&lt;/p&gt;

&lt;p&gt;A brittle test may assert internal implementation details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Method X must call method Y exactly twice.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the actual business requirement may simply be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Approved order must produce the expected result.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer tests around business outcomes.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Given:
A confirmed order requiring manager approval

When:
An unauthorized user attempts approval

Then:
Approval is rejected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test can remain useful even if the internal implementation changes.&lt;/p&gt;

&lt;p&gt;That is exactly what you want during a version upgrade.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Design Data Changes as Migrations
&lt;/h2&gt;

&lt;p&gt;Code is only half the module.&lt;/p&gt;

&lt;p&gt;Production modules also accumulate data.&lt;/p&gt;

&lt;p&gt;Imagine version 1 stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status = "approved"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but a later design introduces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;approval_state = "manager_approved"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changing the Python field definition does not automatically answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should happen to thousands of existing database records?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That belongs in an upgrade strategy.&lt;/p&gt;

&lt;p&gt;Odoo supports module upgrade scripts through a &lt;code&gt;migrate()&lt;/code&gt; function, and its current upgrade utilities are specifically intended to help developers adapt stored data when module structures evolve.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;migrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Transform existing data required by the new module version.
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think about migrations whenever you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rename fields
Replace models
Change stored values
Move data
Change relationships
Remove old structures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A module is not upgrade-safe if only fresh installations work.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Avoid Building One Giant "custom" Module
&lt;/h2&gt;

&lt;p&gt;It starts innocently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;custom_company
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sales customization
Inventory customization
Accounting customization
CRM customization
Website customization
POS customization
Integration code
Reports
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eventually, every change depends on everything else.&lt;/p&gt;

&lt;p&gt;Instead, use boundaries that reflect functionality.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;company_sale_extension
company_inventory_extension
company_pos_extension
company_account_extension
company_external_connector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That does &lt;strong&gt;not&lt;/strong&gt; mean turning every tiny feature into a separate addon.&lt;/p&gt;

&lt;p&gt;It means keeping unrelated responsibilities from becoming one upgrade problem.&lt;/p&gt;

&lt;p&gt;When Odoo changes POS behavior, you should ideally be able to inspect the POS-related customization without reviewing an unrelated accounting report.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Keep the Dependency Graph Small
&lt;/h2&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Module A
  ↓
Module B
  ↓
Module C
  ↓
Module D
  ↓
Module E
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changing Module E may now affect everything above it.&lt;/p&gt;

&lt;p&gt;Before introducing a dependency, ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this module genuinely require the other module?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reducing unnecessary dependencies makes upgrades easier to isolate and test.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Read the Upgrade as a Code Review, Not Just a Deployment
&lt;/h2&gt;

&lt;p&gt;A major version upgrade is a useful opportunity to inspect old assumptions.&lt;/p&gt;

&lt;p&gt;For each custom module, review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Models inherited
Methods overridden
Views inherited
XPath selectors
JavaScript patches
Controllers
Cron jobs
Security rules
External APIs
Dependencies
Deprecated behavior
Migration scripts
Tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compare those extension points against the target Odoo version.&lt;/p&gt;

&lt;p&gt;Odoo's current upgrade guidance is explicit: if a database contains custom modules, compatible versions of those modules are needed for the target version.&lt;/p&gt;

&lt;p&gt;So the best time to make an upgrade inexpensive is not when the upgrade starts.&lt;/p&gt;

&lt;p&gt;It is when the module is originally written.&lt;/p&gt;

&lt;p&gt;A major &lt;a href="https://sdlccorp.com/post/odoo-migration-upgrade-guide/" rel="noopener noreferrer"&gt;Odoo version upgrade&lt;/a&gt; can expose changes in models, fields, constraints, views, and custom code, which is why reviewing extension points before migration is so important.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Simple Upgrade-Friendly Module Structure
&lt;/h2&gt;

&lt;p&gt;A clean addon might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;custom_sale_approval/
│
├── __init__.py
├── __manifest__.py
│
├── models/
│   ├── __init__.py
│   └── sale_order.py
│
├── security/
│   ├── security.xml
│   └── ir.model.access.csv
│
├── views/
│   └── sale_order_views.xml
│
├── data/
│   └── approval_data.xml
│
└── tests/
    ├── __init__.py
    └── test_sale_approval.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact structure can grow with the feature.&lt;/p&gt;

&lt;p&gt;The important property is that another developer can quickly answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Where is the model logic?
Where are the views?
Where is security defined?
Where are the tests?
What does this module depend on?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Odoo's own coding guidelines emphasize consistent module structure and maintainable code because these practices make development, debugging, and maintenance easier.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Upgrade-Survival Checklist
&lt;/h2&gt;

&lt;p&gt;Before calling an &lt;strong&gt;Odoo custom module development&lt;/strong&gt; task finished, ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] Did we modify any Odoo core files?

[ ] Are overrides small?

[ ] Are we calling super() where appropriate?

[ ] Are dependencies explicit?

[ ] Are inherited views minimal?

[ ] Are XPath selectors reasonably stable?

[ ] Did we avoid hardcoded database IDs?

[ ] Is business logic located in reusable model methods?

[ ] Are custom frontend patches isolated?

[ ] Are important workflows tested?

[ ] Do tests verify business outcomes?

[ ] Will existing production data survive future schema changes?

[ ] Is the module responsible for one coherent area?

[ ] Could another developer understand why this customization exists?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If several answers are uncomfortable, the upgrade will probably be uncomfortable too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;Upgrade-safe &lt;strong&gt;Odoo custom module development&lt;/strong&gt; is not about predicting exactly what Odoo will change in its next release.&lt;/p&gt;

&lt;p&gt;That is impossible.&lt;/p&gt;

&lt;p&gt;It is about reducing how much of your code depends on implementation details that you do not control.&lt;/p&gt;

&lt;p&gt;The pattern is fairly consistent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extend instead of copy

Use stable framework mechanisms

Keep overrides small

Keep dependencies explicit

Use the ORM appropriately

Isolate frontend patches

Write tests

Plan data migrations

Keep modules focused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A well-designed custom module may still require changes during an Odoo upgrade.&lt;/p&gt;

&lt;p&gt;That is normal.&lt;/p&gt;

&lt;p&gt;The difference is that instead of spending days trying to understand what the module was doing, you can identify the affected extension points, update them, run the tests, migrate the data, and move forward.&lt;/p&gt;

&lt;p&gt;The best custom module is not simply one that works on the version you are running today.&lt;/p&gt;

&lt;p&gt;It is one that leaves the next developer a reasonable path to the version you will be running tomorrow.&lt;/p&gt;




</description>
      <category>odoo</category>
      <category>erp</category>
    </item>
    <item>
      <title>Debugging Odoo POS Latency in a Multi-Store Deployment</title>
      <dc:creator>Samcorp</dc:creator>
      <pubDate>Wed, 19 Aug 2026 06:38:54 +0000</pubDate>
      <link>https://dev.to/samcorp_388df23e8f0e61ab6/debugging-odoo-pos-latency-in-a-multi-store-deployment-3ab7</link>
      <guid>https://dev.to/samcorp_388df23e8f0e61ab6/debugging-odoo-pos-latency-in-a-multi-store-deployment-3ab7</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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnhkrrxfbfkzs7qa3snnp.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnhkrrxfbfkzs7qa3snnp.png" alt="Debugging Odoo POS Latency in a Multi-Store Deployment" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One store was fast.&lt;/p&gt;

&lt;p&gt;Five stores were acceptable.&lt;/p&gt;

&lt;p&gt;Then more locations came online, product data grew, custom POS logic accumulated, and a simple product search occasionally started feeling much slower than it should.&lt;/p&gt;

&lt;p&gt;The first assumption was predictable:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The server needs more resources."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes it does.&lt;/p&gt;

&lt;p&gt;But in an &lt;strong&gt;Odoo POS multi-store&lt;/strong&gt; deployment, latency can come from several different layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   ↓
Network
   ↓
Reverse Proxy
   ↓
Odoo
   ↓
PostgreSQL
   ↓
Custom Modules
   ↓
External Services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding CPU before identifying which layer is slow can improve nothing.&lt;/p&gt;

&lt;p&gt;A better approach is to measure the transaction from the cashier's click all the way to the database—and work inward from there.&lt;/p&gt;




&lt;h2&gt;
  
  
  The First Question: What Is Actually Slow?
&lt;/h2&gt;

&lt;p&gt;"POS is slow" is not a useful bug report.&lt;/p&gt;

&lt;p&gt;We first split the problem into specific actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Opening a POS session
Searching products
Adding a product
Selecting a customer
Changing quantity
Processing payment
Printing receipt
Validating an order
Synchronizing data
Closing the session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because each action stresses a different part of the system.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow initial loading may point toward excessive POS data.&lt;/li&gt;
&lt;li&gt;Slow product search may be browser-side.&lt;/li&gt;
&lt;li&gt;Slow order validation may involve backend queries or custom business logic.&lt;/li&gt;
&lt;li&gt;Slow payment completion may involve an external terminal or API.&lt;/li&gt;
&lt;li&gt;Problems affecting only one store may indicate local connectivity rather than Odoo itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our first improvement was therefore simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop measuring "Odoo speed." Measure individual POS operations.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Compare Stores Before Touching the Code
&lt;/h2&gt;

&lt;p&gt;In a multi-store environment, different locations give you a built-in comparison test.&lt;/p&gt;

&lt;p&gt;Suppose we record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Store A → Fast
Store B → Fast
Store C → Slow
Store D → Fast
Store E → Slow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That immediately changes the investigation.&lt;/p&gt;

&lt;p&gt;If every store is slow, look more closely at shared infrastructure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Odoo workers
PostgreSQL
shared custom modules
server resources
common integrations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If only one location is slow, investigate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet connection
Wi-Fi quality
browser/device
local hardware
payment terminal
IoT equipment
store-specific POS configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This comparison prevents a local network issue from becoming an unnecessary backend optimization project.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Inspect the Browser Before Blaming PostgreSQL
&lt;/h2&gt;

&lt;p&gt;Odoo POS runs in the browser, and current Odoo documentation describes the POS interface as a specialized single-page application.&lt;/p&gt;

&lt;p&gt;That makes browser profiling extremely useful.&lt;/p&gt;

&lt;p&gt;Open DevTools and inspect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Network
Performance
Memory
Console
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start with the &lt;strong&gt;Network&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;For each slow operation, ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Was an HTTP request made?
How long did it take?
Was most of the delay waiting for the server?
Was a large payload downloaded?
Were several requests triggered unnecessarily?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simple mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User click
   ↓
JavaScript processing
   ↓
RPC/request
   ↓
Odoo processing
   ↓
Database
   ↓
Response
   ↓
Browser rendering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the server responds quickly but the UI remains frozen, PostgreSQL is probably not the first place to investigate.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Measure POS Startup Separately
&lt;/h2&gt;

&lt;p&gt;One of the most noticeable problems in a large &lt;strong&gt;Odoo POS multi-store&lt;/strong&gt; setup is session startup.&lt;/p&gt;

&lt;p&gt;As the deployment grows, so can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;products,&lt;/li&gt;
&lt;li&gt;variants,&lt;/li&gt;
&lt;li&gt;customers,&lt;/li&gt;
&lt;li&gt;pricelists,&lt;/li&gt;
&lt;li&gt;taxes,&lt;/li&gt;
&lt;li&gt;categories,&lt;/li&gt;
&lt;li&gt;custom fields,&lt;/li&gt;
&lt;li&gt;and data introduced by custom POS modules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful debugging question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does every POS terminal really need every record being loaded into its working context?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, imagine a deployment with stores serving very different product ranges.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Store A → Electronics
Store B → Furniture
Store C → Accessories
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If customizations force every location to process data it never uses, startup and browser work can increase unnecessarily.&lt;/p&gt;

&lt;p&gt;In larger deployments, the design of the underlying &lt;a href="https://sdlccorp.com/services/odoo-services/odoo-pos-development-company/" rel="noopener noreferrer"&gt;Odoo POS solution &lt;/a&gt;also matters, particularly when custom functionality, inventory synchronization, integrations, and multiple locations are involved.&lt;/p&gt;

&lt;p&gt;Before optimizing code, inspect what your custom modules add to the POS payload.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Profile Odoo Instead of Guessing
&lt;/h2&gt;

&lt;p&gt;Once browser measurements indicate backend delay, move to Odoo.&lt;/p&gt;

&lt;p&gt;Odoo provides an integrated profiler capable of recording SQL activity and execution traces, making it much more useful than trying to infer bottlenecks from CPU usage alone.&lt;/p&gt;

&lt;p&gt;Suppose validating an order is slow.&lt;/p&gt;

&lt;p&gt;Instead of saying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order validation takes too long.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;profile the operation and ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How many queries run?
Which methods consume the most time?
Is the same query repeated?
Is custom code performing work per order line?
Are expensive computed fields being triggered?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is to turn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POS feels slow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;into something actionable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;80% of this request is spent inside one custom method.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a bug you can work with.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Watch for N+1 Queries in Custom Modules
&lt;/h2&gt;

&lt;p&gt;Customizations were one of the first places worth checking.&lt;/p&gt;

&lt;p&gt;Consider this simplified pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product.product&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;browse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom.rule&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That looks harmless with a three-line order.&lt;/p&gt;

&lt;p&gt;With larger transactions and many concurrent POS sessions, repeated searches can become expensive.&lt;/p&gt;

&lt;p&gt;A better design may retrieve the required records in batches.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;product_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;

&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom.rule&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact implementation depends on the business logic, but the principle remains:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Avoid querying inside loops when the same information can be fetched efficiently as a set.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Odoo's performance documentation specifically recommends batching operations and avoiding algorithmic patterns that create excessive queries.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Check What Custom POS JavaScript Is Doing
&lt;/h2&gt;

&lt;p&gt;Backend optimization alone is not enough.&lt;/p&gt;

&lt;p&gt;Modern Odoo's frontend framework uses Owl components, and POS uses Odoo's JavaScript application framework.&lt;/p&gt;

&lt;p&gt;Custom POS modules may introduce frontend issues such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Repeated filtering of large arrays
Unnecessary component updates
Large synchronous loops
Repeated RPC calls
Expensive getters
Duplicate event listeners
Large custom datasets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;getAvailableProducts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkComplexRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that calculation runs repeatedly during rendering against thousands of products, the browser may become the bottleneck even when the server is healthy.&lt;/p&gt;

&lt;p&gt;The important distinction is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Slow RPC response ≠ Slow rendering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Measure both.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Separate Network Latency from Application Latency
&lt;/h2&gt;

&lt;p&gt;A multi-store deployment often means geographically separated locations.&lt;/p&gt;

&lt;p&gt;The Odoo server may live in one region while POS terminals operate hundreds or thousands of kilometers away.&lt;/p&gt;

&lt;p&gt;Measure network behavior independently.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Store → Reverse proxy
Reverse proxy → Odoo
Odoo → PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If one store consistently has higher request latency while others using the same database are fast, application code becomes less suspicious.&lt;/p&gt;

&lt;p&gt;This also means testing over the same type of connection cashiers actually use.&lt;/p&gt;

&lt;p&gt;A developer testing from a fast office connection may never reproduce a store running over congested Wi-Fi.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Don't Ignore the POS Device
&lt;/h2&gt;

&lt;p&gt;Sometimes the backend is fast.&lt;/p&gt;

&lt;p&gt;The network is fast.&lt;/p&gt;

&lt;p&gt;And the terminal is not.&lt;/p&gt;

&lt;p&gt;In stores, POS devices can stay operational for years.&lt;/p&gt;

&lt;p&gt;Common symptoms include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;High browser memory usage
Old browser versions
Low available RAM
CPU-heavy extensions
Multiple background applications
Long-running browser sessions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare the same POS configuration on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Device A
Device B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Same device + different store/network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple A/B testing can quickly separate hardware problems from application problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Test Integrations Individually
&lt;/h2&gt;

&lt;p&gt;Payment terminals, loyalty systems, inventory services, shipping APIs, custom pricing engines, and other integrations can all become part of the checkout path.&lt;/p&gt;

&lt;p&gt;Odoo supports integrations with payment terminals as part of POS workflows.&lt;/p&gt;

&lt;p&gt;If an external integration is involved, measure it independently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POS action
   ↓
Odoo
   ↓
External service
   ↓
Response
   ↓
Odoo
   ↓
POS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A slow external response can make the user experience look like an Odoo performance issue.&lt;/p&gt;

&lt;p&gt;For custom integrations, useful logging might capture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;integration_start
integration_end
elapsed_time
result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without exposing sensitive payment or customer information.&lt;/p&gt;

&lt;p&gt;Now you can determine whether the delay is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Odoo → 120 ms
External service → 2.4 s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of optimizing the wrong component.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Test Under Realistic Concurrency
&lt;/h2&gt;

&lt;p&gt;A POS deployment that performs well with one developer clicking around is not necessarily ready for multiple stores.&lt;/p&gt;

&lt;p&gt;The real scenario might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Store 1 → 6 terminals
Store 2 → 8 terminals
Store 3 → 4 terminals
Store 4 → 10 terminals
Store 5 → 5 terminals
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During peak periods, those terminals can validate transactions at roughly the same time while other Odoo users continue using Inventory, Sales, Accounting, or eCommerce.&lt;/p&gt;

&lt;p&gt;Performance testing should therefore represent real behavior rather than only record count.&lt;/p&gt;

&lt;p&gt;Test scenarios such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POS login
Product search
Customer lookup
Add 10 products
Apply pricing
Validate payment
Create order
Synchronize stock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;under expected concurrency.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Debugging Workflow That Worked Better
&lt;/h2&gt;

&lt;p&gt;After troubleshooting several layers separately, the process became much clearer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reproduce the slow action
        ↓
Identify affected stores
        ↓
Measure browser timing
        ↓
Measure network timing
        ↓
Profile Odoo
        ↓
Inspect SQL activity
        ↓
Inspect custom modules
        ↓
Measure integrations
        ↓
Test the fix
        ↓
Compare before vs. after
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important part is the final step.&lt;/p&gt;

&lt;p&gt;Without a baseline, statements such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"It feels faster now."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;are difficult to trust.&lt;/p&gt;

&lt;p&gt;Record measurable values before making changes.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Before      After
--------------------------------------
POS startup           X ms       Y ms
Product search        X ms       Y ms
Order validation      X ms       Y ms
Payment workflow      X ms       Y ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use actual measurements from your environment rather than arbitrary performance targets.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Useful Diagnostic Matrix
&lt;/h2&gt;

&lt;p&gt;When investigating an &lt;strong&gt;Odoo POS multi-store&lt;/strong&gt; latency problem, this quick matrix helps narrow the search.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;First Area to Check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Only one store is slow&lt;/td&gt;
&lt;td&gt;Store network/device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Every store is slow&lt;/td&gt;
&lt;td&gt;Shared backend/database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Initial POS load is slow&lt;/td&gt;
&lt;td&gt;Loaded datasets/custom POS models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI freezes without slow requests&lt;/td&gt;
&lt;td&gt;Frontend JavaScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RPC requests are slow&lt;/td&gt;
&lt;td&gt;Odoo/backend profiling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Queries dominate request time&lt;/td&gt;
&lt;td&gt;PostgreSQL/ORM usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment only is slow&lt;/td&gt;
&lt;td&gt;Payment integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance drops at peak time&lt;/td&gt;
&lt;td&gt;Concurrency/resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom workflow only is slow&lt;/td&gt;
&lt;td&gt;Custom module&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Old terminals are slower&lt;/td&gt;
&lt;td&gt;Browser/device performance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The table is not a diagnosis.&lt;/p&gt;

&lt;p&gt;It simply tells you where to begin measuring.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Improved the Process Most
&lt;/h2&gt;

&lt;p&gt;The biggest improvement wasn't adding more CPU.&lt;/p&gt;

&lt;p&gt;It was making performance observable.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cashier
   ↓
"POS is slow"
   ↓
Developer guesses
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we moved toward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cashier reports exact action
   ↓
Browser timing
   ↓
Network timing
   ↓
Odoo profile
   ↓
Query analysis
   ↓
Measured fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That made conversations with both developers and store teams much easier.&lt;/p&gt;

&lt;p&gt;For production environments where performance problems continue beyond initial debugging, structured &lt;a href="https://sdlccorp.com/services/odoo-services/odoo-support-services/" rel="noopener noreferrer"&gt;Odoo support and performance optimization&lt;/a&gt; can also help address server issues, custom-module problems, integrations, and ongoing POS reliability.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;Latency in an &lt;strong&gt;Odoo POS multi-store&lt;/strong&gt; deployment rarely has one universal cause.&lt;/p&gt;

&lt;p&gt;The bottleneck may be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Too much frontend work
Too much loaded data
A custom module
An inefficient ORM pattern
A slow integration
Network latency
Device limitations
Insufficient capacity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The positive part is that each of those problems is measurable.&lt;/p&gt;

&lt;p&gt;Start with the cashier action that feels slow. Compare stores. Inspect the browser. Measure the network. Profile Odoo. Examine database activity and customizations only when the evidence points there.&lt;/p&gt;

&lt;p&gt;That approach turns:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Odoo POS gets slow when we add stores."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;into a much better engineering question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Which part of this transaction becomes slower as our deployment scales?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you can answer that, optimization becomes much more straightforward.&lt;/p&gt;

</description>
      <category>odoo</category>
      <category>erp</category>
    </item>
    <item>
      <title>Five Odoo Estimates We Got Wrong and Why</title>
      <dc:creator>Samcorp</dc:creator>
      <pubDate>Tue, 18 Aug 2026 10:03:42 +0000</pubDate>
      <link>https://dev.to/samcorp_388df23e8f0e61ab6/five-odoo-estimates-we-got-wrong-and-why-197m</link>
      <guid>https://dev.to/samcorp_388df23e8f0e61ab6/five-odoo-estimates-we-got-wrong-and-why-197m</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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuuhacjmedgnhh676i14w.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuuhacjmedgnhh676i14w.png" alt="Odoo Timelines Lessons" width="800" height="450"&gt;&lt;/a&gt;Estimating an Odoo project looks easy when the requirements are still written as a short list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CRM
Sales
Inventory
Accounting
Purchase
Manufacturing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add some configuration, migrate the data, train the users, and go live.&lt;/p&gt;

&lt;p&gt;That sounds reasonable.&lt;/p&gt;

&lt;p&gt;The problem is that an &lt;strong&gt;Odoo implementation timeline&lt;/strong&gt; is rarely determined by the number of apps being installed. The harder questions are usually hiding behind those apps:&lt;/p&gt;

&lt;p&gt;How clean is the existing data? Which business processes need to change? What actually requires customization? Who approves decisions? How much testing is necessary before people trust the new system?&lt;/p&gt;

&lt;p&gt;Looking back at Odoo implementation planning, these are five estimates that are especially easy to get wrong—and what we learned to estimate instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. We Estimated Configuration, Not Discovery
&lt;/h2&gt;

&lt;p&gt;The first mistake was assuming that once the modules were selected, most requirements were already understood.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sales → 3 days
Inventory → 5 days
Purchase → 3 days
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The configuration itself might fit that estimate.&lt;/p&gt;

&lt;p&gt;Understanding the business often does not.&lt;/p&gt;

&lt;p&gt;A requirement such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We need an approval before confirming a sales order."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;quickly turns into questions.&lt;/p&gt;

&lt;p&gt;Who approves it?&lt;/p&gt;

&lt;p&gt;Does every order require approval?&lt;/p&gt;

&lt;p&gt;Is approval based on amount, margin, customer, or product?&lt;/p&gt;

&lt;p&gt;Can managers override it?&lt;/p&gt;

&lt;p&gt;What happens after rejection?&lt;/p&gt;

&lt;p&gt;Should the decision be logged?&lt;/p&gt;

&lt;p&gt;Suddenly, a simple checkbox becomes a business workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  What changed
&lt;/h3&gt;

&lt;p&gt;We started separating &lt;strong&gt;discovery time&lt;/strong&gt; from configuration time.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sales module = 3 days
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the estimate became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Process discovery
→ Solution design
→ Configuration
→ Validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That produced a much more realistic timeline.&lt;/p&gt;

&lt;p&gt;The lesson was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't estimate how long Odoo takes to configure until you understand what the business expects Odoo to do.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  2. We Underestimated Data Migration
&lt;/h2&gt;

&lt;p&gt;Data migration often receives a surprisingly small line in an implementation plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Data Migration — 5 days
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the files arrive.&lt;/p&gt;

&lt;p&gt;Customer names are duplicated.&lt;/p&gt;

&lt;p&gt;SKUs have inconsistent formatting.&lt;/p&gt;

&lt;p&gt;Some products are inactive but still appear in historical transactions.&lt;/p&gt;

&lt;p&gt;Units of measure do not match.&lt;/p&gt;

&lt;p&gt;Tax mappings are incomplete.&lt;/p&gt;

&lt;p&gt;Old systems contain categories nobody understands anymore.&lt;/p&gt;

&lt;p&gt;The difficult part is rarely importing the CSV.&lt;/p&gt;

&lt;p&gt;The difficult part is deciding what each row should become.&lt;/p&gt;

&lt;p&gt;A more realistic migration flow looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extract
   ↓
Profile
   ↓
Clean
   ↓
Normalize
   ↓
Map
   ↓
Import
   ↓
Validate
   ↓
Reconcile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What changed
&lt;/h3&gt;

&lt;p&gt;We stopped estimating migration based only on record count.&lt;/p&gt;

&lt;p&gt;Instead, we started looking at &lt;strong&gt;data complexity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ten thousand clean products can be easier to migrate than two thousand products containing duplicate SKUs, inconsistent categories, variants, serial numbers, and historical exceptions.&lt;/p&gt;

&lt;p&gt;For the &lt;strong&gt;Odoo implementation timeline&lt;/strong&gt;, data quality became a planning input instead of something discovered halfway through the project.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. We Estimated Customizations by Coding Time
&lt;/h2&gt;

&lt;p&gt;This was probably the most misleading estimate.&lt;/p&gt;

&lt;p&gt;A developer might look at a requirement and say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Development: 2 days
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Technically, that may be correct.&lt;/p&gt;

&lt;p&gt;But development is only one part of a customization.&lt;/p&gt;

&lt;p&gt;The actual lifecycle may look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requirement
   ↓
Functional design
   ↓
Technical design
   ↓
Development
   ↓
Code review
   ↓
Testing
   ↓
User validation
   ↓
Bug fixes
   ↓
Deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A two-day development task can therefore occupy a much larger section of the project calendar.&lt;/p&gt;

&lt;p&gt;Customizations can also affect multiple workflows.&lt;/p&gt;

&lt;p&gt;Changing sales order behavior might influence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quotation
→ Sales Order
→ Delivery
→ Invoice
→ Accounting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A small change near the beginning of that chain needs testing further downstream.&lt;/p&gt;

&lt;p&gt;Odoo's own implementation methodology emphasizes phased implementation work and notes that custom development can introduce additional cost and timeline impact.&lt;/p&gt;

&lt;h3&gt;
  
  
  What changed
&lt;/h3&gt;

&lt;p&gt;We began estimating customization as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Design + Development + Review + Testing + Rework
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Coding hours
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result was not necessarily a slower project.&lt;/p&gt;

&lt;p&gt;It was a more honest project plan.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. We Treated Testing as the Final Phase
&lt;/h2&gt;

&lt;p&gt;Our early timelines often looked something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Configuration
Development
Migration
Testing
Go-Live
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes testing look like something that happens when everything else is finished.&lt;/p&gt;

&lt;p&gt;It shouldn't.&lt;/p&gt;

&lt;p&gt;Imagine discovering during final testing that a purchasing customization changes inventory valuation behavior.&lt;/p&gt;

&lt;p&gt;Now the team has to revisit development, purchasing, inventory, accounting, and possibly migrated data.&lt;/p&gt;

&lt;p&gt;The later a workflow problem is discovered, the more expensive it becomes to revisit.&lt;/p&gt;

&lt;h3&gt;
  
  
  What changed
&lt;/h3&gt;

&lt;p&gt;Testing became continuous.&lt;/p&gt;

&lt;p&gt;After configuring a workflow, we validated it.&lt;/p&gt;

&lt;p&gt;After completing a customization, we tested it.&lt;/p&gt;

&lt;p&gt;After migrating sample data, we reconciled it.&lt;/p&gt;

&lt;p&gt;Then the final User Acceptance Testing phase became confirmation rather than discovery.&lt;/p&gt;

&lt;p&gt;Our project flow became closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build
  ↓
Test
  ↓
Validate
  ↓
Continue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build everything
  ↓
Hope
  ↓
Test everything
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This made the &lt;strong&gt;Odoo implementation timeline&lt;/strong&gt; easier to control because problems appeared while the relevant work was still fresh.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. We Estimated Go-Live but Not User Adoption
&lt;/h2&gt;

&lt;p&gt;Technically, an ERP can be ready while the organization is not.&lt;/p&gt;

&lt;p&gt;The database can be configured.&lt;/p&gt;

&lt;p&gt;The modules can work.&lt;/p&gt;

&lt;p&gt;The integrations can pass their tests.&lt;/p&gt;

&lt;p&gt;The migrated totals can reconcile.&lt;/p&gt;

&lt;p&gt;And users can still struggle on Monday morning.&lt;/p&gt;

&lt;p&gt;Consider a warehouse employee who has spent six years using the same process.&lt;/p&gt;

&lt;p&gt;On Friday:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Old ERP
Printed picking sheet
Manual confirmation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Monday:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Odoo
Barcode workflow
New locations
New validation rules
Different exception handling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not simply a software change.&lt;/p&gt;

&lt;p&gt;It is an operational change.&lt;/p&gt;

&lt;p&gt;Training only during the last few days before go-live leaves very little room for users to discover questions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What changed
&lt;/h3&gt;

&lt;p&gt;We started treating adoption as part of implementation.&lt;/p&gt;

&lt;p&gt;Key users became involved earlier.&lt;/p&gt;

&lt;p&gt;Workflows were demonstrated while they were being configured.&lt;/p&gt;

&lt;p&gt;Real scenarios were included in testing.&lt;/p&gt;

&lt;p&gt;Training used actual business processes instead of generic feature demonstrations.&lt;/p&gt;

&lt;p&gt;By the time go-live arrived, users were seeing a system they had already interacted with rather than something completely new.&lt;/p&gt;

&lt;p&gt;That made the transition much smoother.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We Estimate Differently Now
&lt;/h2&gt;

&lt;p&gt;The biggest change in our planning was moving away from this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Number of modules
×
Estimated configuration time
=
Project duration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;toward this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Business complexity
+
Data complexity
+
Customization
+
Integrations
+
Testing
+
Decision time
+
User adoption
=
More realistic implementation plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why two companies implementing the same Odoo apps can have completely different schedules.&lt;/p&gt;

&lt;p&gt;Following proven &lt;a href="https://sdlccorp.com/post/the-best-practices-for-odoo-erp-implementation/" rel="noopener noreferrer"&gt;Odoo ERP implementation best practices&lt;/a&gt;—especially around scope, data preparation, testing, and user adoption—can make those estimates much more reliable.&lt;/p&gt;

&lt;p&gt;One company may use standard Odoo workflows with clean data.&lt;/p&gt;

&lt;p&gt;Another may require historical migration, multiple integrations, custom approvals, manufacturing rules, regional accounting requirements, and several user groups.&lt;/p&gt;

&lt;p&gt;The module names may be identical.&lt;/p&gt;

&lt;p&gt;The projects are not.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Better Way to Think About an Odoo Implementation Timeline
&lt;/h2&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"&lt;a href="https://sdlccorp.com/post/how-long-does-odoo-implementation-take/" rel="noopener noreferrer"&gt;how long an Odoo implementation takes&lt;/a&gt;?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;a better starting question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Which parts of this implementation contain uncertainty?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those areas deserve the most attention during estimation.&lt;/p&gt;

&lt;p&gt;A timeline becomes much more reliable once unknowns are converted into decisions.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unknown customer hierarchy
        ↓
Mapping workshop
        ↓
Approved structure
        ↓
Reliable migration estimate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unclear approval workflow
        ↓
Prototype
        ↓
User validation
        ↓
Reliable development estimate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reducing uncertainty is one of the most effective ways to improve an ERP estimate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;The five estimates were not wrong because Odoo was impossible to predict.&lt;/p&gt;

&lt;p&gt;They were wrong because we were estimating the visible work while missing the work around it.&lt;/p&gt;

&lt;p&gt;We estimated configuration but missed discovery.&lt;/p&gt;

&lt;p&gt;We estimated imports but missed data cleanup.&lt;/p&gt;

&lt;p&gt;We estimated coding but missed validation.&lt;/p&gt;

&lt;p&gt;We estimated final testing instead of continuous testing.&lt;/p&gt;

&lt;p&gt;And we estimated the technical go-live without fully accounting for the people using the system afterward.&lt;/p&gt;

&lt;p&gt;Once those activities became visible in the plan, the &lt;strong&gt;Odoo implementation timeline&lt;/strong&gt; became much easier to explain, defend, and manage.&lt;/p&gt;

&lt;p&gt;The goal is not to create the shortest possible estimate.&lt;/p&gt;

&lt;p&gt;It is to create a timeline the implementation team and the business can actually trust.&lt;/p&gt;




</description>
      <category>odoo</category>
      <category>erp</category>
    </item>
    <item>
      <title>Migrating 40,000 SKUs from QuickBooks to Odoo 17: What Broke</title>
      <dc:creator>Samcorp</dc:creator>
      <pubDate>Tue, 18 Aug 2026 05:42:28 +0000</pubDate>
      <link>https://dev.to/samcorp_388df23e8f0e61ab6/migrating-40000-skus-from-quickbooks-to-odoo-17-what-broke-5enf</link>
      <guid>https://dev.to/samcorp_388df23e8f0e61ab6/migrating-40000-skus-from-quickbooks-to-odoo-17-what-broke-5enf</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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fluvicz7cmucj7m0e5io6.png" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fluvicz7cmucj7m0e5io6.png" alt="Migrating 40,000 SKUs from QuickBooks to Odoo 17" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Migrating a few hundred products from one system to another can feel like a spreadsheet task.&lt;/p&gt;

&lt;p&gt;Migrating &lt;strong&gt;40,000 SKUs from QuickBooks to Odoo 17&lt;/strong&gt; is different.&lt;/p&gt;

&lt;p&gt;It quickly becomes a structured data-migration project involving product identities, categories, variants, units of measure, inventory balances, and accounting relationships.&lt;/p&gt;

&lt;p&gt;The good news is that once the data is properly mapped and validated, a large &lt;a href="https://sdlccorp.com/post/how-to-migrate-from-quickbooks-to-odoo-a-step-by-step-guide/" rel="noopener noreferrer"&gt;QuickBooks to Odoo migration&lt;/a&gt; becomes far more manageable.&lt;/p&gt;

&lt;p&gt;Here are the biggest lessons from handling a catalog at this scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Treat QuickBooks Data as the Source, Not the Final Structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;QuickBooks exports product information in a relatively flat format.&lt;/p&gt;

&lt;p&gt;Odoo can organize the same information using:&lt;/p&gt;

&lt;p&gt;Product templates&lt;br&gt;
Product variants&lt;br&gt;
Categories&lt;br&gt;
Attributes&lt;br&gt;
Units of measure&lt;br&gt;
Inventory tracking&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;TSHIRT-BLACK-S&lt;br&gt;
TSHIRT-BLACK-M&lt;br&gt;
TSHIRT-BLACK-L&lt;/p&gt;

&lt;p&gt;Instead of creating three unrelated products, Odoo can represent them as:&lt;/p&gt;

&lt;p&gt;T-Shirt&lt;br&gt;
├── Color: Black&lt;br&gt;
└── Size: S / M / L&lt;/p&gt;

&lt;p&gt;This creates a cleaner catalog and makes future product management much easier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Normalize SKUs Before Importing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With 40,000 records, small formatting differences can create duplicate products.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;p&gt;ABC-123&lt;br&gt;
abc-123&lt;br&gt;
ABC-123 &lt;br&gt;
ABC–123&lt;/p&gt;

&lt;p&gt;A simple normalization process can clean spaces, capitalization, and special characters before migration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalize_sku&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The important part is to identify duplicate SKUs before they reach Odoo.&lt;/p&gt;

&lt;p&gt;This keeps the product database cleaner from day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Map Categories Instead of Copying Everything&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A migration is also a good opportunity to improve old product structures.&lt;/p&gt;

&lt;p&gt;Instead of automatically copying every QuickBooks category into Odoo, we can create a clean mapping.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;CATEGORY_MAP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;USB Cables&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Electronics / Cables&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mens Shirts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Apparel / Men&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Installation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Services / Installation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This prevents outdated categories such as:&lt;/p&gt;

&lt;p&gt;Misc&lt;br&gt;
Old Products&lt;br&gt;
Other&lt;br&gt;
General&lt;/p&gt;

&lt;p&gt;from becoming permanent parts of the new ERP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Import Dependencies First&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Odoo products can depend on several other records.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;Categories&lt;br&gt;
Units of Measure&lt;br&gt;
Attributes&lt;br&gt;
Attribute Values&lt;br&gt;
Accounts&lt;br&gt;
Taxes&lt;br&gt;
Vendors&lt;/p&gt;

&lt;p&gt;Because of this, migration order matters.&lt;/p&gt;

&lt;p&gt;A reliable sequence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Accounting configuration&lt;/li&gt;
&lt;li&gt;Units of measure&lt;/li&gt;
&lt;li&gt;Categories&lt;/li&gt;
&lt;li&gt;Attributes&lt;/li&gt;
&lt;li&gt;Attribute values&lt;/li&gt;
&lt;li&gt;Product templates&lt;/li&gt;
&lt;li&gt;Product variants&lt;/li&gt;
&lt;li&gt;Vendor information&lt;/li&gt;
&lt;li&gt;Inventory balances&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Preparing these dependencies first significantly reduces import errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Keep Product Data and Inventory Separate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the most useful improvements was separating product creation from opening inventory.&lt;/p&gt;

&lt;p&gt;Instead of loading everything together:&lt;/p&gt;

&lt;p&gt;Products + Stock&lt;/p&gt;

&lt;p&gt;we used:&lt;/p&gt;

&lt;p&gt;Product Master Data&lt;br&gt;
        ↓&lt;br&gt;
Product Validation&lt;br&gt;
        ↓&lt;br&gt;
Opening Inventory&lt;br&gt;
        ↓&lt;br&gt;
Inventory Reconciliation&lt;/p&gt;

&lt;p&gt;This makes it easier to confirm that both product information and stock quantities are correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Use a Staging Layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a large QuickBooks to Odoo migration, importing directly from the original spreadsheet is risky.&lt;/p&gt;

&lt;p&gt;A staging layer provides a safer workflow:&lt;/p&gt;

&lt;p&gt;QuickBooks Export&lt;br&gt;
        ↓&lt;br&gt;
Data Cleaning&lt;br&gt;
        ↓&lt;br&gt;
Normalization&lt;br&gt;
        ↓&lt;br&gt;
Validation&lt;br&gt;
        ↓&lt;br&gt;
Odoo Mapping&lt;br&gt;
        ↓&lt;br&gt;
Test Import&lt;br&gt;
        ↓&lt;br&gt;
Production Import&lt;/p&gt;

&lt;p&gt;Problematic records can be moved into an exception list instead of stopping the entire migration.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;Missing SKU&lt;br&gt;
Duplicate SKU&lt;br&gt;
Unknown Category&lt;br&gt;
Unknown Unit of Measure&lt;br&gt;
Invalid Variant&lt;/p&gt;

&lt;p&gt;This makes troubleshooting much faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Test Representative Products First&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of testing all 40,000 SKUs immediately, start with a smaller group that covers different scenarios.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;Simple products&lt;br&gt;
Services&lt;br&gt;
Variants&lt;br&gt;
Archived items&lt;br&gt;
Zero-stock products&lt;br&gt;
Serialized products&lt;br&gt;
Different categories&lt;br&gt;
Different units of measure&lt;/p&gt;

&lt;p&gt;A well-selected sample reveals most migration issues before the full import begins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Make the Migration Repeatable&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Large migrations normally require several test runs.&lt;/p&gt;

&lt;p&gt;You may need to:&lt;/p&gt;

&lt;p&gt;Update mappings&lt;br&gt;
Fix duplicate SKUs&lt;br&gt;
Improve categories&lt;br&gt;
Correct variants&lt;br&gt;
Adjust accounting rules&lt;/p&gt;

&lt;p&gt;For this reason, migration scripts should be repeatable.&lt;/p&gt;

&lt;p&gt;A stable external identifier can help connect the QuickBooks record with the correct Odoo record.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;external_id = f"qb_product_{source_id}"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This allows future runs to update or validate existing products instead of creating duplicates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Reconcile the Data After Import&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A successful import message does not always mean the migration is complete.&lt;/p&gt;

&lt;p&gt;Validation should include:&lt;/p&gt;

&lt;p&gt;Product count&lt;br&gt;
SKU count&lt;br&gt;
Inventory quantity&lt;br&gt;
Inventory value&lt;br&gt;
Category totals&lt;br&gt;
Duplicate checks&lt;br&gt;
Exception reports&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;QuickBooks SKUs: 40,000&lt;br&gt;
Odoo SKUs:       40,000&lt;/p&gt;

&lt;p&gt;Duplicate SKUs:  0&lt;br&gt;
Unknown UOMs:    0&lt;br&gt;
Unmapped items:  0&lt;/p&gt;

&lt;p&gt;This provides much stronger confidence that the migration is ready for production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Better QuickBooks to Odoo Migration Workflow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The final process looked like this:&lt;/p&gt;

&lt;p&gt;QuickBooks&lt;br&gt;
    ↓&lt;br&gt;
Export&lt;br&gt;
    ↓&lt;br&gt;
Profile Data&lt;br&gt;
    ↓&lt;br&gt;
Normalize&lt;br&gt;
    ↓&lt;br&gt;
Map&lt;br&gt;
    ↓&lt;br&gt;
Validate&lt;br&gt;
    ↓&lt;br&gt;
Test Import&lt;br&gt;
    ↓&lt;br&gt;
Reconcile&lt;br&gt;
    ↓&lt;br&gt;
Production Migration&lt;/p&gt;

&lt;p&gt;The biggest lesson was simple:&lt;/p&gt;

&lt;p&gt;A successful migration is not about moving 40,000 rows. It is about translating 40,000 records into a clean and reliable Odoo data model.&lt;/p&gt;

&lt;p&gt;When approached as a structured data project, a large &lt;strong&gt;QuickBooks to Odoo migration&lt;/strong&gt; can also become an opportunity to clean old records, simplify categories, improve product structures, and build a stronger ERP foundation for future growth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Takeaway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Moving 40,000 SKUs from QuickBooks to Odoo 17 may look complex at first, but good preparation makes a major difference.&lt;/p&gt;

&lt;p&gt;For larger or more complex ERP transitions, structured &lt;a href="https://sdlccorp.com/services/odoo-services/odoo-migration-services/" rel="noopener noreferrer"&gt;Odoo migration services&lt;/a&gt; can help manage data mapping, testing, reconciliation, and production cutover.&lt;/p&gt;

&lt;p&gt;Focus on:&lt;/p&gt;

&lt;p&gt;Clean source data&lt;br&gt;
Clear mapping rules&lt;br&gt;
Dependency-first imports&lt;br&gt;
Separate inventory handling&lt;br&gt;
Small test migrations&lt;br&gt;
Automated validation&lt;br&gt;
Final reconciliation&lt;/p&gt;

&lt;p&gt;With those pieces in place, the migration becomes more predictable, repeatable, and easier to maintain after go-live.&lt;/p&gt;

</description>
      <category>odoo</category>
      <category>erp</category>
    </item>
    <item>
      <title>Top Odoo Development Companies in USA</title>
      <dc:creator>Samcorp</dc:creator>
      <pubDate>Sat, 08 Nov 2025 10:40:16 +0000</pubDate>
      <link>https://dev.to/samcorp_388df23e8f0e61ab6/top-odoo-development-companies-in-usa-4gb0</link>
      <guid>https://dev.to/samcorp_388df23e8f0e61ab6/top-odoo-development-companies-in-usa-4gb0</guid>
      <description>&lt;p&gt;Odoo ERP has become one of the most versatile business management systems worldwide. Companies rely on it to automate workflows, integrate departments, and scale operations efficiently. Choosing the right Odoo development company can determine how well your ERP aligns with your business goals.&lt;/p&gt;

&lt;p&gt;This guide lists the top Odoo development companies in USA (2025), selected for their technical expertise, client satisfaction, and innovation in Odoo customization, integration, and implementation.&lt;br&gt;
How We Selected the Top Odoo Development Companies in USA&lt;br&gt;
Selecting the best Odoo partners required analyzing verified metrics and project portfolios. The following criteria ensure credibility and technical depth:&lt;br&gt;
Evaluation Parameters&lt;br&gt;
Industry Expertise &amp;amp; Experience: We evaluated Odoo Gold and Silver partners with proven records in manufacturing, retail, and IT.&lt;/p&gt;

&lt;p&gt;Client Reviews &amp;amp; Satisfaction: Client references, review platforms, and project outcomes validated reliability.&lt;/p&gt;

&lt;p&gt;Customization &amp;amp; Innovation: The ability to develop custom modules and integrate third-party tools distinguished leaders.&lt;/p&gt;

&lt;p&gt;Support &amp;amp; Maintenance: Companies with strong post-deployment and upgrade services ranked higher.&lt;/p&gt;

&lt;p&gt;Certified Odoo Experts: Verified certifications (v16-v18) demonstrate their active involvement in the Odoo ecosystem.&lt;/p&gt;

&lt;p&gt;Scalability &amp;amp; Regional Coverage: Capacity to manage both mid-scale and enterprise-level projects across U.S. regions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Top 10 Odoo Development Companies in USA&lt;/strong&gt; &lt;br&gt;
Below are the leading Odoo ERP development companies recognized for their innovation, reliability, and business transformation capabilities.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SDLC Corp
Location: United States, New York
Overview
SDLC Corp stands out as a top-tier Odoo implementation partner known for technical precision and enterprise-grade customization. The company delivers scalable ERP systems integrating finance, inventory, HR, and CRM—helping organizations reduce costs and improve efficiency.
Strengths &amp;amp; Highlights
95% client satisfaction across 500+ ERP projects&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Expert in manufacturing, healthcare, and eCommerce domains&lt;/p&gt;

&lt;p&gt;Specialized in AI-enabled automation within Odoo workflows&lt;/p&gt;

&lt;p&gt;Certified Odoo consultants with 20+ years of ERP experience&lt;/p&gt;

&lt;p&gt;Services Offered&lt;br&gt;
Odoo ERP development and implementation&lt;/p&gt;

&lt;p&gt;Odoo customization &amp;amp; integration&lt;/p&gt;

&lt;p&gt;Cloud migration and version upgrades&lt;/p&gt;

&lt;p&gt;Odoo support &amp;amp; maintenance&lt;/p&gt;

&lt;p&gt;Business process automation&lt;/p&gt;

&lt;p&gt;Why It Stands Out&lt;br&gt;
SDLC Corp blends innovation with reliability. Its deep industry knowledge and strong customer retention make it one of the &lt;a href="https://sdlccorp.com/us/odoo-development-company/" rel="noopener noreferrer"&gt;top Odoo development companies in USA&lt;/a&gt; for enterprises seeking long-term digital transformation.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Bista Solutions Inc.
Location: Georgia, Norcross, USA&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Overview&lt;br&gt;
Bista Solutions excels in large-scale Odoo ERP deployments, serving industries such as manufacturing, retail, and finance.&lt;br&gt;
Highlights&lt;br&gt;
234+ references and 21 certified experts&lt;/p&gt;

&lt;p&gt;Experience with projects involving 800+ users&lt;/p&gt;

&lt;p&gt;Deep domain expertise in manufacturing and maintenance&lt;/p&gt;

&lt;p&gt;Services&lt;br&gt;
Odoo implementation, support, consulting, and system integration.&lt;br&gt;
Why It Stands Out&lt;br&gt;
Bista’s strong technical leadership and enterprise experience make it a trusted partner for complex ERP ecosystems.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open Source Integrators
Location: Michigan, Tecumseh, USA&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Overview&lt;br&gt;
Known for Odoo and open-source expertise, Open Source Integrators focuses on scalable business transformation through integrated ERP solutions.&lt;br&gt;
Highlights&lt;br&gt;
143+ references and 12 certified experts&lt;/p&gt;

&lt;p&gt;Strong portfolio in manufacturing and IT sectors&lt;/p&gt;

&lt;p&gt;Why It Stands Out&lt;br&gt;
Their combination of open-source flexibility and enterprise precision ensures sustainable digital growth.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Captivea USA
Location: Florida, Orlando, USA
Overview
Captivea provides comprehensive Odoo ERP customization and CRM integration services, with deep experience in project management and accounting modules.
Highlights
115 references and 11 certified experts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Proven experience in manufacturing and technology domains&lt;/p&gt;

&lt;p&gt;Why It Stands Out&lt;br&gt;
Their expertise in multi-module ERP implementation positions them among the most reliable Odoo ERP development companies in the USA.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Novobi
Location: Texas, Austin, USA&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Overview&lt;br&gt;
Novobi merges ERP and cloud innovation with data analytics, offering specialized AWS-powered Odoo solutions.&lt;br&gt;
Highlights&lt;br&gt;
102 references, 24 certified experts&lt;/p&gt;

&lt;p&gt;Noted for secure cloud deployment and analytics integration&lt;/p&gt;

&lt;p&gt;Why It Stands Out&lt;br&gt;
A leader in cloud-based Odoo ERP services, Novobi ensures security and scalability for fast-growing businesses.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;QOC Innovations
Location: Wisconsin, Columbus, USA&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Overview&lt;br&gt;
QOC Innovations focuses on creating streamlined workflows with tailored Odoo modules for manufacturing and retail.&lt;br&gt;
Highlights&lt;br&gt;
83 references and 8 certified experts&lt;/p&gt;

&lt;p&gt;Expertise in integrating Odoo with modern logistics systems&lt;/p&gt;

&lt;p&gt;Why It Stands Out&lt;br&gt;
Renowned for practical, industry-specific implementations that enhance operational performance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Brainvire Infotech Inc.
Location: Texas, Irving, USA&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Overview&lt;br&gt;
Brainvire delivers high-performance Odoo customization and ERP migration services, with clients like Nike and Disney.&lt;br&gt;
Highlights&lt;br&gt;
12 certified experts and 61 project references&lt;/p&gt;

&lt;p&gt;Focused on scalable ERP integration and eCommerce optimization&lt;/p&gt;

&lt;p&gt;Why It Stands Out&lt;br&gt;
Brainvire’s enterprise-level innovation makes it one of the Odoo developers in USA for global brands.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cudio Inc.
Location: Massachusetts, Boston, USA&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Overview&lt;br&gt;
Cudio provides flexible, mid-sized Odoo implementations with expertise in manufacturing and IT industries.&lt;br&gt;
Highlights&lt;br&gt;
69 references and 11 certified experts&lt;/p&gt;

&lt;p&gt;Balanced approach between customization and affordability&lt;/p&gt;

&lt;p&gt;Why It Stands Out&lt;br&gt;
Ideal for mid-level enterprises seeking agile Odoo deployment.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Silverdale Technology LLC
Location: Washington, Silverdale, USA&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Overview&lt;br&gt;
Silverdale Technology delivers Odoo ERP solutions tailored to agriculture, manufacturing, and retail.&lt;br&gt;
Highlights&lt;br&gt;
52 references and 5 certified experts&lt;/p&gt;

&lt;p&gt;Notable for reliable support and maintenance services&lt;/p&gt;

&lt;p&gt;Why It Stands Out&lt;br&gt;
A dependable Odoo implementation partner for businesses requiring continuous system optimization.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Talus ERP
Location: Georgia, Alpharetta, USA&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Overview&lt;br&gt;
Talus ERP is known for precision ERP customization with a deep focus on wholesale, manufacturing, and finance.&lt;br&gt;
Highlights&lt;br&gt;
47 references and 3 certified experts&lt;br&gt;
Excellent implementation performance for SMBs&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Stands Out
&lt;/h2&gt;

&lt;p&gt;Talus’s strong customer engagement and post-deployment service make it a reliable Odoo partner.&lt;br&gt;
How to Choose the Right Odoo Development Partner&lt;br&gt;
Choosing the right partner ensures your ERP aligns with business strategy.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Match Expertise with Your Industry
Ensure your partner understands your domain workflows, compliance needs, and integration landscape.&lt;/li&gt;
&lt;li&gt;Review Customization &amp;amp; Integration Strength
Look for certified developers capable of connecting Odoo with third-party tools like Shopify, Salesforce, or QuickBooks.&lt;/li&gt;
&lt;li&gt;Validate Long-Term Support Plans
Select firms offering proactive maintenance, version upgrades, and team training.&lt;/li&gt;
&lt;li&gt;Check Client References &amp;amp; Certifications
Authentic client testimonials and Odoo certifications prove reliability and technical mastery.&lt;/li&gt;
&lt;li&gt;Consider Scalability &amp;amp; Future Upgrades
Your ERP should evolve with business growth—choose a partner that builds flexible, upgradable systems.
Why Odoo ERP Is the Preferred Choice for Businesses in 2025
Odoo provides a unified, open-source platform covering sales, accounting, HR, and inventory management. Its modular design enables companies to start small and expand as they grow.
Key Benefits
All-in-one ERP platform&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Modular and customizable architecture&lt;/p&gt;

&lt;p&gt;Cloud-ready and integration-friendly&lt;/p&gt;

&lt;p&gt;Strong community and continuous innovation&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;br&gt;
The above top Odoo development companies in the USA (2025) have established authority in implementing reliable ERP systems across industries. Each excels in customization, scalability, and client satisfaction.&lt;br&gt;
If you’re ready to optimize your operations with a proven Odoo partner, SDLC Corp offers end-to-end consulting, customization, and support to help your business reach the next level.&lt;/p&gt;

</description>
      <category>odoo</category>
      <category>development</category>
      <category>erp</category>
    </item>
    <item>
      <title>How do I implement multi-turn memory in AI chatbots?</title>
      <dc:creator>Samcorp</dc:creator>
      <pubDate>Sat, 30 Aug 2025 05:39:20 +0000</pubDate>
      <link>https://dev.to/samcorp_388df23e8f0e61ab6/how-do-i-implement-multi-turn-memory-in-ai-chatbots-47pc</link>
      <guid>https://dev.to/samcorp_388df23e8f0e61ab6/how-do-i-implement-multi-turn-memory-in-ai-chatbots-47pc</guid>
      <description>&lt;p&gt;Here’s a practical, production-ready pattern for multi-turn memory that you can drop into a small API. It combines:&lt;/p&gt;

&lt;p&gt;Short-term memory: rolling chat window (last K turns)&lt;/p&gt;

&lt;p&gt;Conversation summaries: to keep context small&lt;/p&gt;

&lt;p&gt;Long-term memory: vector store of user facts &amp;amp; past topics&lt;/p&gt;

&lt;p&gt;Entity memory: lightweight key→value store (name, timezone, preferences)&lt;/p&gt;

&lt;p&gt;Below is a complete FastAPI service with a simple SQLite + FAISS store. It’s model-agnostic, but an OpenAI adapter is included for convenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  1) Files &amp;amp; setup
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;requirements.txt&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fastapi
uvicorn
pydantic
openai&amp;gt;=1.30.0
python-dotenv
sqlalchemy
faiss-cpu
sentence-transformers


&amp;gt; .env

OPENAI_API_KEY=sk-...

#change these if you like:
OPENAI_CHAT_MODEL=gpt-4o-mini
OPENAI_EMBED_MODEL=text-embedding-3-small

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  2) Data model
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;messages:&lt;/strong&gt; all user/assistant turns (for short-term + summarization)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;entities:&lt;/strong&gt; simple key/value facts per user (entity memory)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;memories:&lt;/strong&gt; vector index of long-term memories with embedding + text&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3) App code
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;app.py&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import os
import time
from typing import List, Optional, Tuple
from dataclasses import dataclass

from dotenv import load_dotenv
from fastapi import FastAPI
from pydantic import BaseModel
from sqlalchemy import (create_engine, Column, Integer, String, Text, Float,
                        ForeignKey, select, func)
from sqlalchemy.orm import declarative_base, sessionmaker, relationship
import numpy as np

# Embeddings &amp;amp; LLM
import faiss
from sentence_transformers import SentenceTransformer
from openai import OpenAI

load_dotenv()

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
CHAT_MODEL = os.getenv("OPENAI_CHAT_MODEL", "gpt-4o-mini")
EMBED_MODEL_NAME = os.getenv("OPENAI_EMBED_MODEL", "text-embedding-3-small")

# --- DB setup ---
Base = declarative_base()
engine = create_engine("sqlite:///memory.db", echo=False, future=True)
SessionLocal = sessionmaker(bind=engine, expire_on_commit=False)

class User(Base):
    __tablename__ = "users"
    id = Column(String, primary_key=True)          # app-level user id
    created_at = Column(Float, default=lambda: time.time())
    messages = relationship("Message", back_populates="user", cascade="all, delete-orphan")
    entities = relationship("Entity", back_populates="user", cascade="all, delete-orphan")
    memories = relationship("Memory", back_populates="user", cascade="all, delete-orphan")

class Message(Base):
    __tablename__ = "messages"
    id = Column(Integer, primary_key=True, autoincrement=True)
    user_id = Column(String, ForeignKey("users.id"), index=True)
    role = Column(String)  # "user" or "assistant" or "system"
    text = Column(Text)
    created_at = Column(Float, default=lambda: time.time())
    user = relationship("User", back_populates="messages")

class Entity(Base):
    __tablename__ = "entities"
    id = Column(Integer, primary_key=True, autoincrement=True)
    user_id = Column(String, ForeignKey("users.id"), index=True)
    key = Column(String, index=True)
    value = Column(Text)
    updated_at = Column(Float, default=lambda: time.time())
    user = relationship("User", back_populates="entities")

class Memory(Base):
    __tablename__ = "memories"
    id = Column(Integer, primary_key=True, autoincrement=True)
    user_id = Column(String, ForeignKey("users.id"), index=True)
    text = Column(Text)
    kind = Column(String, default="fact")  # "fact", "topic", "preference", etc.
    vector = Column(Text)  # store as comma-joined string
    score = Column(Float, default=0.0)
    updated_at = Column(Float, default=lambda: time.time())
    user = relationship("User", back_populates="memories")

Base.metadata.create_all(engine)

# --- Embeddings ---
# We use local sentence-transformers for FAISS indexing (fast + private),
# and we store the same texts; you can swap to API embeddings if you prefer.
embedder = SentenceTransformer("all-MiniLM-L6-v2")

# FAISS index per process (we rebuild from DB on boot and when needed)
@dataclass
class FaissBundle:
    index: faiss.IndexFlatIP
    ids: List[int]  # Memory.id order aligned to FAISS

def normalize(v: np.ndarray) -&amp;gt; np.ndarray:
    norms = np.linalg.norm(v, axis=1, keepdims=True) + 1e-10
    return v / norms

def build_faiss_for_user(db, user_id: str) -&amp;gt; FaissBundle:
    mems = db.execute(select(Memory).where(Memory.user_id == user_id)).scalars().all()
    if not mems:
        return FaissBundle(faiss.IndexFlatIP(384), [])
    texts = [m.text for m in mems]
    vecs = embedder.encode(texts, convert_to_numpy=True)
    vecs = normalize(vecs.astype("float32"))
    index = faiss.IndexFlatIP(vecs.shape[1])
    index.add(vecs)
    return FaissBundle(index=index, ids=[m.id for m in mems])

# --- LLM client (OpenAI as example; swap to your provider easily) ---
client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None

def chat_completion(messages: List[dict], model: str = CHAT_MODEL, max_tokens: int = 600) -&amp;gt; str:
    if not client:
        # Offline/dev fallback so the server runs without a key
        return "(LLM disabled) You asked: " + messages[-1]["content"]
    resp = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=0.4,
        max_tokens=max_tokens,
    )
    return resp.choices[0].message.content

def extract_salient_memories(latest_user_text: str) -&amp;gt; List[str]:
    """
    Ask the LLM to extract durable facts/preferences to remember.
    Keep it minimal; you can add rules per domain.
    """
    prompt = [
        {"role": "system", "content": (
            "You extract durable, reusable facts from a single user message. "
            "Return 0–5 bullet-worthy snippets, each a single short sentence. "
            "Only include things that would still be useful weeks later "
            "(preferences, profile facts, long-term goals, stable constraints). "
            "If nothing durable, return 'NONE'."
        )},
        {"role": "user", "content": latest_user_text}
    ]
    text = chat_completion(prompt, max_tokens=120)
    if "NONE" in text.strip().upper():
        return []
    # very light parsing; assumes bullets or sentences
    lines = [l.strip(" -•\t") for l in text.splitlines() if l.strip()]
    return [l for l in lines if len(l) &amp;gt; 2][:5]

def summarize_conversation(history: List[Tuple[str, str]]) -&amp;gt; str:
    """
    Summarize a long chat when token budget is tight.
    history: list of (role, text)
    """
    parts = "\n".join([f"{r.upper()}: {t}" for r, t in history[-30:]])
    prompt = [
        {"role": "system", "content": (
            "Summarize the conversation so far into 5–8 crisp bullets, "
            "preserving decisions, plans, constraints, and unresolved questions."
        )},
        {"role": "user", "content": parts}
    ]
    return chat_completion(prompt, max_tokens=180)

# --- Memory manager ---
class MemoryManager:
    def __init__(self):
        self._faiss_cache = {}  # user_id -&amp;gt; FaissBundle

    def ensure_user(self, db, user_id: str):
        user = db.get(User, user_id)
        if not user:
            user = User(id=user_id)
            db.add(user)
            db.commit()
        if user_id not in self._faiss_cache:
            self._faiss_cache[user_id] = build_faiss_for_user(db, user_id)
        return user

    def upsert_entity(self, db, user_id: str, key: str, value: str):
        ent = db.execute(select(Entity).where(
            Entity.user_id == user_id, Entity.key == key
        )).scalar_one_or_none()
        if ent:
            ent.value = value
            ent.updated_at = time.time()
        else:
            ent = Entity(user_id=user_id, key=key, value=value)
            db.add(ent)
        db.commit()

    def search_memories(self, db, user_id: str, query: str, k: int = 5) -&amp;gt; List[Memory]:
        bundle = self._faiss_cache.get(user_id)
        if not bundle or bundle.index.ntotal == 0:
            return []
        qv = embedder.encode([query], convert_to_numpy=True).astype("float32")
        qv = normalize(qv)
        scores, idx = bundle.index.search(qv, k)
        results = []
        for rank in idx[0]:
            if rank == -1: 
                continue
            mem_id = bundle.ids[rank]
            mem = db.get(Memory, mem_id)
            if mem:
                results.append(mem)
        return results

    def add_memories(self, db, user_id: str, texts: List[str], kind: str = "fact"):
        if not texts:
            return
        vecs = embedder.encode(texts, convert_to_numpy=True).astype("float32")
        vecs = normalize(vecs)
        new_ids = []
        for text, vec in zip(texts, vecs):
            m = Memory(
                user_id=user_id,
                text=text.strip(),
                kind=kind,
                vector=",".join(map(str, vec.tolist())),
                score=0.0
            )
            db.add(m)
            db.flush()
            new_ids.append(m.id)
        db.commit()
        # rebuild FAISS for this user (simple + safe)
        self._faiss_cache[user_id] = build_faiss_for_user(db, user_id)

memory_mgr = MemoryManager()

# --- FastAPI ---
app = FastAPI(title="Chat with Multi-Turn Memory")

class ChatRequest(BaseModel):
    user_id: str
    message: str
    # optional hints for entity memory
    user_name: Optional[str] = None
    timezone: Optional[str] = None

class ChatResponse(BaseModel):
    reply: str
    used_memories: List[str]
    summary_used: bool

SYSTEM_GUARDRAILS = (
    "You are a helpful assistant. "
    "Use retrieved memories if relevant. "
    "Be concise and avoid repeating the user."
)

MAX_WINDOW = 8   # last 8 turns kept verbatim before summarizing
MAX_TOKENS_BUDGETED = 4096  # conceptual; we’re using it to decide when to summarize

@app.post("/chat", response_model=ChatResponse)
def chat(req: ChatRequest):
    db = SessionLocal()
    try:
        user = memory_mgr.ensure_user(db, req.user_id)

        # Optional: update entity memory
        if req.user_name:
            memory_mgr.upsert_entity(db, req.user_id, "name", req.user_name)
        if req.timezone:
            memory_mgr.upsert_entity(db, req.user_id, "timezone", req.timezone)

        # Save incoming user message
        db.add(Message(user_id=req.user_id, role="user", text=req.message))
        db.commit()

        # 1) Extract durable facts from the new message and store to long-term memory
        facts = extract_salient_memories(req.message)
        memory_mgr.add_memories(db, req.user_id, facts, kind="fact")

        # 2) Retrieve relevant long-term memories for the current query
        retrieved = memory_mgr.search_memories(db, req.user_id, req.message, k=5)
        retrieved_texts = [f"- {m.text}" for m in retrieved]

        # 3) Build short-term context: last K turns (user+assistant)
        all_msgs = db.execute(select(Message).where(
            Message.user_id == req.user_id
        ).order_by(Message.created_at.asc())).scalars().all()
        turns = [(m.role, m.text) for m in all_msgs]

        window = turns[-(MAX_WINDOW*2):]  # roughly last K exchanges
        summary_used = False
        summary_text = ""
        # Optional: summarize if conversation is getting long
        if len(turns) &amp;gt; MAX_WINDOW * 2 + 2:
            summary_text = summarize_conversation(turns[:- (MAX_WINDOW*2)])
            summary_used = True

        # 4) Pull entity memory
        entities = db.execute(select(Entity).where(Entity.user_id == req.user_id)).scalars().all()
        entity_lines = [f"{e.key}: {e.value}" for e in entities]

        # 5) Compose final prompt
        messages = [{"role": "system", "content": SYSTEM_GUARDRAILS}]
        if entity_lines:
            messages.append({"role": "system", "content": "Known user entities:\n" + "\n".join(entity_lines)})
        if retrieved_texts:
            messages.append({"role": "system", "content": "Relevant long-term memories:\n" + "\n".join(retrieved_texts)})
        if summary_text:
            messages.append({"role": "system", "content": "Conversation summary so far:\n" + summary_text})

        for r, t in window:
            messages.append({"role": r, "content": t})

        # 6) Generate answer
        reply = chat_completion(messages)

        # 7) Save assistant reply
        db.add(Message(user_id=req.user_id, role="assistant", text=reply))
        db.commit()

        return ChatResponse(
            reply=reply,
            used_memories=[m.text for m in retrieved],
            summary_used=summary_used
        )
    finally:
        db.close()

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  fastapi-env-api-keys-screenshot.png
&lt;/h2&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8g53acuszq3rugn134ul.png" 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%2F8g53acuszq3rugn134ul.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4) Run it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install -r requirements.txt
uvicorn app:app --reload --port 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Query it:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "u123",
    "user_name": "Sanket",
    "timezone": "Asia/Kolkata",
    "message": "I prefer concise answers and dark UI themes. Also remind me to ship the Odoo article on Friday."
  }'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Then another turn:&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;curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "u123",
    "message": "What were my preferences again? And help me plan the Odoo article outline."
  }'

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

&lt;/div&gt;



&lt;p&gt;You’ll see used_memories include the preference facts pulled from long-term memory, while the rolling window + summary keep the LLM context tight.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How it works (brief)&lt;/strong&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;1. On each user turn&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Save the raw message.&lt;/li&gt;
&lt;li&gt;Ask the LLM to extract durable facts (preferences, profile, long-term goals). Store them in memories + FAISS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2. Before responding&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve top-k memories by semantic similarity to the new message.&lt;/li&gt;
&lt;li&gt;Build the prompt from:&lt;/li&gt;
&lt;li&gt;Guardrails&lt;/li&gt;
&lt;li&gt;Entity memory&lt;/li&gt;
&lt;li&gt;Retrieved long-term memories&lt;/li&gt;
&lt;li&gt;Conversation summary (if history is long)&lt;/li&gt;
&lt;li&gt;Recent short-term turns (last K exchanges)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;3. After responding&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Save the assistant message.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tweaks you’ll likely add
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Expiry &amp;amp; decay: lower score or prune old memories unless re-used.&lt;/li&gt;
&lt;li&gt;Memory categories: preference, identity, project, task, etc., and retrieve by type.&lt;/li&gt;
&lt;li&gt;Task memory: store “open loops” and have the bot proactively follow up.&lt;/li&gt;
&lt;li&gt;Privacy switches: only store memories if the user opted in.&lt;/li&gt;
&lt;li&gt;RAG: add a document retriever alongside personal memories.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Minimal no-DB variant (for prototypes)
&lt;/h2&gt;

&lt;p&gt;If you just need short-term memory with summarization (no vector store), keep only a deque of messages and periodically compress it with the summarize_conversation function. That alone handles many chat UX cases.&lt;/p&gt;

&lt;p&gt;Multi-turn memory is one of the biggest challenges in building production-grade AI assistants. While this guide shows a practical FastAPI pattern, in real-world projects we often combine this with advanced pipelines and deployment workflows. For end-to-end solutions, you can explore &lt;a href="https://sdlccorp.com/ai-development-services/" rel="noopener noreferrer"&gt;AI development&lt;/a&gt; that apply these techniques at scale.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Real-Time Encryption of Bets and Results</title>
      <dc:creator>Samcorp</dc:creator>
      <pubDate>Tue, 17 Jun 2025 13:33:17 +0000</pubDate>
      <link>https://dev.to/samcorp_388df23e8f0e61ab6/real-time-encryption-of-bets-and-results-2eo9</link>
      <guid>https://dev.to/samcorp_388df23e8f0e61ab6/real-time-encryption-of-bets-and-results-2eo9</guid>
      <description>&lt;p&gt;&lt;strong&gt;Objective&lt;/strong&gt;:&lt;br&gt;
Ensure that bet data and game results can't be tampered with in transmission or storage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technology Stack:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Backend Language:&lt;/strong&gt; Node.js / Java / Python / Golang&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encryption&lt;/strong&gt;: AES, RSA, SHA-256&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database&lt;/strong&gt;: PostgreSQL / MongoDB with encrypted fields&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Example *&lt;/em&gt;(Node.js with AES):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const crypto = require('crypto');
const algorithm = 'aes-256-cbc';
const key = crypto.randomBytes(32); // secret key
const iv = crypto.randomBytes(16);  // initialization vector

function encrypt(data) {
  const cipher = crypto.createCipheriv(algorithm, key, iv);
  let encrypted = cipher.update(JSON.stringify(data), 'utf-8', 'hex');
  encrypted += cipher.final('hex');
  return { encryptedData: encrypted, iv: iv.toString('hex') };
}

function decrypt(encryptedData, iv) {
  const decipher = crypto.createDecipheriv(algorithm, key, Buffer.from(iv, 'hex'));
  let decrypted = decipher.update(encryptedData, 'hex', 'utf-8');
  decrypted += decipher.final('utf-8');
  return JSON.parse(decrypted);
}

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Device Fingerprinting &amp;amp; Anomaly Detection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Objective:&lt;/strong&gt;&lt;br&gt;
Track user devices and behavior to detect bots, duplicate accounts, or risky login patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technology Stack:&lt;/strong&gt;&lt;br&gt;
Frontend: JavaScript (React/Angular/Vue)&lt;/p&gt;

&lt;p&gt;Backend: Python (Flask/FastAPI) or Node.js&lt;/p&gt;

&lt;p&gt;Libraries: FingerprintJS, DeviceDetector, UA-parser, GeoIP2&lt;/p&gt;

&lt;p&gt;ML Models (Optional): Scikit-learn or TensorFlow (Python)&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Example *&lt;/em&gt;(JavaScript + Node.js):&lt;br&gt;
Client (browser)&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;script src="https://openfpcdn.io/fingerprintjs/v3"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;
FingerprintJS.load().then(fp =&amp;gt; {
  fp.get().then(result =&amp;gt; {
    fetch('/api/track-device', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        visitorId: result.visitorId,
        browser: result.components.userAgent.value,
        screen: window.screen.width + 'x' + window.screen.height
      })
    });
  });
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Backend (Node.js):&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;app.post('/api/track-device', async (req, res) =&amp;gt; {
  const { visitorId, browser, screen } = req.body;
  const previousDevices = await db.findDevicesByUser(req.user.id);

  if (!previousDevices.includes(visitorId)) {
    // alert risk engine
    logSuspiciousActivity(req.user.id, visitorId);
  }

  res.sendStatus(200);
});

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

&lt;/div&gt;



&lt;p&gt;3.2FA, CAPTCHA, and Geo-Fencing&lt;br&gt;
&lt;strong&gt;Objective&lt;/strong&gt;:&lt;br&gt;
Add extra layers of access control and compliance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech&lt;/strong&gt;:&lt;br&gt;
2FA: TOTP via Google Authenticator&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CAPTCHA&lt;/strong&gt;: Google reCAPTCHA v3&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Geo-Fencing: IP-to-country + Rules Engine&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 1 – Google reCAPTCHA (Frontend):&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;form id="login-form"&amp;gt;
  &amp;lt;input name="email"&amp;gt;
  &amp;lt;input name="password"&amp;gt;
  &amp;lt;div class="g-recaptcha" data-sitekey="your-site-key"&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;button&amp;gt;Login&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;script src="https://www.google.com/recaptcha/api.js" async defer&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example 2 – TOTP (Node.js using speakeasy):&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;const speakeasy = require('speakeasy');
const secret = speakeasy.generateSecret({ name: "MyCasinoApp" });

// Send QR to user
console.log(secret.otpauth_url);

// Verify
const isVerified = speakeasy.totp.verify({
  secret: secret.base32,
  encoding: 'base32',
  token: userInputCode
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example 3 – Geo-Fencing with IP:&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 geoip2.database

reader = geoip2.database.Reader('/GeoLite2-Country.mmdb')
response = reader.country('103.31.144.0')
country = response.country.iso_code

if country not in ["UK", "MT", "GI", "IN"]:
    raise PermissionError("Access denied from restricted jurisdiction")

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

&lt;/div&gt;



&lt;p&gt;End-to-End Platform Security&lt;br&gt;
From encrypted game data and fingerprinting to real-time geofencing and multi-factor authentication, securing your platform is non-negotiable. For a complete overview of setting up a secure and fully compliant casino environment, refer to this &lt;a href="https://sdlccorp.com/post/how-to-start-an-online-casino-a-step-by-step-guide/" rel="noopener noreferrer"&gt;step-by-step guide to launching an online game&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Fatal Error: Allowed Memory Size Exhausted in WordPress</title>
      <dc:creator>Samcorp</dc:creator>
      <pubDate>Wed, 18 Dec 2024 09:02:10 +0000</pubDate>
      <link>https://dev.to/samcorp_388df23e8f0e61ab6/fatal-error-allowed-memory-size-exhausted-in-wordpress-b5b</link>
      <guid>https://dev.to/samcorp_388df23e8f0e61ab6/fatal-error-allowed-memory-size-exhausted-in-wordpress-b5b</guid>
      <description>&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;br&gt;
PHP script exceeds memory allocation, causing a fatal error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Causes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Too many plugins.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inefficient code in themes/plugins.&lt;br&gt;
Solution&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Increase memory limit in &lt;code&gt;wp-config.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;define('WP_MEMORY_LIMIT', '256M');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Identify and optimize problematic plugins or custom code by deactivating them one at a time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://sdlccorp.com/services/wordpress/custom-wordpress-development-services/" rel="noopener noreferrer"&gt;WordPress development services&lt;/a&gt; to build custom websites that are fast, secure, and easy to manage. From creating themes and plugins to optimizing performance, we ensure your website meets your business needs. Get a professional, user-friendly site that stands out.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>customwordpress</category>
      <category>websitedesign</category>
      <category>wordpressdevelopment</category>
    </item>
    <item>
      <title>Mixed Content Warnings (HTTP/HTTPS) in WordPress</title>
      <dc:creator>Samcorp</dc:creator>
      <pubDate>Wed, 18 Dec 2024 08:57:17 +0000</pubDate>
      <link>https://dev.to/samcorp_388df23e8f0e61ab6/mixed-content-warnings-httphttps-in-wordpress-1385</link>
      <guid>https://dev.to/samcorp_388df23e8f0e61ab6/mixed-content-warnings-httphttps-in-wordpress-1385</guid>
      <description>&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;br&gt;
After enabling SSL, some resources are still loaded over HTTP, causing "mixed content" warnings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Causes&lt;/strong&gt;&lt;br&gt;
Hardcoded HTTP URLs in theme or plugins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use a Plugin&lt;/strong&gt;&lt;br&gt;
Install and configure the "Really Simple SSL" plugin.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Update Database URLs&lt;/strong&gt;&lt;br&gt;
Use the "Better Search Replace" plugin or run the following SQL query&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UPDATE wp_options SET option_value = replace(option_value, 'http://yourdomain.com', 'https://yourdomain.com') WHERE option_name = 'home' OR option_name = 'siteurl';
UPDATE wp_posts SET post_content = replace(post_content, 'http://yourdomain.com', 'https://yourdomain.com');
UPDATE wp_postmeta SET meta_value = replace(meta_value, 'http://yourdomain.com', 'https://yourdomain.com');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://sdlccorp.com/services/wordpress/custom-wordpress-development-services/" rel="noopener noreferrer"&gt;WordPress development services&lt;/a&gt; to build custom websites that are fast, secure, and easy to manage. From creating themes and plugins to optimizing performance, we ensure your website meets your business needs. Get a professional, user-friendly site that stands out.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>customwordpress</category>
      <category>wordpressdevelopment</category>
      <category>webdesign</category>
    </item>
    <item>
      <title>Image Upload Errors in WordPress</title>
      <dc:creator>Samcorp</dc:creator>
      <pubDate>Wed, 18 Dec 2024 07:36:34 +0000</pubDate>
      <link>https://dev.to/samcorp_388df23e8f0e61ab6/image-upload-errors-in-wordpress-bdg</link>
      <guid>https://dev.to/samcorp_388df23e8f0e61ab6/image-upload-errors-in-wordpress-bdg</guid>
      <description>&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;br&gt;
Image uploads fail with errors like "HTTP error" or "Unable to create directory."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Causes&lt;/strong&gt;&lt;br&gt;
Incorrect file permissions.&lt;br&gt;
Low PHP upload limit.&lt;br&gt;
Temporary folder missing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Check File Permissions:&lt;br&gt;
Ensure wp-content/uploads has 755 permissions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Increase Upload Limit:&lt;br&gt;
Update &lt;code&gt;php.ini&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Define Temporary Folder in &lt;code&gt;wp-config.php&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;define('WP_TEMP_DIR', dirname(__FILE__) . '/wp-content/temp/');&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://sdlccorp.com/services/wordpress/custom-wordpress-development-services/" rel="noopener noreferrer"&gt;WordPress development services&lt;/a&gt; to build custom websites that are fast, secure, and easy to manage. From creating themes and plugins to optimizing performance, we ensure your website meets your business needs. Get a professional, user-friendly site that stands out.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>customwordpress</category>
      <category>wordpressdevelopment</category>
      <category>webdesign</category>
    </item>
    <item>
      <title>CSS/JavaScript Changes Not Reflecting in WordPress</title>
      <dc:creator>Samcorp</dc:creator>
      <pubDate>Wed, 18 Dec 2024 07:32:00 +0000</pubDate>
      <link>https://dev.to/samcorp_388df23e8f0e61ab6/cssjavascript-changes-not-reflecting-in-wordpress-47in</link>
      <guid>https://dev.to/samcorp_388df23e8f0e61ab6/cssjavascript-changes-not-reflecting-in-wordpress-47in</guid>
      <description>&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;br&gt;
Changes to CSS or JavaScript files do not appear on the front end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Causes&lt;/strong&gt;&lt;br&gt;
Browser caching.&lt;br&gt;
CDN cache.&lt;br&gt;
Incorrect file versioning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Clear Browser and CDN Cache.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Versioning for Enqueued Files:&lt;br&gt;
Update the file version in &lt;code&gt;functions.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wp_enqueue_style('custom-style', get_template_directory_uri() . '/css/style.css', array(), filemtime(get_template_directory() . '/css/style.css'));
wp_enqueue_script('custom-script', get_template_directory_uri() . '/js/script.js', array('jquery'), filemtime(get_template_directory() . '/js/script.js'), true);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://sdlccorp.com/services/wordpress/custom-wordpress-development-services/" rel="noopener noreferrer"&gt;WordPress development services&lt;/a&gt; to build custom websites that are fast, secure, and easy to manage. From creating themes and plugins to optimizing performance, we ensure your website meets your business needs. Get a professional, user-friendly site that stands out.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>customwordpress</category>
      <category>wordpressdevelopment</category>
      <category>websitedesign</category>
    </item>
    <item>
      <title>AJAX Errors in WordPress Admin</title>
      <dc:creator>Samcorp</dc:creator>
      <pubDate>Wed, 18 Dec 2024 07:25:30 +0000</pubDate>
      <link>https://dev.to/samcorp_388df23e8f0e61ab6/ajax-errors-in-wordpress-admin-hkl</link>
      <guid>https://dev.to/samcorp_388df23e8f0e61ab6/ajax-errors-in-wordpress-admin-hkl</guid>
      <description>&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;br&gt;
AJAX-based functionalities (e.g., media uploads, custom metaboxes) fail in the admin panel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Causes&lt;/strong&gt;&lt;br&gt;
Invalid admin-ajax.php calls.&lt;br&gt;
Plugin conflicts.&lt;br&gt;
Nonces not verified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Debug AJAX Calls:&lt;br&gt;
Use the browser's developer tools to inspect the network request. Look for errors in the response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify Nonce Validation:&lt;br&gt;
Ensure proper nonce verification in AJAX functions:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'your_action')) {
    wp_send_json_error('Invalid request');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Increase PHP Limits:
Increase execution time and memory limit in &lt;code&gt;php.ini&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;max_execution_time = 300
memory_limit = 256M
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://sdlccorp.com/services/wordpress/custom-wordpress-development-services/" rel="noopener noreferrer"&gt;WordPress development services&lt;/a&gt; to build custom websites that are fast, secure, and easy to manage. From creating themes and plugins to optimizing performance, we ensure your website meets your business needs. Get a professional, user-friendly site that stands out.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>wordpressdevelopment</category>
      <category>customdevelopment</category>
      <category>websitedevelopment</category>
    </item>
  </channel>
</rss>
