Zalino Logo white

Odoo POS 18 – How to load new fields In odoo Point of Sale 18, Give discount to employee

Odoo POS Dashboard Image with cart
Odoo POS 18, how to load custom field in pos & give discount to employee by using RPC call

This repository demonstrates advanced Odoo 18 Point of Sale (POS) customization techniques, covering the following features:

  • Demonstrates how to add and load new fields in Odoo POS 18.
  • Explains the process of using backend fields in the POS system.
  • Guides on adding fields to existing models in Odoo.
  • Covers the inheritance of templates to enhance existing views by adding fields.
  • Shows how to implement special discounts for employees based on a custom field in product records.
  • How Controllers Work, by adding a custom controller to validate employee

These enhancements will empower you to tailor the POS system according to unique business requirements. By the end of this version, you will gain in-depth knowledge to develop and manage custom POS functionalities seamlessly.

Stay tuned as we dive into practical examples, step-by-step tutorials, and tips for optimizing your POS workflows. Let’s get started!

Directory Structure of your custom app

your_app/
├── __init__.py
├── __manifest__.py
├── controllers/
│         └── __init__.py
│         └── validate_employee_discount.py
├── models/
│         └── __init__.py
│         └── hr_employee.py
│         └── product_tamplate.py
├── views/
│         └── hr_employee.xml
│         └── product_template.xml
├── static/
│         └── src/
│                 └── app/
│                         └── custom_button/
│                                 └── custom_button.xml
│                                 └── custom_button.js
│                         └── custom_popup/
│                                 └── text_input_popup.xml
│                                 └── text_input_popup.xml
└── README.md

Add The xml code inside validate_employee_discount.py
from odoo import http
from odoo.http import request, Response
from datetime import datetime, timedelta

class ValidateEmpDiscount(http.Controller):

@http.route('/pos/validate_emp_discount', type='json', auth='user', csrf=False, methods=["POST"])
def validate_emp_discount(self, cnic):
if not cnic.isdigit() or len(cnic) != 13:
return {"success": False, "message": "Invalid CNIC. Must be 13 digits."}

employee_id = request.env['hr.employee'].sudo().search([('cnic','=',cnic)])

if len(employee_id) > 0:
return {
"success": True,
"message": "Validation successful!",
}
else:
return {"success": False, "message": f"No Employee Found with given CNIC: {cnic}"}
Add The xml code inside custom_button.xml
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_custom.ControlButtons" t-inherit="point_of_sale.ControlButtons" t-inherit-mode="extension">

<xpath expr=”//button[@class=’btn btn-light btn-lg flex-shrink-0 ms-auto’]” position=”before”>
<button class=”btn btn-light btn-lg flex-shrink-0 ms-auto”
t-on-click=”() => this.onClickPopupSingleField()”>Custom Button</button>
</xpath>

<xpath expr=”//t[@t-if=’props.showRemainingButtons’]/div/OrderlineNoteButton” position=”after”>
<button class=”btn btn-secondary btn-lg py-5″ t-on-click=”() => this.onClickPopupSingleField()”>
<i class=”fa fa-pencil-square me-1″ role=”img” aria-label=”Custom Alert” title=”Custom Alert”/>Custom
Button
</button>
</xpath>

</t>
</templates>

Add the js code to handle click event inside custom_button.js
your_app>static>src>app>custom_button>custom_button.js:

/**@odoo-module **/
import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { _t } from "@web/core/l10n/translation";
import { ControlButtons } from "@point_of_sale/app/screens/product_screen/control_buttons/control_buttons";
import { patch } from "@web/core/utils/patch";
import { TextInputPopup } from "@pos_custom/app/custom_popup/text_input_popup";

import { rpc } from "@web/core/network/rpc";


patch(ControlButtons.prototype, {
    async onClickPopupSingleField() {
        this.dialog.add(TextInputPopup, {

            title: _t("To Apply Discount, Enter CNIC"),
            placeholder: _t("Enter CNIC"),
            getPayload: async (code) => {
                let cnic = code.trim();

                // Validation Logic
                if (cnic.length !== 13 || isNaN(cnic)) {
                    this.dialog.add(AlertDialog, {
                        title: _t("CNIC ERROR"),
                        body: _t("CNIC must be a 13-digit number without dashes"),
                    });
                    return;
                }

                const order = this.pos.get_order();
                const orderLines = order.get_orderlines();
                try {
                    const result = await rpc(
                        "/pos/validate_emp_discount",
                        { cnic },
                    );
                    if (result.success) {
                        orderLines.forEach(line => {
                            //line.set_unit_price(line.product_id.price_for_employee || line.product_id.lst_price); // Update the price
                            line.set_unit_price(line.product_id.price_for_employee || line.product_id.lst_price); // Update the price
                        });
//                        order.cnic = cnic;
                    } else {
                         this.dialog.add(AlertDialog, {
                            title: _t("ERROR"),
                            body: _t(result.message),
                        });
                        return;
                    }
                } catch (error) {
                    this.dialog.add(AlertDialog, {
                            title: _t("SYSTEM ERROR"),
                            body: _t("Currently, some services are unavailable. Please wait for a while and try again. If the issue persists for an extended period, please contact the administrator."),
                        });
                        return;
                }

            },
        });
    },
});

    
Add The xml code inside text_input_popup.xml
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="pos_custom.TextInputPopup">
<Dialog title="props.title">
<t t-foreach="props.buttons" t-as="button" t-key="button_index">
<button t-on-click="() => this.buttonClick(button)" type="button"
t-attf-class="btn btn-lg me-2 mb-2 toggle-button {{button.isSelected? 'btn-primary' : 'btn-secondary'}} lh-lg">
<t t-esc="button.label"/>
</button>
</t>
<textarea t-att-rows="props.rows" class="form-control form-control-lg mx-auto" type="text"
t-model="state.inputValue" t-ref="input" t-att-placeholder="props.placeholder"
t-on-keydown="onKeydown"/>
<t t-set-slot="footer">
<button class="btn btn-primary btn-lg lh-lg o-default-button" t-on-click="confirm">Apply</button>
<button class="btn btn-secondary btn-lg lh-lg o-default-button" t-on-click="close">Discard</button>
</t>
</Dialog>
</t>
</templates>
Add the js code to handle click event inside text_input_popup.js
your_app>static>src>app>custom_popup>text_input_popup.js:

