Extending Point of Sale in ERPNext using POS Controller

We are adding some domain specific features to default POS in ERPNext. We were able to do it by extending the POS Controller Class implemented [here]. (erpnext/erpnext/selling/page/point_of_sale/pos_controller.js at develop · frappe/erpnext · GitHub). We want to extend the existing POS from a custom optional app.

Some common question you may have and quick answers to it:
Q1) Why not raise a PR in existing POS.
A) We are implementing domain specific features that only people of that domain will benefit from. Adding it for all only will add unnecessary complexity for even those who have zero value from it.

Q2) Why not create a new POS independent from default POS?
A) Recreating a POS for implementing some domain specific features made no sense to us. Especially as almost all POS features are required. Extending existing POS via a domain specific custom optional frappe app, seemed to be the most effective way forward.

What has been done so far?
We have been able to extend POS by initiating in another Frappe page. We initiated a POS using the similar code as it is implemented in the original POS page, using frappe.provide:
frappe.provide('erpnext.PointOfSale');

For this, we first created a new Frappe Page. Then we then initiated the Frappe Point of Sale class, after extending it. See the code below:

frappe.provide('erpnext.PointOfSale');

frappe.pages['rest-pos'].on_page_load = function (wrapper) {

	var page = frappe.ui.make_app_page({
		parent: wrapper,
		title: 'REST POS',
		single_column: true
	});

	frappe.require('point-of-sale.bundle.js', function () {

		erpnext.PointOfSale.Controller = class MyPosController extends erpnext.PointOfSale.Controller {
			constructor(wrapper) {
				super(wrapper);
			}

			prepare_menu() {
				this.page.clear_menu();
				this.page.add_menu_item(("Open Form View"), this.open_form_view.bind(this), false, 'Ctrl+F');
				this.page.add_menu_item(("Toggle Recent Orders"), this.toggle_recent_order.bind(this), false, 'Ctrl+O');
				this.page.add_menu_item(("Save as Draft"), this.save_draft_invoice.bind(this), false, 'Ctrl+S');	
				this.page.add_menu_item(('New Menu POS'), this.close_pos.bind(this), false, 'Shift+Ctrl+C');	
				this.page.add_menu_item(__('Close the POS'), this.close_pos.bind(this), false, 'Shift+Ctrl+C');
			}

		};

		wrapper.pos = new erpnext.PointOfSale.Controller(wrapper);
		window.cur_pos = wrapper.pos;
	});
};


You can see the original POS page code here.


See the screenshot of POS with new menu added below:


While the above solution works, we trying to figure out the following:

  1. The current approach works by extending the POS & doesn’t require change to core POS code. However we end up having the enhanced POS in another route. In this case at /rest-pos. Only way to override core POS via this method is by creating a redirect script.

  2. If a custom app can load a JS that extends the existing POS in ERPNext implemented by POS controller it will be great. Is there any way we can load a JS file that extends the POS before it is used by ERPNext?

4 Likes

Have you tried using page_js hook in hooks.py? ref.

This hook is called on page load. If you add point-of-sale entry in that hook, you can inject custom js when the point of sale page is loaded.

1 Like

Thank you for the suggestion. We where able to use the page_js hook, by adding the below to to hooks.py:

“page_js = {“point-of-sale”: “public/js/rest_pos.js” }”

The code in rest_pos.js was as below:

frappe.provide('erpnext.PointOfSale');
frappe.require('point-of-sale.bundle.js', function () {

    erpnext.PointOfSale.Controller = class MyPosController extends erpnext.PointOfSale.Controller {
        constructor(wrapper) {
            super(wrapper);
        }

        prepare_menu() {
            this.page.clear_menu();

            this.page.add_menu_item(("Open Form View"), this.open_form_view.bind(this), false, 'Ctrl+F');

            this.page.add_menu_item(("Toggle Recent Orders"), this.toggle_recent_order.bind(this), false, 'Ctrl+O');

            this.page.add_menu_item(("Save as Draft"), this.save_draft_invoice.bind(this), false, 'Ctrl+S');
            
            this.page.add_menu_item(('New REST POS'), this.close_pos.bind(this), false, 'Shift+Ctrl+C');
            
            this.page.add_menu_item(__('Close the POS'), this.close_pos.bind(this), false, 'Shift+Ctrl+C');
        }

    };


    wrapper.pos = new erpnext.PointOfSale.Controller(wrapper);
    window.cur_pos = wrapper.pos;
});

The method worked as expected. Our conclusion is as below:

  1. We can extend an existing Frappe page either by creating a new page build extending the existing one or by using the page_js hook.
  2. If the objective is to create a new functionality keeping the existing page intact, then extending the existing page in a new page is the way to go.
  3. If you want to extend and existing page then using ‘page_js’ hooks seems to be the best way forward.

Thank you for the support.

6 Likes