Triggers, Listeners and Functions on Sales Invoice and Sales Invoice Items

Here is my problem.

On my custom application, I have a JavaScript that contains one function and one trigger
like so:

Function

function calculate(){
frm.doc.items.forEach((item_row, index) => {
if (item_row.name == cdn) {
stock_qty_for_calculation = (item_row.qty * item_row.conversion_factor);
};
}

Trigger

frappe.ui.form.on(“Sales Invoice”, {
onload_post_render: function(frm, cdt, cdn){
// Code goes here
frm.fields_dict.items.grid.wrapper.on(‘blur’, ‘input[data-fieldname=“conversion_factor”][data-doctype=“Sales Invoice Item”]’, function(e) {
console.log(“Now blurring from the Item Code Field”);
setTimeout(function() { calculate(frm, cdt, cdn) }, 100);
});
frm.fields_dict.items.grid.wrapper.on(‘blur’, ‘input[data-fieldname=“qty”][data-doctype=“Sales Invoice Item”]’, function(e) {
console.log(“Now blurring from the Item Code Field”);
setTimeout(function() { calculate(frm, cdt, cdn) }, 100);
});
frm.fields_dict.items.grid.wrapper.on(‘blur’, ‘input[data-fieldname=“uom”][data-doctype=“Sales Invoice Item”]’, function(e) {
console.log(“Now blurring from the Item Code Field”);
setTimeout(function() { calculate(frm, cdt, cdn) }, 100);
});
}
});

These run on Sales Invoice and Sales Invoice Item Doctype. The user creates a new
sales invoice, adds a customer, dates and fills other fields. Then the user moves down to
the items child table and enters the first item. ERPNext will load a default quantity, rate
and estimate the amount.
*This should also apply for Sales Invoices which have been created from Delivery Notes or
Sales Orders or similar!

At this point the user has not clicked on the arrow control in the child table
that allows you to modify more information on the child table. Once clicked, a pop up
comes up with additional fields for modification.

The expected behavior is for the function to run when the user has just changed data in
the rate, uom or conversion factor field and is now moving onto another field. When the code runs it should calculate
what is the amount of stock units of measure based on the recently entered or loaded conversion factor
multiplied by the quantity of purchase uom entered.

The trigger executes the function precisely on cue (when blurring or leaving the field by
clicking elsewhere, or moving to the next field with TAB). Basically, my triggers and listener events are setup properly and run the code as expected.

The function runs just fine, and otherwise calculates appropriately. Its purpose it to estimate
the stock_qty of the item being entered in that line, so that I can estimate the “special”
or excise tax as the product of the excise tax amount multiplied by the stock quantity.

Now, whenever the user changes the Purchase Unit of Measure, or otherwise adjusts the quantity
purchased in terms of that Unit of Measure, the conversion factor field will automatically behave
as it should, by either:

  1. Loading a pre-defined conversion factor
  2. Remaining at zero, for the user to manually enter a conversion factor.

Upon changing the Purchase Unit of Measure, or Conversion Factor field the function is run,
but my calculation comes out with arithmetical errors. The first time it calculates correctly, but for subsequent times
I have found that there is an apparent “lag” in the variables, meaning that the results shown
in the console or target fields refer to the previous quantity, uom or conversion factor values, NOT to the most recently entered. Basically this means that it is using “old” or “stale” variables at some point.

So far I have figured out that somehow this code might be what I need to access:

cur_frm.fields_dict.items.wrapper.innerText

However I am confused and would prefer to access the DOM directly to modify the object values
Or should I get into parsing the actual HTML for the added pop up element, and in that parsing add the
resulting calculation?

Also, my calculations do not run properly upon simply just “adding an item”
When I add an item, with a default quantity of “1”, the calculations will not run. It is only when the quantity field is updated manually, that the calculations run appropriately. How can I circumvent this?

Thanks!

why you don’t use these custom scripts to handle your logic

cur_frm.cscript.uom = 
cur_frm.cscript.conversion_factor = 
cur_frm.cscript.qty=function(doc, cdt, cdn){
calculate(cur_frm, cdt, cdn)
}

this workaround will solve your issue please give a feedback

Hope that helps,

1 Like

Hey Alain, a few things:

  1. settimeout will give the appearance of lag, when you are actually telling it to sleep (on purpose), which sound like it is not your intent.
  2. Are you calling any field refreshes? …frm.refresh_field("items")
  3. I have had better luck with change than blur
  4. This is barking up the wrong tree, the stuff you want to manipulate is well above the HTML and available from Frappe helpers.

1.You were right. I removed the settimeout yesterday. I just used it for testing purposes. It speeds things up.
2. Your concern about field refreshes is also noted and you are right, I figured out the precise usage for these. I use them sparingly now and get the results I want through “JavaScript Sorcery”
3. I will try change.
4. Yes, I understand this is a bit above and beyond the basics offered by Frappé, and after trial and error (i.e. Hacking) I managed to get the results I wanted.

After a hack-a-thon yesterday I got 99.9% of the results I expected. The reason it is 99.9% is that if you change the conversion factor field (for which I have a listener not shown above, but similar) it will calculate erroneously, but once you press save or “click” elsewhere or TAB elsewhere, the calculation is precise. I’ll continue with these tips on to the .01% not solved yet.

Everything recalculates properly upon pressing the save button or commanding a save with a shortcut. I will be merging to my master branch of the app in these days, as soon as I place the comments in english, so it is easy to read and understand. :slight_smile:

I had opted for separate listeners to ensure calculations are done before refreshes, basically a workaround to run order of the JS.
Will try these though and let you know. Thanks!

2 Likes