{"id":36340,"date":"2026-03-21T11:18:22","date_gmt":"2026-03-21T11:18:22","guid":{"rendered":"https:\/\/tenthplanet.in\/odoo\/?p=36340"},"modified":"2026-03-21T11:22:23","modified_gmt":"2026-03-21T11:22:23","slug":"odoo-customization-best-practices-the-5s-framework-for-developers","status":"publish","type":"post","link":"https:\/\/tenthplanet.in\/odoo\/product\/odoo-customization-best-practices-the-5s-framework-for-developers\/","title":{"rendered":"Odoo Customization Best Practices: The 5S Framework for Developers"},"content":{"rendered":"<p><span class=\"h3-fs\"><b>5S Framework <\/b>is a technical documentation standard used for\u00a0Odoo Architects and Developers to bridge the gap between functional requirements and high-quality code. This framework ensures that every customization is documented with precision, covering the UI, logic, automation, database structure, and data retrieval.<\/span><\/p>\n<h2>Each layer answers one core question:<\/h2>\n<table class=\"table table-bordered o_table\">\n<tbody>\n<tr>\n<th>Layer<\/th>\n<th>Answers<\/th>\n<th>Output<\/th>\n<\/tr>\n<tr>\n<td><strong>Screen<\/strong><\/td>\n<td><em>What does the user see?<\/em><\/td>\n<td>XML views, fields, layouts<\/td>\n<\/tr>\n<tr>\n<td><strong>Pseudo Code<\/strong><\/td>\n<td><em>What does the system think?<\/em><\/td>\n<td>Logic in plain English before Python<\/td>\n<\/tr>\n<tr>\n<td><strong>Script<\/strong><\/td>\n<td><em>What runs automatically?<\/em><\/td>\n<td>Automations, cron jobs, workflows<\/td>\n<\/tr>\n<tr>\n<td><strong>Schema<\/strong><\/td>\n<td><em>How is data stored?<\/em><\/td>\n<td>Python models, field types, relations<\/td>\n<\/tr>\n<tr>\n<td><strong>SQL<\/strong><\/td>\n<td><em>How is data retrieved?<\/em><\/td>\n<td>Raw queries, reports, dashboards<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>5S Framework\u00a0for Odoo<\/h2>\n<p><span class=\"h3-fs\">Here&#8217;s a clean visual overview of the 5S Framework, followed by a breakdown of each layer.<\/span><\/p>\n<p><img decoding=\"async\" class=\"img-fluid\" src=\"https:\/\/ten10.in\/web\/image\/24597-1ea99aeb\/image.png?access_token=dbe65361-6935-4c09-8232-61ecf08008ca\" data-file-name=\"image.png\" \/><\/p>\n<p><span class=\"h3-fs\"><strong>The golden rule of 5S<\/strong> is that you work top-to-bottom, never jumping ahead. You don&#8217;t write Python until the Screen is documented. You don&#8217;t write SQL until the Schema is defined. This prevents the most common Odoo mistake \u2014 developers writing code before they&#8217;ve agreed on what the field is called or where it lives.<\/span><\/p>\n<p><span class=\"h3-fs\">Here&#8217;s how each layer maps to actual files inside your custom Odoo module<\/span>:<\/p>\n<table class=\"table table-bordered o_table\">\n<tbody>\n<tr>\n<td><b>Layer<\/b><\/td>\n<td><b>Name<\/b><\/td>\n<td><b>Focus<\/b><\/td>\n<td><b>Output<\/b><\/td>\n<td><b>Odoo Artifact<\/b><\/td>\n<\/tr>\n<tr>\n<td>S1<\/td>\n<td>Screen<\/td>\n<td>UI\/UX<\/td>\n<td>XML views<\/td>\n<td>ir.ui.view<\/td>\n<\/tr>\n<tr>\n<td>S2<\/td>\n<td>Pseudo Code<\/td>\n<td>Logic<\/td>\n<td>Python code<\/td>\n<td>models.Model<\/td>\n<\/tr>\n<tr>\n<td>S3<\/td>\n<td>Script<\/td>\n<td>Automation<\/td>\n<td>Cron \/ triggers<\/td>\n<td>ir.cron \/ ir.actions<\/td>\n<\/tr>\n<tr>\n<td>S4<\/td>\n<td>Schema<\/td>\n<td>Database<\/td>\n<td>Model definition<\/td>\n<td>models.Model (_name)<\/td>\n<\/tr>\n<tr>\n<td>S5<\/td>\n<td>SQL<\/td>\n<td>Reporting<\/td>\n<td>PostgreSQL view<\/td>\n<td>SQL + read-only model<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div class=\"o_editor_banner user-select-none o-contenteditable-false lh-1 d-flex align-items-center alert alert-info pb-0 pt-3\" role=\"status\" data-oe-role=\"status\">\n<p><i class=\"o_editor_banner_icon mb-3 fst-normal\" data-oe-aria-label=\"Banner Info\" aria-label=\"Banner Info\">1\ufe0f\u20e3<\/i><\/p>\n<div class=\"o_editor_banner_content o-contenteditable-true w-100 px-3\">\n<h2><span class=\"h1-fs\"><b>Screen \u2014 UI &amp; Field Mapping<\/b><\/span><\/h2>\n<p><span class=\"h3-fs\">XML views \u00b7 form \u00b7 tree \u00b7 kanban \u00b7 xpath inheritance<\/span><\/p>\n<\/div>\n<\/div>\n<h2>What is Screen?<\/h2>\n<p><span class=\"h3-fs\">The Screen phase defines how the user interacts with the system. In Odoo, this translates directly to XML view definitions. Before writing a single line of Python, the developer documents which fields exist, where they appear in the UI, and who can see or edit them.<\/span><\/p>\n<h3><b>Key decisions in Screen<\/b><\/h3>\n<table class=\"table table-bordered o_table\">\n<tbody>\n<tr>\n<td><b>Decision<\/b><\/td>\n<td><b>What to document<\/b><\/td>\n<\/tr>\n<tr>\n<td>View type<\/td>\n<td>Form (edit one record), Tree\/List (browse many), Kanban (card layout), Pivot (reports)<\/td>\n<\/tr>\n<tr>\n<td>Field type<\/td>\n<td>Char, Float, Many2one, Selection, Boolean, Monetary, Html, Date<\/td>\n<\/tr>\n<tr>\n<td>Inheritance<\/td>\n<td>Which existing Odoo view ID is being inherited (use xpath expressions)<\/td>\n<\/tr>\n<tr>\n<td>Visibility<\/td>\n<td>attrs=invisible \/ readonly per user group or field value<\/td>\n<\/tr>\n<tr>\n<td>Permissions<\/td>\n<td>Which groups (Sales Manager, Inventory User) see or edit the field<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Example \u2014 adding commission fields to sale.order form<\/h3>\n<p><span class=\"h3-fs\">Scenario: Add a Commission Rate and Commission Total field to the Sales Order form view.<\/span><\/p>\n<table class=\"table table-bordered o_table\">\n<tbody>\n<tr>\n<td><b>Technical name<\/b><\/td>\n<td><b>Label<\/b><\/td>\n<\/tr>\n<tr>\n<td>x_comm_rate<\/td>\n<td>Commission %<\/td>\n<\/tr>\n<tr>\n<td>x_comm_total<\/td>\n<td>Total Commission<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div class=\"o_editor_banner user-select-none o-contenteditable-false lh-1 d-flex align-items-center alert alert-warning pb-0 pt-3\" role=\"status\" data-oe-role=\"status\">\n<p><i class=\"o_editor_banner_icon mb-3 fst-normal\" data-oe-aria-label=\"Banner Warning\" aria-label=\"Banner Warning\">2\ufe0f\u20e3<\/i><\/p>\n<div class=\"o_editor_banner_content o-contenteditable-true w-100 px-3\">\n<h1><span class=\"h1-fs\">Pseudo Code \u2014 Business Logic<\/span><\/h1>\n<p><span class=\"h3-fs\">Python \u00b7 @api.depends \u00b7 @api.constrains \u00b7 UserError<\/span><\/p>\n<\/div>\n<\/div>\n<h2><b>What is Pseudo Code?<\/b><\/h2>\n<p><span class=\"h3-fs\">Before writing actual Python, every developer must document the logic in plain, structured English. This ensures that business rules are agreed upon before implementation begins \u2014 preventing costly rewrites when requirements are misunderstood.<\/span><\/p>\n<h3><b>Logic types<\/b><\/h3>\n<table class=\"table table-bordered o_table\">\n<tbody>\n<tr>\n<td><b>Type<\/b><\/td>\n<td><b>When to use<\/b><\/td>\n<\/tr>\n<tr>\n<td>Computed field (@api.depends)<\/td>\n<td>Auto-recalculates a field value when a source field changes. Use store=True to save to DB.<\/td>\n<\/tr>\n<tr>\n<td>Constraint (@api.constrains)<\/td>\n<td>Raises a ValidationError to block saving invalid data (e.g. credit limit exceeded).<\/td>\n<\/tr>\n<tr>\n<td>Onchange (@api.onchange)<\/td>\n<td>Updates the UI instantly before saving (useful for live feedback to the user).<\/td>\n<\/tr>\n<tr>\n<td>Override (super())<\/td>\n<td>Extend Odoo built-in methods like action_confirm or write to inject custom logic.<\/td>\n<\/tr>\n<tr>\n<td>UserError<\/td>\n<td>Raise a user-facing error message to halt an operation with a clear explanation.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3><span class=\"h2-fs\">Example \u2014 commission total computed field<\/span><\/h3>\n<p><span class=\"h3-fs\">Pseudo logic: If the order is in state &#8216;sale&#8217; or &#8216;done&#8217;, calculate commission as untaxed amount \u00d7 rate. If no rate is set, default to 10%. Otherwise set commission to zero.<\/span><\/p>\n<div class=\"o_editor_banner user-select-none o-contenteditable-false lh-1 d-flex align-items-center alert alert-success pb-0 pt-3\" role=\"status\" data-oe-role=\"status\">\n<p><i class=\"o_editor_banner_icon mb-3 fst-normal\" data-oe-aria-label=\"Banner Success\" aria-label=\"Banner Success\">3\ufe0f\u20e3<\/i><\/p>\n<div class=\"o_editor_banner_content o-contenteditable-true w-100 px-3\">\n<h1><span class=\"h1-fs\"><b>Script \u2014 Automation &amp; Scheduled Actions<\/b><\/span><\/h1>\n<p><span class=\"h3-fs\">Cron jobs \u00b7 automated actions \u00b7 server actions \u00b7 API sync<\/span><\/p>\n<\/div>\n<\/div>\n<h2><b>What is Script?<\/b><\/h2>\n<p><span class=\"h3-fs\">Script refers to automated actions and background processes that keep the ERP running without manual intervention. The focus is: who does what, in what order. This covers scheduled jobs, event-based triggers (on create \/ on update \/ on delete), server actions tied to buttons, and external API integrations.<\/span><\/p>\n<h3><b>Script types<\/b><\/h3>\n<table class=\"table table-bordered o_table\">\n<tbody>\n<tr>\n<td><b>Type<\/b><\/td>\n<td><b>Description<\/b><\/td>\n<\/tr>\n<tr>\n<td>Automated Action<\/td>\n<td>Triggers on record creation, update, or deletion. Configured in Settings \u2192 Technical \u2192 Automation.<\/td>\n<\/tr>\n<tr>\n<td>Scheduled Action (Cron)<\/td>\n<td>Runs at a fixed interval (hourly, daily, monthly). Ideal for reports, syncs, and digest emails.<\/td>\n<\/tr>\n<tr>\n<td>Server Action<\/td>\n<td>Multi-step process triggered by a button click (e.g. Batch Validate, Send Reminders).<\/td>\n<\/tr>\n<tr>\n<td>External API Script<\/td>\n<td>Maps the data flow for third-party integrations (Shopify, WooCommerce, payment gateways).<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3><b>Workflow example \u2014 sales order lifecycle<\/b><\/h3>\n<table class=\"table table-bordered o_table\">\n<tbody>\n<tr>\n<td><b>Step<\/b><\/td>\n<td><b>Actor &amp; Action<\/b><\/td>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>Sales Executive creates quotation<\/td>\n<\/tr>\n<tr>\n<td>2<\/td>\n<td>Customer confirms quotation (portal or email)<\/td>\n<\/tr>\n<tr>\n<td>3<\/td>\n<td>Sales Executive converts to Sales Order<\/td>\n<\/tr>\n<tr>\n<td>4<\/td>\n<td>System automatically checks stock availability<\/td>\n<\/tr>\n<tr>\n<td>5a (stock available)<\/td>\n<td>System creates Delivery Order \u2192 Warehouse validates<\/td>\n<\/tr>\n<tr>\n<td>5b (stock unavailable)<\/td>\n<td>System creates backorder, notifies warehouse<\/td>\n<\/tr>\n<tr>\n<td>6<\/td>\n<td>Invoice generated after delivery confirmation<\/td>\n<\/tr>\n<tr>\n<td>7<\/td>\n<td>Accounts records payment \u2192 order marked as paid<\/td>\n<\/tr>\n<tr>\n<td>Exception<\/td>\n<td>If payment overdue \u2192 block new orders for that customer<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div><\/div>\n<div class=\"o_editor_banner user-select-none o-contenteditable-false lh-1 d-flex align-items-center alert alert-danger pb-0 pt-3\" role=\"status\" data-oe-role=\"status\">\n<p><i class=\"o_editor_banner_icon mb-3 fst-normal\" data-oe-aria-label=\"Banner Danger\" aria-label=\"Banner Danger\">4\ufe0f\u20e3<\/i><\/p>\n<div class=\"o_editor_banner_content o-contenteditable-true w-100 px-3\">\n<h1><span class=\"h1-fs\"><b>Schema \u2014 Database &amp; Model Structure<\/b><\/span><\/h1>\n<p><span class=\"h3-fs\">models.Model \u00b7 Many2one \u00b7 One2many \u00b7 ondelete strategy<\/span><\/p>\n<\/div>\n<\/div>\n<h2><b>What is Schema?<\/b><\/h2>\n<p>Schema is the blueprint of the data layer. In Odoo, this means Python model class definitions \u2014 which become real PostgreSQL tables via the ORM. Field types, relationships between models, and deletion rules all need to be thought through before writing code.<\/p>\n<h3><b>_inherit vs _name \u2014 the most important distinction<\/b><\/h3>\n<table class=\"table table-bordered o_table\">\n<tbody>\n<tr>\n<td><b>Keyword<\/b><\/td>\n<td><b>Behaviour<\/b><\/td>\n<\/tr>\n<tr>\n<td>_inherit = &#8216;sale.order&#8217;<\/td>\n<td>Adds fields\/methods to an existing model and its table. No new table is created.<\/td>\n<\/tr>\n<tr>\n<td>_name = &#8216;commission.history&#8217;<\/td>\n<td>Creates a brand new model with its own PostgreSQL table.<\/td>\n<\/tr>\n<tr>\n<td>Both together<\/td>\n<td>Copies the existing model into a new one (prototype inheritance \u2014 rarely used).<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3><b>Relation types<\/b><\/h3>\n<table class=\"table table-bordered o_table\">\n<tbody>\n<tr>\n<td><b>Type<\/b><\/td>\n<td><b>Meaning &amp; Example<\/b><\/td>\n<\/tr>\n<tr>\n<td>Many2one<\/td>\n<td>Many records link to one parent. e.g. sale.order \u2192 res.partner (customer).<\/td>\n<\/tr>\n<tr>\n<td>One2many<\/td>\n<td>One parent has many children. e.g. sale.order \u2192 sale.order.line (order lines).<\/td>\n<\/tr>\n<tr>\n<td>Many2many<\/td>\n<td>Cross-links between two models. e.g. product.template \u2194 product.tag.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div class=\"o_editor_banner user-select-none o-contenteditable-false lh-1 d-flex align-items-center alert alert-info pb-0 pt-3\" role=\"status\" data-oe-role=\"status\">\n<p><i class=\"o_editor_banner_icon mb-3 fst-normal\" data-oe-aria-label=\"Banner Info\" aria-label=\"Banner Info\">5\ufe0f\u20e3<\/i><\/p>\n<div class=\"o_editor_banner_content o-contenteditable-true w-100 px-3\">\n<h1><span class=\"h1-fs\"><b>SQL \u2014 Reporting &amp; Data Queries<\/b><\/span><\/h1>\n<p><span class=\"h3-fs\">PostgreSQL views \u00b7 JOIN \u00b7 GROUP BY \u00b7 read-only Odoo model<\/span><\/p>\n<\/div>\n<\/div>\n<h2><b>What is SQL?<\/b><\/h2>\n<p><span class=\"h3-fs\">While Odoo uses an ORM (Object-Relational Mapper) for most operations, complex reporting often requires raw SQL for performance. The SQL layer is used to build management dashboards by creating PostgreSQL VIEWs and exposing them as read-only Odoo models accessible via pivot and graph views.<\/span><\/p>\n<h3><b>When to use raw SQL<\/b><\/h3>\n<table class=\"table table-bordered o_table\">\n<tbody>\n<tr>\n<td><b>Scenario<\/b><\/td>\n<td><b>Reason<\/b><\/td>\n<\/tr>\n<tr>\n<td>Cross-module reports<\/td>\n<td>Join Sales + Accounting + Inventory in a single query \u2014 ORM can&#8217;t do this cleanly.<\/td>\n<\/tr>\n<tr>\n<td>Aggregation at scale<\/td>\n<td>SUM, COUNT, AVG across thousands of records is far faster in native SQL.<\/td>\n<\/tr>\n<tr>\n<td>Management dashboards<\/td>\n<td>Read-only model backed by a PostgreSQL VIEW for pivot\/graph views.<\/td>\n<\/tr>\n<tr>\n<td>Performance tuning<\/td>\n<td>Bypass ORM overhead for high-volume analytics queries.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div><\/div>\n<h2>Example<\/h2>\n<p><span class=\"h4-fs\">Let&#8217;s\u00a0look at how all\u00a05 layers connect in a real Odoo customization project \u2014 the complete flow from requirement to deployed feature<\/span><\/p>\n<p><img decoding=\"async\" class=\"img-fluid\" src=\"https:\/\/ten10.in\/web\/image\/24599-9d5a5e08\/image.png?access_token=50cf6736-d6f0-439c-a48c-60649a5c5e73\" data-file-name=\"image.png\" \/><\/p>\n<h1><b>Putting It All Together \u2014 Module Structure<\/b><\/h1>\n<table class=\"table table-bordered o_table\">\n<tbody>\n<tr>\n<th class=\"o_table_header\"><b>File \/ Folder<\/b><\/th>\n<th class=\"o_table_header\"><b>5S Layer &amp; Purpose<\/b><\/th>\n<\/tr>\n<tr>\n<td>__manifest__.py<\/td>\n<td>Module metadata \u2014 name, version, depends, data file list<\/td>\n<\/tr>\n<tr>\n<td>models\/sale_order.py<\/td>\n<td>S2 Pseudo Code + S4 Schema \u2014 extends sale.order with commission fields and computed logic<\/td>\n<\/tr>\n<tr>\n<td>models\/commission_history.py<\/td>\n<td>S4 Schema \u2014 new commission.history model with its own table<\/td>\n<\/tr>\n<tr>\n<td>views\/sale_commission_views.xml<\/td>\n<td>S1 Screen \u2014 XML view inheriting sale.order form, adding commission fields<\/td>\n<\/tr>\n<tr>\n<td>views\/commission_report_views.xml<\/td>\n<td>S5 SQL \u2014 pivot and graph views for the commission analysis report<\/td>\n<\/tr>\n<tr>\n<td>data\/cron_commission.xml<\/td>\n<td>S3 Script \u2014 scheduled action for monthly commission digest email<\/td>\n<\/tr>\n<tr>\n<td>data\/automated_action.xml<\/td>\n<td>S3 Script \u2014 event-based trigger (e.g. on order confirmation)<\/td>\n<\/tr>\n<tr>\n<td>report\/commission_analysis.py<\/td>\n<td>S5 SQL \u2014 read-only Odoo model backed by PostgreSQL view<\/td>\n<\/tr>\n<tr>\n<td>report\/commission_analysis.sql<\/td>\n<td>S5 SQL \u2014 raw SQL for the PostgreSQL VIEW (optional separate file)<\/td>\n<\/tr>\n<tr>\n<td>security\/ir.model.access.csv<\/td>\n<td>Access rights \u2014 which user groups can read\/write each model<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>5S Framework is a technical documentation standard used for\u00a0Odoo Architects and Developers to bridge the gap between functional requirements and [&hellip;]<\/p>\n","protected":false},"author":9,"featured_media":36341,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"class_list":["post-36340","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-product"],"acf":[],"_links":{"self":[{"href":"https:\/\/tenthplanet.in\/odoo\/wp-json\/wp\/v2\/posts\/36340","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tenthplanet.in\/odoo\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tenthplanet.in\/odoo\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tenthplanet.in\/odoo\/wp-json\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/tenthplanet.in\/odoo\/wp-json\/wp\/v2\/comments?post=36340"}],"version-history":[{"count":0,"href":"https:\/\/tenthplanet.in\/odoo\/wp-json\/wp\/v2\/posts\/36340\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tenthplanet.in\/odoo\/wp-json\/wp\/v2\/media\/36341"}],"wp:attachment":[{"href":"https:\/\/tenthplanet.in\/odoo\/wp-json\/wp\/v2\/media?parent=36340"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tenthplanet.in\/odoo\/wp-json\/wp\/v2\/categories?post=36340"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tenthplanet.in\/odoo\/wp-json\/wp\/v2\/tags?post=36340"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}