I want to write server side and client side code on the ERPNext UI that can allow me to achieve the following scenario. In the Stock Entry doctype, I have added 4 new fields in the Stock Entry Detail which are custom_day_in, custom_day_out, custom_no_of_storage_days and custom_use_last_day. The custom_day_in is only accessible during a Stock Entry Type of ‘Material Receipt’ and it marks the day an item was received into the warehouse. The other 3 are accessible during a Stock Entry Type of ‘Material Issue’ and they are meant to mark the day an item was taken out of the warehouse(custom_day_out), the number of days the item has been in the warehouse(custom_no_of_storage_days) and the last is a check field(custom_use_last_day). Each stock entry detail item is also attached a batch_no. A batch_no can also be used multiple times. Based off of this, I’ve defined the below code that will allow me to calculate and set the custom_no_of_storage_days which will be the difference between either the first custom_day_in and the custom_day_out or the last custom_day_in and the custom_day_out which are of the same batch_no and use the use_last_day check to decide whether to use the first option or the second one.
Client-side:
frappe.ui.form.on(‘Stock Entry’, {
refresh: function(frm) {
// Refresh the field after setting it
frm.fields_dict.items.grid.refresh();
}
});
// Listen for changes in the child table (Stock Entry Detail)
frappe.ui.form.on(‘Stock Entry Detail’, {
custom_day_out: function(frm, cdt, cdn) {
calculate_storage_days(frm, cdt, cdn);
},
custom_use_last_day: function(frm, cdt, cdn) {
calculate_storage_days(frm, cdt, cdn);
}
});
// Function to calculate the storage days
function calculate_storage_days(frm, cdt, cdn) {
let row = locals[cdt][cdn]; // Get the specific row in the child table
if (frm.doc.stock_entry_type === 'Material Issue' && row.batch_no && row.custom_day_out) {
// Call server method to fetch the first or last custom_day_in
frappe.call({
method: 'calculate_no_of_storage_days', // This is the API Method defined in the Server Script
args: {
batch_no: row.batch_no,
use_last_day: row.custom_use_last_day,
day_out: row.custom_day_out
},
callback: function(r) {
console.log("Server Response:", r); // Debug: log the full response
if (r.message) {
row.custom_no_of_storage_days = r.message;
frm.refresh_field('items');
} else {
console.log("No message received from server");
}
}
});
}
}
Server-side:
def calculate_no_of_storage_days(batch_no, use_last_day, day_out):
frappe.log_error(“Batch No: {} | Use Last Day: {} | Day Out: {}”.format(batch_no, use_last_day, day_out), “Calculate Storage Days Debug”)
stock_entries = frappe.db.sql("""
SELECT sed.custom_day_in
FROM `tabStock Entry Detail` sed
JOIN `tabStock Entry` se ON sed.parent = se.name
WHERE sed.batch_no = %s
AND sed.custom_day_in IS NOT NULL
ORDER BY sed.custom_day_in {0}
LIMIT 1
""".format('ASC' if not use_last_day else 'DESC'), (batch_no), as_dict=True)
if stock_entries:
day_in = stock_entries[0].get('custom_day_in')
if day_in and day_out:
# Calculate the number of storage days
no_of_storage_days = (getdate(day_out) - getdate(day_in)).days
frappe.log_error("Day In: {} | Day Out: {} | Storage Days: {}".format(day_in, day_out, no_of_storage_days), "Calculate Storage Days Success")
return no_of_storage_days
else:
frappe.log_error("Missing Day In or Day Out", "Calculate Storage Days Error")
else:
frappe.log_error("No Stock Entries Found for Batch No: {}".format(batch_no), "Calculate Storage Days Error")
return 0 # Return 0 if no matching entry is found
No message Received from server is logged on the console but no messages are being received on the error logs. On the network tab on the Developer tools, I see the method is being called and the payload has the correct values.