Zahid Anwar
December 9, 2024
In this version, we will go a step further and learn about:
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!
your_app/
├── __init__.py
├── __manifest__.py
├── controllers/
│ └── __init__.py
│ └── validate_employee_discount.py
├── static/
│ └── src/
│ └── app/
│ └── custom_button/
│ └── custom_button.xml
│ └── custom_button.js
│ └── custom_popup/
│ └── text_input_popup.xml
│ └── text_input_popup.xml
└── README.md
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."}
if cnic == '1234567891234':
return {
"success": True,
"message": "Validation successful!",
}
else:
return {"success": False, "message": f"No Employee Found with given CNIC: {cnic}"}
<?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>
/**@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.lst_price - (line.product_id.lst_price* 5/100)); // 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;
}
},
});
},
});
<?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>
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();
}
}
}
{
'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'],
'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
}
That’s it! You’ve added the custom buttons in point of sale!