Skip to content

Master Odoo 18 POS OWL

  • owl
Odoo 18 POS OWL

Welcome to the world of Odoo 18 POS OWL development! If you’re looking to transform your Odoo Point of Sale experience, you’ve come to the right place. This guide will walk you through the essentials of customizing the Odoo POS using the powerful OWL (Odoo Web Library) framework. We’ll explore how to modify the look and feel, extend functionalities, and create new components, turning your POS into a tailored solution that perfectly fits your business needs. Get ready to unlock the full potential of your Odoo 18 POS OWL system.

Your Ultimate Customization Guide!

source code : https://github.com/alferjay/odoo-owl-tutorial/tree/pos_inheritance_v2/custom-addons/owl

This tutorial is not for absolute beginners; a foundational understanding of JavaScript, XML, and general Odoo module structure will be beneficial. We’ll cover everything from basic UI tweaks like changing logos and colors to more advanced JavaScript extensions for adding new features and overriding existing methods.

Understanding the Odoo 18 POS OWL Structure: The Foundation of Customization

Before diving into coding, it’s crucial to understand the underlying structure of an Odoo 18 POS OWL module and the key files you’ll be interacting with.

Setting Up Your Custom Odoo 18 POS OWL Module

First, you’ll need a custom Odoo module. Let’s call it my_custom_pos.

  • Create the Module Directory:
    Create a new directory named my_custom_pos within your Odoo addons path.
  • The __manifest__.py File:
    Next, create an __manifest__.py file inside my_custom_pos. This file defines your module’s metadata.
# my_custom_pos/__manifest__.py
{
    'name': 'My Custom POS Enhancements for Odoo 18 POS OWL',
    'version': '1.0',
    'summary': 'Customizations for the Odoo 18 Point of Sale using OWL.',
    'author': 'Your Name',
    'category': 'Point of Sale',
    'depends': ['point_of_sale'], # Crucial dependency
    'assets': {
        'point_of_sale.assets': [
            # SCSS files for styling
            'my_custom_pos/static/src/scss/pos.scss',
            'my_custom_pos/static/src/scss/favorite_products.scss',
            # XML files for OWL templates
            'my_custom_pos/static/src/xml/chrome.xml',
            'my_custom_pos/static/src/xml/payment_screen.xml',
            'my_custom_pos/static/src/xml/favorite_products.xml',
            # JavaScript files for OWL components and logic
            'my_custom_pos/static/src/js/payment_screen.js',
            'my_custom_pos/static/src/js/favorite_products.js',
            'my_custom_pos/static/src/js/pos_global_state.js',
        ],
    },
    'installable': True,
    'application': False,
    'auto_install': False,
}
  • Important: The point_of_sale.assets key is where you register all your static assets (JS, XML, SCSS) that need to be loaded by the POS.
  • The __init__.py File:
    Then, create an __init__.py file in my_custom_pos to make it a Python package. If you add Python models, import them here. # my_custom_pos/__init__.py from . import models And an __init__.py inside my_custom_pos/models: # my_custom_pos/models/__init__.py from . import product_product

Exploring Key JavaScript Files in the Native Odoo 18 POS OWL System

The Odoo POS JavaScript codebase is organized into several key files within the point_of_sale module (typically found under addons/point_of_sale/static/src/js/ in older versions, and app/ subdirectories in Odoo 17+ for OWL components). Understanding their roles is vital for Odoo 18 POS OWL development:

  • chrome.js (or its equivalent in app/chrome/): This is often the root component of the POS application. It handles the main layout, including headers, and manages the overall application state.
  • registry.js (often part of app/store/registries.js): Odoo POS uses a registry system to manage and extend components, popups, screens, and more. This allows for modularity and easier customization without directly altering core files. You’ll frequently use Registries.Component.add() or Registries.Component.extend().
  • models.js (often part of app/store/models.js or pos_store.js): This file (or its new equivalent) defines the data models used within the POS, such as how orders, products, and customers are structured and handled on the client-side. It also includes logic for RPC calls to the backend.
  • db.js (often part of app/store/db.js): The POS database (this.env.pos.db or this.pos.db) is an in-memory JavaScript database that stores data fetched from the server (products, categories, customers, etc.) for quick offline access. This file manages the initialization and interaction with this local database.
  • Screen Components (e.g., ProductScreen.js, PaymentScreen.js in app/screens/): Each major view in the POS (like the product selection screen or payment screen) is an OWL component.

