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__.pyfile 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:
Createcustom_pos_features/static/src/scss/pos_styles.scssif 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.assetsin 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 incustom_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 likeProductScreen, you typically need to extend theProductScreencomponent itself or its control button area. The transcript mentionsControlButtonsMixin.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 theProductScreenor 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 itscontrolButtonToShowmethod if available.
Alternatively, if theProductScreenrenders buttons from a list of components (which is often the case), you can try to patch that list. For this example, let’s assumeProductScreenhas 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 itscontrolButtonsprops or similar mechanism.The transcript’s approach via_addControlButtonwithControlButtonsMixinis older; OWL would use patching or extend a getter ifProductScreenis designed that way.
ProductScreenand adding your button component to itscontrolButtonsconfiguration or by patching its template. - Identifying the target screen (e.g.,
// 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 incustom_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 incustom_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 yourmodelsdirectory (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:
- Install/Upgrade Your Module: Go to Odoo Apps, search for
custom_pos_features, and install/upgrade it. - Clear Browser Cache & Restart PoS: Hard refresh your browser (Ctrl+Shift+R) and restart any active PoS sessions to load the new assets.
- 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.
- Open the PoS
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.


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