import { Component, onMounted, useRef, useState } from "@odoo/owl";
import { Dialog } from "@web/core/dialog/dialog";

export class TextInputPopup extends Component {
    static template = "pos_custom.TextInputPopup";
    static components = { Dialog };
    static props = {
        title: String,
        buttons: { type: Array, optional: true },
        startingValue: { type: String, optional: true },
        placeholder: { type: String, optional: true },
        rows: { type: Number, optional: true },
        getPayload: Function,
        close: Function,
    };
    static defaultProps = {
        startingValue: "",
        placeholder: "",
        rows: 1,
        buttons: [],
    };

    setup() {
        this.state = useState({ inputValue: this.props.startingValue });
        this.inputRef = useRef("input");
        onMounted(this.onMounted);
    }
    onMounted() {
        this.inputRef.el.focus();
        this.inputRef.el.select();
    }
    confirm() {
        this.props.getPayload(this.state.inputValue);
        this.props.close();
    }

    close() {
        this.props.close();
    }

    buttonClick(button) {
        console.log('Button click');
        const lines = this.state.inputValue.split("\n").filter((line) => line !== "");
        if (lines.includes(button.label)) {
            this.state.inputValue = lines.filter((line) => line !== button.label).join("\n");
            button.isSelected = false;
        } else {
            this.state.inputValue = lines.join("\n");
            this.state.inputValue += (lines.length > 0 ? "\n" : "") + button.label;
            button.isSelected = true;
        }
    }

    onKeydown(ev) {
        if (this.props.rows === 1 && ev.key.toUpperCase() === "ENTER") {
            this.confirm();
        }
    }
}


    
Add the js code to handle click event inside hr_employee.py
your_app>models>hr_employee.py:

from odoo import api, fields, models, _
from odoo.exceptions import UserError

class HrEmployee(models.Model):
    _inherit = 'hr.employee'

    cnic = fields.Char('CNIC')

    
Add the js code to handle click event inside product_template.py
your_app>models>product_template.py:


from odoo import api, fields, models, _
from odoo.exceptions import UserError

class ProductTemplate(models.Model):
    _inherit = 'product.template'

    price_for_employee = fields.Float('Price For Employee')

    @api.model
    def _load_pos_data_fields(self, config_id):
        fields = super()._load_pos_data_fields(config_id)
        fields += ['price_for_employee']
        return fields



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

    @api.model
    def _load_pos_data_fields(self, config_id):
        fields = super()._load_pos_data_fields(config_id)
        fields += ['price_for_employee']
        return fields

    
Finally Add the xml file hr_employee.xml
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_hr_employee_form_inherit_pos_custom" model="ir.ui.view">
<field name="name">hr.employee.form.inherit.pos.custom</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">

<xpath expr="//notebook" position="inside">
<page string="Additional Info">
<group>
<group>
<field name="cnic"/>
</group>
</group>
</page>
</xpath>
</field>
</record>
</odoo>
Finally Add the xml file product_template.xml
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_product_template_form_inherit_pos_custom" model="ir.ui.view">
<field name="name">product.template.form.inherit.pos.custom</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_only_form_view"/>
<field name="arch" type="xml">

<xpath expr="//notebook" position="inside">
<page string="Additional Info">
<group>
<group>
<field name="price_for_employee"/>
</group>
</group>

</page>
</xpath>
</field>
</record>
</odoo>
Finally Add the reference in __manifest__.py
add the file reference in manifest file to load the assets in pos:

{
    'name': 'POS Custom',
    'version': '1.0.0',
    'category': 'Point of Sale',
    'summary': "POS Custom, Odoo18, "
               "Odoo Apps",
    'description': "Design for better styles",
    'author': 'Zahid Anwar',
    'company': 'zalino',
    'maintainer': 'Nil',
    'website': 'https://www.zalinotech.com',
    'depends': ['base', 'point_of_sale','hr'],
    'data': [

        'views/product_template.xml',
        'views/hr_employee.xml',

    ],
    'assets': {
        'point_of_sale._assets_pos': [
            'pos_custom/static/src/app/custom_popup/text_input_popup.js',
            'pos_custom/static/src/app/custom_popup/text_input_popup.xml',

            'pos_custom/static/src/app/custom_button/custom_button.js',
            'pos_custom/static/src/app/custom_button/custom_button.xml',
        ],
    },
    'license': 'AGPL-3',
    'installable': True,
    'auto_install': False,
    'application': False
}


    
Restart the service
Once all is done. Now restart the odoo service and upgrade the app. (if assets not loaded properly clear the cache of browser)

That’s it! You’ve added the custom buttons in point of sale!

🔗 Useful Links:
👉 Visit Our Website: https://zalinotech.com
👉 GitHub: https://github.com/ZalinoTech/OdooPos18/tree/Version05

Leave a Reply

Your email address will not be published. Required fields are marked *