Core XML Structure in Odoo 18 POS OWL Applications

OWL components use XML templates to define their structure. Key native XML files include:

  • chrome.xml (or pos_app.xml in app/chrome/templates/): This template defines the main layout of the POS, including the header, content area, and potentially placeholders for notifications and popups.
  • Screen Templates (e.g., ProductScreen.xml, PaymentScreen.xml in app/screens/ respective subdirectories): Each screen component has an associated XML template defining its visual structure. For instance, ProductScreen.xml typically outlines the left pane (order widget, numpad) and the right pane (product list).

The OWL Component Lifecycle and Essential Concepts for Odoo 18 POS OWL

When building with Odoo 18 POS OWL, you’ll constantly use these core OWL concepts:

  • PosComponent: Most custom POS components will extend PosComponent (from @point_of_sale/app/store/pos_store or a similar path). This base component provides access to the POS environment (this.env.pos or this.pos).
  • setup(): This is the constructor equivalent in OWL components. Importantly, always call super.setup(...args) at the beginning of your setup method to ensure the parent component initializes correctly.
  • static template: This static property in your JS component class links to its XML template (e.g., static template = 'my_custom_pos.MyCustomButton';).
  • useState(initialState): Imported from @odoo/owl, this hook makes your component’s state reactive. Any changes to state variables will trigger a re-render of the component.
  • useService(serviceName): This hook allows you to access various Odoo services like orm (for RPC calls), popup, notification, and pos itself.
  • useListener(eventName, selector?, callback): This hook attaches event listeners to elements within your component’s template (or globally if no selector).
  • trigger(eventName, detail?): Used to dispatch custom events that other components (often parents) can listen for.

Customizing the Look and Feel with CSS and XML in Odoo 18 POS OWL

Let’s start with some visual customizations. These often involve SCSS for styling and XML for template modifications.

Personalizing Your Odoo 18 POS OWL Interface: Changing the POS Logo

The POS logo is usually defined in the chrome.xml (or equivalent main app) template.

  • Create an img Directory:
    Inside my_custom_pos/static/src/, create an img directory. Place your new logo (e.g., my_logo.png) here.
  • Inherit chrome.xml:
    Create my_custom_pos/static/src/xml/chrome.xml:
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
    <t t-name="point_of_sale.Chrome" t-inherit="point_of_sale.Chrome" t-inherit-mode="extension">
        <xpath expr="//img[hasclass('pos-logo')]" position="attributes">
            <attribute name="src">/my_custom_pos/static/src/img/my_logo.png</attribute>
        </xpath>
    </t>
</templates>
  • This code finds the img tag with the class pos-logo and changes its src attribute to your new logo.

Modifying Background Colors and Styles with SCSS in Your Odoo 18 POS OWL System

You can add custom styles using SCSS.

  • Create pos.scss:
    Create my_custom_pos/static/src/scss/pos.scss.
  • Add Styles:
    For example, to change the header background and the product list background:
// my_custom_pos/static/src/scss/pos.scss

// Targeting the POS header
// Inspect your POS to find the correct class for the header bar.
// It might be '.pos .pos-header' or a more specific class.
.pos .pos-header {
    background-color: #333A40; // A dark green example
    // You might need to adjust text color for readability
    color: white;

    .pos-logo {
        height: 80% !important; // Adjust logo height if needed
        margin: 5px;
    }
}

// Targeting the product list area (right pane in ProductScreen)
// Inspect to find the correct class. It could be '.pos .rightpane' or similar.
.pos .product-screen .rightpane { // Example selector
    background-color: #FFFFFF; // Make product list background white
}

