Go to file
2026-06-04 14:55:14 +07:00
models refactor: restrict POS paid order history to the current day instead of including yesterday 2026-06-04 14:55:14 +07:00
static/src feat: add server-side guard to prevent accidental release of occupied restaurant tables in multi-device POS setups 2026-06-03 21:17:44 +07:00
views feat: add configurable setting to hide Cash In/Out button in POS navbar and update portrait mode layout styles 2026-06-03 08:42:58 +07:00
__init__.py feat: add configurable setting to hide Cash In/Out button in POS navbar and update portrait mode layout styles 2026-06-03 08:42:58 +07:00
__manifest__.py feat: add server-side guard to prevent accidental release of occupied restaurant tables in multi-device POS setups 2026-06-03 21:17:44 +07:00
.gitignore feat: implement mobile-responsive portrait mode layout for PoS screens with tab-based navigation 2026-05-26 08:43:33 +07:00
README.md feat: add server-side guard to prevent accidental release of occupied restaurant tables in multi-device POS setups 2026-06-03 21:17:44 +07:00

POS UI Optimization

Odoo 19 | Custom Addon

Optimizes the Point of Sale UI for resource-constrained devices and adds a switchable portrait display mode designed for 6-inch touchscreen terminals.


Features

1. Incremental Product Rendering

Prevents low-RAM devices from freezing when the product catalogue is large.

  • Only 40 products are rendered on initial load
  • More products load automatically as the user scrolls down
  • Resets the counter when the search word or category changes

2. Incremental Order Line Rendering

Same approach for the order/cart display.

  • Only 20 order lines rendered initially
  • Scrolling loads more lines progressively
  • Ensures the selected line is always visible (expands count if needed)

3. Hide Invoice Button

Hides the "Invoice" button on the payment stage (Payment Screen) and expands the Customer/Partner button to full width, preventing cashiers from using this button.

4. Portrait Mode (6-inch Display)

A dedicated compact layout for portrait-orientation small screens. Designed for devices like 6-inch Android POS terminals where the standard Odoo two-column layout clutters the screen.

Layout

┌──────────────────────────────────────┐
│  Navbar (compact)                    │
├──────────────────────────────────────┤
│                                      │
│  Active tab pane:                    │
│  Products / Cart / Numpad            │
│                                      │
├──────────────────────────────────────┤
│ TOTAL: Rp X,XX [Checker] [Send] [Pay]│  ← always visible
├──────────────────────────────────────┤
│  [Products]    [Cart]    [Numpad]    │  ← bottom tab bar
└──────────────────────────────────────┘

Key Differences from Standard Mode

Feature Standard Portrait
Layout Two-column (order left, products right) Single pane with bottom tabs
Categories Horizontal scrollable chips Compact hierarchical select dropdown
Product grid auto-fill minmax(115px) Fixed 2 columns
Product images Full aspect ratio Capped at 90px height
Numpad Shown inline in left pane Dedicated "Numpad" tab (shows only selected line)
Actions (Pay/Send/Checker) Inside left pane action pad Always-visible bottom strip
Navbar Full labels Compact with smaller padding

Category Dropdown

In portrait mode, the category selector is transformed into a compact dropdown list. It lists all active POS categories formatted hierarchically with indentation, making it extremely easy to filter products on small touch screens.

Selected Line Actions (Note & Delete)

The Numpad tab contains actions for modifying the selected orderline:

  • Note: Triggers the standard customer note input dialog.
  • Delete: Instantly removes the selected line from the cart.

Numpad Single Line Focus

To prevent overlaps on 5.8-inch screens:

  • Cart Tab: Shows only the order lines (full height). The numpad is hidden.
  • Numpad Tab: Hides all non-selected lines and hides the order summary block, showing only the selected line and the numpad/actions. If no item is selected, a friendly guide card is displayed.

Table Checker Button

