Skip to content

Inherit Odoo 18 PoS: 5 Steps to a Revolutionary Interface!

Inherit Odoo 18 PoS

This tutorial will expertly guide you through the process to inherit Odoo 18 PoS (Point of Sale), enabling you to craft a truly customized user experience. Consequently, you will learn to build new screens, integrate custom buttons, design interactive popups, and even update backend data directly from the PoS interface without full page reloads. Furthermore, by mastering these OWL (Odoo Web Library) techniques, you can significantly enhance the functionality and efficiency of your Odoo 18 Point of Sale system, making it a perfect solution when you need to inherit Odoo 18 PoS for specific business needs.


5 Powerful Steps to Revolutionize Your Interface and Inherit Odoo 18 PoS

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

Why Inherit Odoo 18 PoS? Unleashing Custom Potential

The ability to inherit Odoo 18 PoS opens up a world of possibilities for tailoring the Point of Sale to your unique operational requirements. Firstly, standard PoS interfaces, while robust, may not always cater to niche workflows or specialized tasks your business performs. Secondly, by extending the PoS, you can introduce custom buttons for quick actions, new screens for displaying targeted information (like a list of favorite products), and interactive popups for data input or confirmations. \

For instance, you might inherit Odoo 18 PoS to add a button that launches a custom product lookup screen or a popup for special customer requests. Ultimately, these customizations lead to increased efficiency, improved user experience, and a PoS system that perfectly aligns with your business logic.

Essential Toolkit: Prerequisites for Your Journey to Inherit Odoo 18 PoS

Before you begin to inherit Odoo 18 PoS and modify its components, ensure you have the following:

  • A Functioning Odoo 18 Instance: You must have an Odoo 18 development environment with the Point of Sale module installed and running.
  • Developer Mode Activated: Activating developer mode in Odoo is crucial for accessing various developer tools and technical information needed to inherit Odoo 18 PoS.
  • Solid Understanding of Odoo Module Structure: Familiarity with creating custom Odoo modules, including the __manifest__.py file and standard directory conventions, is essential.
  • JavaScript (OWL) Proficiency: A strong grasp of JavaScript, especially the Odoo Web Library (OWL) component architecture, lifecycle hooks, and reactivity, is fundamental to inherit Odoo 18 PoS components.
  • XML Knowledge: You will use XML extensively for defining PoS templates and views.
  • Basic Python Skills: While OWL handles the frontend, any backend logic (like custom methods on models) your PoS customizations interact with will require Python.
  • A Reliable Code Editor: Tools like VSCode will make your development process smoother and more efficient.

Step-by-Step Guide: How to Effectively Inherit Odoo 18 PoS Components

This section provides a comprehensive walkthrough to inherit Odoo 18 PoS, covering the creation of a new screen, adding a control button, and implementing a custom popup. We’ll use an example of creating a “Favorite Products” feature.

Step 1: Setting Up Your Custom Odoo Module for PoS Enhancements

First, you must create a new custom Odoo module or use an existing one to house your PoS customizations. Let’s name our module custom_pos_features.

  • Create Module Directory:
    Establish a new directory for your module (e.g., custom_pos_features).
    The basic structure will be:
custom_pos_features/
├── __init__.py
├── __manifest__.py
└── static/
    └── src/
        ├── js/         // For your OWL JavaScript components
        │   └── components/
        │       ├── screens/
        │       ├── buttons/
        │       └── popups/
        └── xml/        // For your OWL XML templates
            └── components/
                ├── screens/
                ├── buttons/
                └── popups/
└── models/       // For any Python model extensions
└── views/        // For backend XML views (if any)
  • Configure __manifest__.py:
    This file defines your module and its dependencies, and most importantly, registers your PoS assets.
{
    'name': 'Custom PoS Features (Odoo 18)',
    'version': '18.0.1.0',
    'summary': 'How to inherit Odoo 18 PoS: Adds custom screens, buttons, and popups.',
    'author': 'Your Name',
    'depends': ['point_of_sale'], # Crucial dependency
    'assets': {
        'point_of_sale.assets': [ # Target PoS assets
            # JS Components
            'custom_pos_features/static/src/js/components/screens/favorite_products_screen.js',
            'custom_pos_features/static/src/js/components/buttons/favorite_screen_button.js',
            'custom_pos_features/static/src/js/components/popups/message_popup.js',
            # You can also use wildcards like:
            # 'custom_pos_features/static/src/js/components/**/*.js',

            # XML Templates
            'custom_pos_features/static/src/xml/components/screens/favorite_products_screen.xml',
            'custom_pos_features/static/src/xml/components/buttons/favorite_screen_button.xml',
            'custom_pos_features/static/src/xml/components/popups/message_popup.xml',
            # And wildcards:
            # 'custom_pos_features/static/src/xml/components/**/*.xml',

            # SCSS Styles (if any)
            # 'custom_pos_features/static/src/scss/pos_styles.scss',
        ],
    },
    'installable': True,
    'application': False,
    'license': 'LGPL-3',
}
  • This manifest properly sets up your module to inherit Odoo 18 PoS and include your custom OWL components.