// Example: Darkening the order list (left pane)
.pos .product-screen .leftpane { // Example selector
    background-color: #333A40;
    color: #F0F0F0;

    .orderline {
        color: #D0D0D0;
        .product-name {
            color: #FFFFFF;
        }
        .price{
            color: #FFFFFF;
        }
    }
    .order-summary {
         .entry {
            color: #FFFFFF;
         }
    }
    .numpad button {
        background-color: #4A525A;
        color: white;
        &:hover {
            background-color: #5A626A;
        }
    }
    .actionpad button {
        background-color: #00A09D; // Odoo green for action buttons
        color: white;
        &:hover {
            background-color: #00B0AD;
        }
    }
}
  • Note: Always inspect the live POS interface with your browser’s developer tools to find the exact CSS classes you need to target. Classes can change between Odoo versions.

Efficiently Rearranging Elements in the Odoo 18 POS OWL Layout

Let’s say you want to swap the order of the left and right panes on the ProductScreen. This is often controlled by CSS Flexbox properties.

  • Target Panes in SCSS:
    In your my_custom_pos/static/src/scss/pos.scss:
// my_custom_pos/static/src/scss/pos.scss

// Assuming .product-screen uses display: flex for .leftpane and .rightpane
.pos .product-screen {
    // This is an example; the parent container of leftpane and rightpane
    // needs to be a flex container for 'order' to work.
    // display: flex; // Ensure the parent is a flex container if not already

    .leftpane {
        order: 2; // Move left pane to the second position
    }

    .rightpane {
        order: 1; // Move right pane to the first position
    }
}
  • This demonstrates how CSS order can rearrange flex items. The success of this depends on the existing CSS structure of ProductScreen.

Extending POS Functionality with JavaScript and OWL in Odoo 18

Now for the more dynamic customizations using JavaScript and the Odoo 18 POS OWL framework.

Integrating a Custom Button into the Payment Screen of Your Odoo 18 POS OWL

Let’s add a new button to the PaymentScreen.

  • Create the Button Component (JS):
    Create my_custom_pos/static/src/js/my_custom_payment_button.js:
// my_custom_pos/static/src/js/my_custom_payment_button.js
odoo.define('my_custom_pos.MyCustomPaymentButton', function(require) {
    'use strict';

    const { PosComponent } = require("@point_of_sale/app/store/pos_store");
    const { Registries } = require("@point_of_sale/app/store/registries");
    const { useService } = require("@web/core/utils/hooks");

    class MyCustomPaymentButton extends PosComponent {
        setup() {
            super.setup();
            this.notification = useService("notification");
        }

        async onClick() {
            // Custom logic for the button click
            console.log("Custom Payment Button Clicked!");
            this.notification.add("Custom button was pressed!", { type: 'info' });
            // You can call other methods or services here
            // For example, this.env.pos.showScreen('ProductScreen');
        }
    }
    MyCustomPaymentButton.template = 'my_custom_pos.MyCustomPaymentButton';

    Registries.Component.add(MyCustomPaymentButton);

    return MyCustomPaymentButton;
});
  • Create the Button Template (XML):
    Create my_custom_pos/static/src/xml/my_custom_payment_button_template.xml (and register it in __manifest__.py‘s assets, or combine it with other XML files):
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
    <t t-name="my_custom_pos.MyCustomPaymentButton" owl="1">
        <button class="btn btn-primary control-button" t-on-click="onClick">
            Next (Custom)
            <i class="fa fa-arrow-right" role="img" aria-label="Next"/>
        </button>
    </t>
</templates>
  • Self-correction: It’s better to put this button template inside payment_screen.xml or a dedicated button XML file for clarity, then ensure that file is in the manifest. Let’s assume we’ll put it into payment_screen.xml for now.
  • Inherit PaymentScreen.xml to Add the Button:
    Modify/Create my_custom_pos/static/src/xml/payment_screen.xml:
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
    <t t-name="my_custom_pos.MyCustomPaymentButton" owl="1">
        <button class="btn btn-lg btn-primary control-button" style="right: 150px; bottom: 0px; font-size: 1.2em;" t-on-click="onClick">
            Next (Custom)
            <i class="fa fa-arrow-right" role="img" aria-label="Next"/>
        </button>
    </t>

    <t t-name="point_of_sale.PaymentScreen" t-inherit="point_of_sale.PaymentScreen" t-inherit-mode="extension">
        <xpath expr="//div[hasclass('paymentmethods-container')]" position="inside">
             <MyCustomPaymentButton />
        </xpath>
    </t>
