How can we override ERPNext core functions?

In my current project we need to modify the default calculation of ‘Purchase Order Item’ table, which is a Child table of ‘Purchase Order’. By default each ‘Amount’ field of the purchase order item is calculated by multiplying the ‘Quantity’ field and ‘Rate’ field of that row. Here is the default calculation -
Amount = Quantity * Rate

But in our project we had to introduce some custom fields, like Kg Per Drum, Conversion Rate, Utility etc. And we need all of them to be involved on the final amount calculation for every item row of the table. This is our Purchase Order Item Table. I am giving the Expected Calculations Below -

I already tried to alter the calculation with client script and custom .py file which is adding in the hooks.py files doc_events section -

("Purchase Order"): {
		"validate": ["erpnext.buying.doctype.purchase_order.custom_hooks.items_table_calculation"]
	},

The custom_hooks files code is -

from __future__ import unicode_literals
from pydoc import doc
import frappe


def items_table_calculation(doc, action):
	final_amount = 0
	for row in doc.items:
		row.total_weight = row.qty * row.kg_per_drum
		row.inmon = row.total_weight / row.conversion
		row.amount = (row.inmon * row.rate) + row.utility
		final_amount += row.amount

The problem is, after saving, child tables calculation shows ok but in the Total and Grand Total show qty*rate calculations result. How can I show my customized calculated amount in these fields?

And I figured it out that this calculation is handled by the taxes and totals.py and taxes and totals.js file. But if we change the taxes and totals.py file, the change doesn’t show immediately. And we really don’t want to change the core files here, so we need to know how we can override the functions here?

Hi there,

There aren’t hooks to override arbitrary python, so you’d have to find where in the Purchase Order controller those methods from taxes_and_totals.py are getting called. It’ll get messy, as there’s a fair bit of inheritance going on, but it’s doable.

May be this is what you are looking for override_doctype_class

1 Like

@mujeerhashmi, I have tried. Can you please show me how can I change Purchase Order Item child table table’s value customize from override_doctype_class?

How can I override functions from these files?

taxes_and _totals.js
taxes_and _totals.py

If anyone already done override functions or class to change amount calculations, please share.

1 Like

Like I said in my last post, there’s no way to override arbitrary functions. Your options are:

  1. Edit the source code of ERPNext directly, which is straightforward but requires you to manually merge changes on every version update.
  2. Use the override_doctype_class hook to create a custom controller for Purchase Orders, which you can load from a custom app. This has the advantage of not getting overwritten on every update. The disadvantage is that you won’t be able to edit taxes_and_totals.py directly, but you can find the places where purchase_order.py calls taxes_and_totals.py and replace those calls with calls to other methods.

It’s a bit simpler for the js if you’re willing to do some monkey patching. You just need to load a custom javascript file that overwrites the erpnext.taxes_and_totals variable defined here:

6 Likes

If I use custom javascript, is it effects after save in database?

FYI, you can also use monkey patching with Python. I needed to override erpnext.controllers.taxes_and_totals.calculate_taxes_and_totals. Here is what I did:

  1. Create a new module called my_app.my_module and a file inside it called override_calculate_taxes_and_totals.py with a new class like this:
from erpnext.controllers import taxes_and_totals

class my_app_calculate_taxes_and_totals(
        taxes_and_totals.calculate_taxes_and_totals
):
    """Override for calculating taxes and totals in MyApp."""
    <your changes here>

taxes_and_totals.calculate_taxes_and_totals = my_app_calculate_taxes_and_totals

Note that I import the module, not the class, and reference the class as taxes_and_totals.calculate_taxes_and_totals.

The last line changes the reference for taxes_and_totals.calculate_taxes_and_totals to my_app_calculate_taxes_and_totals so any other imports in standard ERPNext code get my class instead of the original class.

  1. Make sure your code runs before any other imports:

This only works if the code above runs before the standard code imports taxes_and_totals.calculate_taxes_and_totals so you can add the following line to hooks.py:

extend_bootinfo = [
	"my_app.startup.boot.boot_session",
]

and create a module called my_app.startup with a file called boot.py as so:

# This is the important line
from my_app.my_module import override_calculate_taxes_and_totals


def boot_session(bootinfo):
    """boot session."""

We don’t need the boot_session function to do anything. It is the import at the top that matters. Because this code runs every time a user logs in, the patch is set and the modified code will run. Of course, because I am inheriting from the original class, the original function is available as a super() reference.

3 Likes

This is interesting. Because monkey have some know problem.

  1. If declared in an app’s init.py, it is loaded and used even the app is not installed.
  2. If declared in hooks.py, it is loaded only if the app is installed, but only works in dev environment, and not in production.

Do you think by indirectly use extend_bootinfo, is this the correct way of using monkey patch?

Thanks!

Tested it. I think this is the best way for Monkey Patch so far.