Step 2: Crafting a New Custom PoS Screen (e.g., FavoriteProductsScreen)

Let’s create a new screen that will display favorite products. This is a common way to inherit Odoo 18 PoS for specialized views.

  • JavaScript Component (favorite_products_screen.js):
    Create this file in
/** @odoo-module **/

import { PosComponent } from "@point_of_sale/app/store/pos_store";
import { Registries } from "@point_of_sale/app/store/registries";
import { useService } from "@web/core/utils/hooks";
import { useState } from "@odoo/owl"; // For reactive state

export class FavoriteProductsScreen extends PosComponent {
    static template = "custom_pos_features.FavoriteProductsScreen"; // Link to XML template

    setup() {
        super.setup();
        this.pos = useService("pos"); // Access global PoS data and methods
        this.orm = useService("orm"); // ORM service for backend calls
        this.popup = useService("popup"); // Popup service

        // Reactive state for this screen
        this.state = useState({
            favoriteProducts: [], // Will hold favorite products
            allProducts: [...this.pos.db.get_product_by_category(0)], // Initial list of all products
            activeProduct: null, // Currently selected product for details
            showFavoritesOnly: true, // Toggle state
            popupMessage: "", // Message from popup
            currentDate: new Date().toLocaleDateString(),
        });

        this.loadFavoriteProducts(); // Load favorites when the component is set up
        console.log("FavoriteProductsScreen setup for inherit odoo 18 pos complete.");
    }

    // Method to go back to the ProductScreen
    back() {
        this.pos.showScreen("ProductScreen");
    }

    // Placeholder methods for button actions on this screen
    async onMarkAsFavorite() {
        if (!this.state.activeProduct) return;
        console.log("Marking as favorite:", this.state.activeProduct.display_name);
        await this.orm.call("product.product", "mark_as_favorite_pos", [[this.state.activeProduct.id]]);
        await this.loadFavoriteProducts(); // Refresh list
        this.render(true); // Force re-render
    }

    async onUnmarkAsFavorite() {
        if (!this.state.activeProduct) return;
        console.log("Unmarking as favorite:", this.state.activeProduct.display_name);
        await this.orm.call("product.product", "unmark_as_favorite_pos", [[this.state.activeProduct.id]]);
        await this.loadFavoriteProducts(); // Refresh list
        this.state.activeProduct = null; // Clear selection if it's removed
        this.render(true); // Force re-render
    }

    toggleFavoriteDisplay() {
        this.state.showFavoritesOnly = !this.state.showFavoritesOnly;
        console.log("Show favorites only:", this.state.showFavoritesOnly);
        this.render(true);
    }

    get displayedProducts() {
        if (this.state.showFavoritesOnly) {
            return this.state.favoriteProducts;
        }
        return this.state.allProducts;
    }

    selectProduct(product) {
        this.state.activeProduct = product;
        console.log("Product selected:", product.display_name);
    }

    async loadFavoriteProducts() {
        const favoriteProductIds = await this.orm.call("product.product", "get_favorite_product_ids_pos", [[]]);
        const products = this.pos.db.get_products_by_ids(favoriteProductIds);
        this.state.favoriteProducts = products;
        console.log("Favorite products loaded:", this.state.favoriteProducts);
    }

    // Method to show a custom message popup
    async showMessagePopup() {
        const { confirmed, payload } = await this.popup.add("MessagePopup", {
            title: "Enter Your Message",
        });
        if (confirmed) {
            this.state.popupMessage = payload;
            console.log("Popup message confirmed:", payload);
        } else {
            console.log("Popup cancelled.");
        }
    }
}