</templates>
  • Note: The xpath expression is critical. You need to find a stable element in the original PaymentScreen.xml to append your button to. Using <MyCustomPaymentButton /> directly works because we registered it.
  • The JavaScript file for the Payment Screen (if you need to add methods called by the button):
    If your MyCustomPaymentButton needs to call methods on its parent PaymentScreen, you’d extend PaymentScreen.js. For the current button that only logs and notifies, this step isn’t strictly needed yet, but for the sake of demonstrating method overriding:
    Create my_custom_pos/static/src/js/payment_screen.js:
// my_custom_pos/static/src/js/payment_screen.js
odoo.define('my_custom_pos.PaymentScreen', function(require) {
    'use_strict';

    const PaymentScreen = require("@point_of_sale/app/screens/payment_screen/payment_screen");
    const Registries = require("@point_of_sale/app/store/registries");

    const MyCustomPosPaymentScreen = (OriginalPaymentScreen) =>
        class extends OriginalPaymentScreen {
            setup() {
                super.setup();
                console.log("Inherited Payment Screen Setup Correctly!");
            }

            // Example: Overriding an existing method or adding a new one
            async addNewPaymentLine({ detail: paymentMethod }) {
                console.log("Inherited addNewPaymentLine for method:", paymentMethod.name);
                // Always call super if you are overriding and want original functionality
                const result = await super.addNewPaymentLine(...arguments);
                // Add custom logic after original method execution
                console.log("Custom logic after adding payment line.");
                return result;
            }

            // Method for our new button (if it needs to interact with screen state)
            async goNext() {
                console.log("Go Next method called from custom button on Payment Screen!");
                // Example: Navigate back to product screen
                // this.pos.showScreen('ProductScreen');
            }
        };

    Registries.Component.extend(PaymentScreen, MyCustomPosPaymentScreen);

    return PaymentScreen;
});

Advanced Customization: Creating a “Favorite Products” Section in Your Odoo 18 POS OWL Interface

This is a more involved example that combines XML, SCSS, and JavaScript, including fetching data.

  • Design the XML Layout:
    Create my_custom_pos/static/src/xml/favorite_products.xml:
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
    <t t-name="my_custom_pos.FavoriteProductsSection" owl="1">
        <div class="favorite-products-container">
            <h3>Favorite Products</h3>
            <div class="favorite-products-list">
                <t t-if="favoriteProducts.length === 0">
                    <p>No favorite products configured.</p>
                </t>
                <t t-foreach="favoriteProducts" t-as="product" t-key="product.id">
                    <div class="product favorite-product-item" t-on-click="() => this.clickFavoriteProduct(product)">
                        <div class="product-img">
                            <img t-att-src="getProductImage(product)" t-att-alt="product.display_name"/>
                        </div>
                        <div class="product-name">
                            <t t-esc="product.display_name"/>
                        </div>
                    </div>
                </t>
            </div>
        </div>
    </t>

    <t t-name="point_of_sale.ProductScreen" t-inherit="point_of_sale.ProductScreen" t-inherit-mode="extension">
        <xpath expr="//div[hasclass('rightpane')]" position="inside">
            <FavoriteProductsSection />
        </xpath>
    </t>
</templates>
  • Style with SCSS:
    Create my_custom_pos/static/src/scss/favorite_products.scss: //