The Checker button is available in the bottom pay strip in portrait mode. It allows waiters or cashiers to quickly print a basic table checker receipt.

The button is visible only when:

  1. The basic_receipt option is enabled in POS settings.
  2. The current cashier's role is either waiter or cashier.

The button is disabled when the current order is empty.

Auto-Activation

Portrait mode auto-activates when the browser viewport width is < 400 px on first load. A manual override is always available via the burger menu (☰ → Switch to Portrait/Standard Mode). The choice is saved to localStorage.

Send Button Behavior

The Send button appears only when:

  1. The POS is in restaurant mode (module_pos_restaurant = True)
  2. At least one Preparation Category is configured in POS → Kitchen Printers settings
  3. The current order has unsent changes (nbrOfChanges > 0)
  4. The order is not a direct sale / refund

This mirrors Odoo's standard restaurant swapButton logic exactly.


5. Safe Release Table Guard (Multi-Device)

Prevents an accidental order cancellation when multiple POS devices share the same login account in a restaurant setup.

The Problem

When Device X places orders on a table, Device Y sees the table turn purple (occupied). If Device Y opens that table before the order has fully synced locally, it may see an empty order and display the Release table button. Clicking Release on Device Y calls action_pos_order_cancel on the server, permanently cancelling the order created on Device X.

The Fix

Before executing the Release action, this feature performs a quick server-side check:

  1. An RPC call to pos.order.check_table_has_real_orders(table_id) is made.

  2. If the server reports real orders with non-zero lines exist on this table:

    • The release is blocked.
    • An alert dialog informs the user: "This table has N active order(s) placed from another device."
    • After the user acknowledges, deviceSync.readDataFromServer() is triggered so the real orders from Device X appear on Device Y.
    • The user is sent back to the Floor Screen with the correct table state.
  3. If the server confirms no real orders exist, the Release proceeds as normal.

Offline Fallback

If the RPC call fails (e.g. device is offline), the guard falls back to the original unbookTable() behaviour to avoid blocking legitimate releases.


Installation

  1. Copy the pos_ui_optimization folder into your customaddons directory
  2. Restart the Odoo server
  3. Go to Apps → search for POS UI Optimization → Install
  4. Reload your POS session

Upgrade

./odoo-bin -u pos_ui_optimization -d <your_database>

Compatibility

Item Value
Odoo version 19.0
Depends on point_of_sale
Compatible with pos_restaurant, pos_custom_access, pos_kitchen_printer
License LGPL-3

File Structure

pos_ui_optimization/
├── __manifest__.py
├── README.md
├── static/
│   └── src/
│       ├── app/
│       │   ├── components/
│       │   │   ├── category_selector/
│       │   │   │   ├── category_selector_patch.js # Hierarchical dropdown list
│       │   │   │   └── category_selector_patch.xml
│       │   │   ├── navbar/
│       │   │   │   ├── navbar_patch.js       # Display Mode toggle in burger menu
│       │   │   │   └── navbar_patch.xml
│       │   │   └── order_display/
│       │   │       ├── order_display_patch.js # Incremental order line rendering
│       │   │       └── order_display_patch.xml
│       │   ├── screens/
│       │   │   ├── payment_screen/
│       │   │   │   └── payment_screen_patch.xml # Hide Invoice button patch
│       │   │   └── product_screen/
│       │   │       ├── order_summary/
│       │   │       │   └── safe_release_patch.js # Multi-device Release guard
│       │   │       ├── portrait_mode_patch.js # Tab state (products/cart/numpad)
│       │   │       ├── portrait_screen.xml    # Portrait layout XML patch
│       │   │       ├── product_screen_patch.js # Incremental product rendering
│       │   │       └── product_screen_patch.xml
│       │   └── services/
│       │       └── portrait_mode.js           # Auto-detect + localStorage service
│       └── scss/
│           └── portrait.scss                  # All portrait CSS (scoped to .pos-portrait)

Author

Suherdy Yacob