// Register the new screen component
Registries.Component.add(FavoriteProductsScreen);
  • XML Template (favorite_products_screen.xml):
    Create this file in
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
    <t t-name="custom_pos_features.FavoriteProductsScreen" owl="1">
        <div class="o_pos_favorite_screen screen">
            <div class="screen-header">
                <h1>Favorite Products Screen</h1>
                <span>Date: <t t-esc="state.currentDate"/></span>
                <button class="button back" t-on-click="back">Back to Products</button>
            </div>
            <div class="screen-content">
                <div class="left-pane">
                    <div class="control-buttons">
                        <button class="button" t-on-click="toggleFavoriteDisplay">
                            <t t-if="state.showFavoritesOnly">Show All Products</t>
                            <t t-else="">Show Favorites Only</t>
                        </button>
                        <button class="button" t-on-click="showMessagePopup">Show Message Popup</button>
                    </div>
                    <div class="product-list">
                        <table>
                            <thead>
                                <tr><th>Product Name</th></tr>
                            </thead>
                            <tbody>
                                <t t-foreach="displayedProducts" t-as="product" t-key="product.id">
                                    <tr t-on-click="() => this.selectProduct(product)"
                                        t-att-class="{ 'selected': state.activeProduct and state.activeProduct.id === product.id }">
                                        <td><t t-esc="product.display_name"/></td>
                                    </tr>
                                </t>
                            </tbody>
                        </table>
                    </div>
                </div>
                <div class="right-pane">
                    <div t-if="state.activeProduct" class="product-details">
                        <h2><t t-esc="state.activeProduct.display_name"/></h2>
                        <p>Code: <t t-esc="state.activeProduct.default_code || 'N/A'"/></p>
                        <p>Price: <t t-esc="pos.format_currency(state.activeProduct.get_price(pos.config.pricelist, 1))"/></p>
                        <div class="action-buttons">
                            <button class="button green" t-on-click="onMarkAsFavorite">Mark as Favorite</button>
                            <button class="button orange" t-on-click="onUnmarkAsFavorite">Unmark as Favorite</button>
                        </div>
                    </div>
                    <div t-else="" class="no-product-selected">
                        <p>Select a product to see details.</p>
                    </div>
                    <div t-if="state.popupMessage" class="popup-message-display">
                        <strong>Message from Popup:</strong> <t t-esc="state.popupMessage"/>
                    </div>
                </div>
            </div>
        </div>
    </t>
</templates>
  • This template defines the structure for your new screen, showcasing how to inherit Odoo 18 PoS view elements.
  • (Optional) Styling with SCSS:
    Create custom_pos_features/static/src/scss/pos_styles.scss if you need custom styles.
// Example SCSS for FavoriteProductsScreen
.o_pos_favorite_screen {
    .screen-header {
        // styles
    }
    .screen-content {
        display: flex;
        height: calc(100% - 60px); // Adjust based on header height
        .left-pane {
            flex: 1;
            padding: 10px;
            overflow-y: auto;
            border-right: 1px solid #ccc;
        }
        .right-pane {
            flex: 2;
            padding: 10px;
            .product-details h2 { margin-top: 0; }
        }
    }
    // Add more specific styles
}
  • Remember to include this SCSS file in point_of_sale.assets in your manifest.

Step 3: Adding a Control Button to an Existing PoS Screen

Now, let’s add a button to the ProductScreen that navigates to our FavoriteProductsScreen. This demonstrates a key aspect of how to inherit Odoo 18 PoS and extend its functionality.

  • JavaScript Component for the Button (favorite_screen_button.js):
    Create in
/** @odoo-module **/

import { PosComponent } from "@point_of_sale/app/store/pos_store";
import { Registries } from "@point_of_sale/app/store/registries";
import { useService } from "@web/core/utils/hooks";

export class FavoriteScreenButton extends PosComponent {
    static template = "custom_pos_features.FavoriteScreenButton";

    setup() {
        super.setup();
        this.pos = useService("pos");
    }

    onClick() {
        // Action to navigate to our custom screen
        this.pos.showScreen("FavoriteProductsScreen");
        console.log("FavoriteScreenButton clicked, navigating to FavoriteProductsScreen.");
    }
}