// my_custom_pos/static/src/scss/favorite_products.scss
.favorite-products-container {
    padding: 10px;
    background-color: #f9f9f9;
    border-top: 1px solid #ddd;
    min-height: 150px; // Ensure it has some height
    margin-top: 10px;

    h3 {
        margin-top: 0;
        margin-bottom: 10px;
        font-size: 1.2em;
        color: #555;
    }

    .favorite-products-list {
        display: flex;
        flex-wrap: wrap; // Allow items to wrap
        gap: 10px; // Space between items
        overflow-x: auto; // Allow horizontal scrolling if needed, or use flex-wrap

        .favorite-product-item {
            flex: 0 0 100px; // Flex-grow, flex-shrink, flex-basis
            border: 1px solid #ccc;
            border-radius: 3px;
            padding: 5px;
            text-align: center;
            cursor: pointer;
            background-color: white;
            box-shadow: 0 1px 2px rgba(0,0,0,0.1);

            &:hover {
                box-shadow: 0 2px 4px rgba(0,0,0,0.15);
            }

            .product-img {
                img {
                    max-width: 80px;
                    max-height: 80px;
                    object-fit: contain;
                }
            }
            .product-name {
                font-size: 0.9em;
                margin-top: 5px;
                height: 2.5em; // Ensure consistent height for names
                overflow: hidden;
            }
        }
    }
}
  • Create the Python Model Method (Backend Logic):
    Create my_custom_pos/models/product_product.py:
# my_custom_pos/models/product_product.py
from odoo import models, fields, api

class ProductProduct(models.Model):
    _inherit = 'product.product'

    # You could add a boolean field 'is_favorite' or use product tags.
    # For this example, we'll use product tags.
    # Ensure you have a product tag named 'Favorite' in your Odoo backend.

    @api.model
    def get_pos_favorite_products(self):
        """Returns IDs of products tagged as 'Favorite' and available in POS."""
        # Find the 'Favorite' product tag
        favorite_tag = self.env['product.tag'].search([('name', '=', 'Favorite')], limit=1)
        if not favorite_tag:
            return []

        products = self.search([
            ('available_in_pos', '=', True),
            ('product_tag_ids', 'in', favorite_tag.id)
        ])
        return products.ids # Return only IDs as POS will load full data from its DB
  • Create the JavaScript Logic for Favorite Products and Extend PosGlobalState:
    • pos_global_state.js (To load favorite products on POS start):
      Create my_custom_pos/static/src/js/pos_global_state.js:
// my_custom_pos/static/src/js/pos_global_state.js
odoo.define('my_custom_pos.PosGlobalState', function(require) {
    'use strict';

    const { PosGlobalState } = require("@point_of_sale/app/store/pos_store");
    const Registries = require("@point_of_sale/app/store/registries");

    const MyCustomPosGlobalState = (OriginalPosGlobalState) =>
        class extends OriginalPosGlobalState {
            constructor() {
                super(...arguments);
                this.favoriteProductIds = []; // Initialize
            }

            async _loadNewData() {
                await super._loadNewData(...arguments);
                // Fetch favorite product IDs
                const fav_ids = await this.env.services.rpc({
                    model: 'product.product',
                    method: 'get_pos_favorite_products',
                    args: [[]], // Pass context if needed
                    kwargs: { context: this.env.session.user_context },
                });
                this.favoriteProductIds = fav_ids;
                console.log("Favorite Product IDs loaded:", this.favoriteProductIds);
            }

            getFavoriteProducts() {
                // Get full product objects from the POS DB using the loaded IDs
                return this.favoriteProductIds.map(id => this.db.get_product_by_id(id)).filter(p => p);
            }
        };

    Registries.Model.extend(PosGlobalState, MyCustomPosGlobalState);

    return PosGlobalState;
});
  • favorite_products.js (Component logic):
    Create my_custom_pos/static/src/js/favorite_products.js:
// my_custom_pos/static/src/js/favorite_products.js
odoo.define('my_custom_pos.FavoriteProductsSection', function(require) {
    'use strict';

    const { PosComponent } = require("@point_of_sale/app/store/pos_store");
    const Registries = require("@point_of_sale/app/store/registries");
    const { useState, onWillStart, useComponent } = require("@odoo/owl");

    class FavoriteProductsSection extends PosComponent {
        setup() {
            super.setup();
            // this.state = useState({ products: [] });
            // No need for useState here if we directly access this.env.pos.getFavoriteProducts()
            // which itself might be reactive or fetched once.

            // onWillStart is not ideal here because PosGlobalState already loads it.
            // We'll fetch them from the already populated PosGlobalState
        }

        get favoriteProducts() {
            // Access the method from the extended PosGlobalState
            return this.env.pos.getFavoriteProducts() || [];
        }

        getProductImage(product) {
            return `/web/image/product.product/${product.id}/image_128`;
        }

        async clickFavoriteProduct(product) {
            // Logic similar to clicking a product in the main list
            this.env.pos.addProductToCurrentOrder(product);
        }
    }
    FavoriteProductsSection.template = 'my_custom_pos.FavoriteProductsSection';

    Registries.Component.add(FavoriteProductsSection);

    return FavoriteProductsSection;
});
  • Install/Upgrade Your Module:
    After adding these files and updating __manifest__.py, install (or upgrade) your my_custom_pos module in Odoo. Then, refresh your POS interface.

This detailed “Favorite Products” example shows how to interact with the backend, modify global POS state, and render new UI elements within an existing screen in an Odoo 18 POS OWL application.

Navigating Between Screens in Your Odoo 18 POS OWL Application

To switch between different POS screens (e.g., from a custom screen back to ProductScreen):

// Inside an OWL component method
this.env.pos.showScreen('ProductScreen');
// or to show a custom screen you registered:
// this.env.pos.showScreen('MyCustomScreenName');

Creating Custom Screens and Popups in Odoo 18 POS OWL (Brief Overview)

  • Custom Screens:
    • Define a JS class extending PosComponent.
    • Create its corresponding XML template.
    • Register it using Registries.Component.add('YourScreenName', YourScreenClass);.
    • Navigate to it using this.env.pos.showScreen('YourScreenName');.
  • Custom Popups:
    • Define a JS class extending AbstractAwaitablePopup (from @point_of_sale/app/utils/popUp/abstract_awaitable_popup).
    • Implement getPayload() to return data when the popup closes.
    • Register it: Registries.Component.add('YourPopupName', YourPopupClass);.
    • Show it: const result = await this.env.services.popup.add('YourPopupName', { title: 'My Popup', body: 'Hello!' });.

Best Practices and Key Takeaways for Odoo 18 POS OWL Development

  • super.setup(): Always call super.setup(...args) first in your setup() method when extending components.
  • Consistent Naming: Ensure your JS module names (odoo.define), static template names in JS, and t-name attributes in XML are consistent.
  • patch Utility: For more complex modifications to existing components, especially their internal structures or getters/setters that are not easily extensible via class inheritance, Odoo provides a patch utility (@web/core/utils/patch). This allows targeted modifications.
  • RPC vs. Local DB (this.env.pos.db):
    • Use this.env.pos.db for frequently accessed data that’s loaded once (products, categories).
    • Use RPC calls (this.env.services.rpc) for actions requiring real-time backend interaction or data not typically stored in the POS DB.
  • onWillStart() Hook: Use this asynchronous hook in your components to perform any pre-rendering setup, like fetching data via RPC, before the component is first rendered.
  • Developer Tools: Your browser’s developer tools are indispensable for inspecting elements, debugging JavaScript, and understanding the existing structure of the Odoo 18 POS OWL application.

Conclusion: Empowering Your Odoo 18 POS OWL Experience

Customizing the Odoo 18 POS OWL interface offers incredible flexibility to tailor the Point of Sale to your specific operational workflows. By understanding the core concepts, file structures, and leveraging the power of OWL components, services, and hooks, you can significantly enhance user experience and efficiency.

This guide has provided a comprehensive walkthrough of common customization tasks. Remember to explore the official Odoo documentation and community resources for even deeper insights.

Happy Odoo 18 POS OWL coding!

For further official Odoo development resources, visit the Odoo Developer Documentation.


Discover more from teguhteja.id

Subscribe to get the latest posts sent to your email.

Tags:

Leave a Reply

WP Twitter Auto Publish Powered By : XYZScripts.com