// Register the button component
Registries.Component.add(FavoriteScreenButton);
  • XML Template for the Button (favorite_screen_button.xml):
    Create in custom_pos_features/static/src/xml/components/buttons/favorite_screen_button.
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
    <t t-name="custom_pos_features.FavoriteScreenButton" owl="1">
        <button class="control-button" t-on-click="onClick">
            <i class="fa fa-star"/> Favorites
        </button>
    </t>
</templates>
  • Adding the Button to ProductScreen’s Control Buttons:
    To add this button to an existing screen like ProductScreen, you typically need to extend the ProductScreen component itself or its control button area. The transcript mentions ControlButtonsMixin.js. In modern OWL PoS, you’d often patch the existing component or its template. For simplicity and based on common patterns for adding control buttons dynamically, we can reference how Odoo adds its own control buttons. A more direct approach is to ensure your component is registered and then potentially modify the ProductScreen or its associated control button component list if it’s configurable, or patch its template. A common way is to extend the target screen (e.g., ProductScreen) and use a getter to include your button component in the list of control buttons, or patch its controlButtonToShow method if available.
    Alternatively, if the ProductScreen renders buttons from a list of components (which is often the case), you can try to patch that list. For this example, let’s assume ProductScreen has a known area or iterates over a list of control button components. A more robust way to inherit Odoo 18 PoS for this would involve:
    • Identifying the target screen (e.g., ProductScreen).Patching its controlButtons props or similar mechanism.The transcript’s approach via _addControlButton with ControlButtonsMixin is older; OWL would use patching or extend a getter if ProductScreen is designed that way.
    Since a direct, simple way to inject control buttons into any screen’s header without patching the screen itself can be complex and version-dependent, we’ll simplify for this tutorial. The most straightforward (though less flexible for any screen) is to modify a known template. However, the goal is usually to add to the existing ProductScreen. The best practice for adding control buttons to PoS screens in OWL involves extending the ProductScreen and adding your button component to its controlButtons configuration or by patching its template.
// Example: custom_pos_features/static/src/js/screens/product_screen_inherit.js
/** @odoo-module **/

import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
import { FavoriteScreenButton } from "@custom_pos_features/js/components/buttons/favorite_screen_button"; // Adjust path
import { patch } from "@web/core/utils/patch";
import { Registries } from "@point_of_sale/app/store/registries";

patch(ProductScreen.prototype, {
    // This assumes ProductScreen has a way to define its control buttons.
    // The exact method might vary slightly based on Odoo 18's final PoS structure.
    // One common pattern is a getter for control buttons.
    get controlButtons() {
        let buttons = super.controlButtons || []; // Call original getter or initialize
        buttons = [...buttons, {
            component: FavoriteScreenButton,
            position: "replace", // or "before", "after" relative to another button name
            // name: "FavoriteScreenButton", // Optional: for positioning
        }];
        return buttons;
    }
});

// Ensure this file is loaded in your manifest

Step 4: Implementing a Custom Popup (e.g., MessagePopup)

Now, let’s create a draggable popup that can take user input.

  • JavaScript Component for the Popup (message_popup.js):
    Create in custom_pos_features/static/src/js/components/popups/message_popup.js.
/** @odoo-module **/

import { AbstractAwaitablePopup } from "@point_of_sale/app/popup/abstract_awaitable_popup";
import { Registries } from "@point_of_sale/app/store/registries";
import { useState, useRef, onMounted } from "@odoo/owl";

export class MessagePopup extends AbstractAwaitablePopup {
    static template = "custom_pos_features.MessagePopup";
    static defaultProps = {
        confirmText: "Confirm",
        cancelText: "Cancel",
        title: "Message",
        body: "",
    };

    setup() {
        super.setup();
        this.state = useState({ inputValue: this.props.body });
        this.inputRef = useRef("input"); // Reference to the input field in the template

        onMounted(() => { // Focus the input when the popup is mounted
            if (this.inputRef.el) {
                this.inputRef.el.focus();
            }
        });
        console.log("MessagePopup setup for inherit odoo 18 pos.");
    }

    // Called when the confirm button is clicked
    async confirm() {
        this.props.resolve({ confirmed: true, payload: this.state.inputValue });
        super.confirm(); // Closes the popup
    }

    // Called when the cancel button is clicked
    cancel() {
        this.props.resolve({ confirmed: false, payload: null });
        super.cancel(); // Closes the popup
    }

    // Returns the data to be passed when confirming
    getPayload() {
        return this.state.inputValue;
    }
}

// Register the popup component
Registries.Component.add(MessagePopup);
  • XML Template for the Popup (message_popup.xml):
    Create in custom_pos_features/static/src/xml/components/popups/message_popup.xml.

<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
    <t t-name="custom_pos_features.MessagePopup" owl="1">
        <div class="popup popup-textinput">
            <header class="title draggable">
                <t t-esc="props.title" />
            </header>
            <main class="body">
                <input type="text" t-model="state.inputValue" t-ref="input" class="popup-input"/>
            </main>
            <footer class="footer">
                <div class="button confirm highlight" t-on-click="confirm">
                    <t t-esc="props.confirmText" />
                </div>
                <div class="button cancel" t-on-click="cancel">
                    <t t-esc="props.cancelText" />
                </div>
            </footer>
        </div>
    </t>
</templates>
  • This creates a draggable popup with an input field, demonstrating how to inherit Odoo 18 PoS for interactive dialogs.

Step 5: Integrating Backend Logic (Example: Favorite Product Tagging)

To make the “Favorite” feature functional, you need a backend Python method.

  • Python Model Method (product.product):
    Create/extend a Python file in your models directory (e.g., models/product_product.py).
from odoo import models, fields, api

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

    # A helper field to store the "Favorite" tag ID if you create it via data
    # Or you can find it by name
    # favorite_tag_id = fields.Many2one('product.tag', compute='_compute_favorite_tag_id', store=False)

    # def _compute_favorite_tag_id(self):
    #     fav_tag = self.env.ref('your_module_name.product_tag_favorite', raise_if_not_found=False)
    #     for product in self:
    #         product.favorite_tag_id = fav_tag if fav_tag else False

    @api.model
    def get_favorite_product_ids_pos(self):
        """Returns IDs of products tagged as 'Favorite'."""
        fav_tag = self.env['product.tag'].search([('name', '=', 'Favorite')], limit=1)
        if not fav_tag:
            return []
        products = self.search([('pos_tag_ids', 'in', fav_tag.id)])
        return products.ids

    def mark_as_favorite_pos(self):
        """Marks products as favorite for PoS."""
        fav_tag = self.env['product.tag'].search([('name', '=', 'Favorite')], limit=1)
        if not fav_tag: # Create the tag if it doesn't exist
            fav_tag = self.env['product.tag'].create({'name': 'Favorite'})

        for product in self:
            if fav_tag.id not in product.pos_tag_ids.ids:
                product.pos_tag_ids = [(4, fav_tag.id)]
        return True

    def unmark_as_favorite_pos(self):
        """Unmarks products as favorite for PoS."""
        fav_tag = self.env['product.tag'].search([('name', '=', 'Favorite')], limit=1)
        if fav_tag:
            for product in self:
                if fav_tag.id in product.pos_tag_ids.ids:
                    product.pos_tag_ids = [(3, fav_tag.id)]
        return True

Deployment and Seeing Your Inherited Odoo 18 PoS in Action

After developing these components to inherit Odoo 18 PoS:

  1. Install/Upgrade Your Module: Go to Odoo Apps, search for custom_pos_features, and install/upgrade it.
  2. Clear Browser Cache & Restart PoS: Hard refresh your browser (Ctrl+Shift+R) and restart any active PoS sessions to load the new assets.
  3. Test:
    • Open the PoS ProductScreen. You should see your “Favorites” button.
    • Click it to navigate to your FavoriteProductsScreen.
    • Test the “Show Message Popup” button; it should open your custom popup.
    • Test marking/unmarking products as favorites and observe the list updates.

Conclusion: You Can Now Expertly Inherit Odoo 18 PoS!

By following these steps, you’ve learned the fundamentals of how to inherit Odoo 18 PoS using the OWL framework. You can now create custom screens, add functional buttons, implement interactive popups, and connect your PoS interface to backend logic for dynamic data handling. This capability is immensely powerful for tailoring Odoo PoS to precise business workflows and enhancing user productivity. For more advanced topics and detailed API references, always consult the official Odoo 18 Documentation and its Point of Sale section.


Discover more from teguhteja.id

Subscribe to get the latest posts sent to your email.

1 thought on “Inherit Odoo 18 PoS: 5 Steps to a Revolutionary Interface!”

  1. Pingback: Amazing 5 Tips to Odoo 19 One-Click Payment

Leave a Reply

WP Twitter Auto Publish Powered By : XYZScripts.com