Script to list related documents of the same doctype

I need a client script for frappe that does the following:

There is a doctype called “Disciplinary Action” which has two child tables, “Disciplinary History” and “Disciplinary Charges”.

The “Disciplinary Action” doctype has fields called “accused”, “outcome_date” and “outcome”.

The Child Table called “Disciplinary Charges” has the following two fields: “code_item” and “charge” and is a Table Fild in the “Disciplinary Action” DocType called “final_charges”.

The Child Table called “Disciplinary History” has the following four fields: “disc_action”, “date”, “charges” and “sanction” and is a table field in the “Disciplinary Action” DocType called “previous_disciplinary_outcomes”.

Once the field “accused” in the “Disciplinary Action” DocType has been completed, I want to populate the “Disciplinary History” Child Table in the “Disciplinary Action” DocType with the following data:
“disc_action” should refer to all other documents of the “Disciplinary Action” doctype that has the same “accused”.
“date” should get the “outcome_date” field for each of those documents.
“charges” should list the “charge” fields from the “Disciplinary Charge” Child Table from each of those documents.
“sanction” should list the “outcome” field from each of those documents.

I have written a client script that should do this:

frappe.ui.form.on('Disciplinary Action', {
    accused: function(frm) {
        // Clear the Disciplinary History table first
        frm.clear_table('previous_disciplinary_outcomes');

        // Check if accused field is filled
        if (frm.doc.accused) {
            // Fetch all Disciplinary Action records with the same accused
            frappe.call({
                method: 'frappe.client.get_list',
                args: {
                    doctype: 'Disciplinary Action',
                    filters: {
                        accused: frm.doc.accused,
                        name: ['!=', frm.doc.name] // Exclude the current document
                    },
                    fields: ['name', 'outcome_date', 'outcome']
                },
                callback: function(response) {
                    const disciplinary_actions = response.message;

                    if (disciplinary_actions) {
                        // Create a promise to handle asynchronous calls
                        const promises = disciplinary_actions.map(action => {
                            return new Promise((resolve, reject) => {
                                // Fetch the charges from Disciplinary Charges child table
                                frappe.call({
                                    method: 'frappe.client.get_list',
                                    args: {
                                        doctype: 'Disciplinary Charges',
                                        filters: {
                                            parent: action.name
                                        },
                                        fields: ['charge']
                                    },
                                    callback: function(charges_response) {
                                        const charges = charges_response.message.map(charge => charge.charge).join(', ');

                                        // Add a new row to the Disciplinary History table
                                        let new_row = frm.add_child('previous_disciplinary_outcomes');
                                        new_row.disc_action = action.name || '';
                                        new_row.date = action.outcome_date || '';
                                        new_row.charges = charges || '';
                                        new_row.sanction = action.outcome || '';

                                        resolve();
                                    },
                                    error: function(err) {
                                        console.error("Error fetching disciplinary charges: ", err);
                                        frappe.msgprint({
                                            title: __('Error'),
                                            indicator: 'red',
                                            message: __('Error fetching disciplinary charges: ') + err.message
                                        });
                                        reject(err);
                                    }
                                });
                            });
                        });

                        // Wait for all promises to complete
                        Promise.all(promises).then(() => {
                            // Refresh the table once all rows are added
                            frm.refresh_field('previous_disciplinary_outcomes');
                        }).catch((err) => {
                            console.error("Error processing disciplinary actions: ", err);
                        });
                    }
                },
                error: function(err) {
                    console.error("Error fetching disciplinary actions: ", err);
                    frappe.msgprint({
                        title: __('Error'),
                        indicator: 'red',
                        message: __('Error fetching disciplinary actions: ') + err.message
                    });
                }
            });
        }
    }
});

When I try and use this DocType, I get permission errors, however I am admin on a development instance and have full privileges, that means somewhere in my script I am exceeding the permissions of Frappe, I need to understand where I am going wrong here.

Disciplinary charges is child tabel so use for that get_all function when you are using frappe.get_ list permission is working for get_all permission is not working.for child table always use get_all

1 Like

So if I understand you correclty, changing frappe.client.get_list to frappe.get_all should resolve the calls from the Disciplinary Charges table. However, with that change, I am running into new permission issues.

You are not permitted to access this resource.Function frappe.get_all is not whitelisted

disciplinary_actions.forEach(action => {
                    // Fetch the charges for each Disciplinary Action
                    frappe.call({
                        method: 'frappe.get_all',
                        args: {
                            doctype: 'Disciplinary Charges',
                            filters: {
                                parent: action.name
                            },
                            fields: ['charge']
                        },
                        callback: function(charge_response) {
                            let charges = charge_response.message.map(charge => charge.charge).join(', ');

                            // Add entry to the Disciplinary History table
                            let entry = frm.add_child('previous_disciplinary_outcomes');
                            entry.disc_action = action.name;
                            entry.date = action.outcome_date;
                            entry.charges = charges;
                            entry.sanction = action.outcome;

                            frm.refresh_field('previous_disciplinary_outcomes');
                        }
                    });
                });
1 Like

Don’t create complexity, please use the server script and solve it easily without a long code.

I solved this by using the frappe.model.with_doc function:

if (frm.doc.accused) {
            frappe.call({
                method: 'frappe.client.get_list',
                args: {
                    doctype: 'Disciplinary Action',
                    filters: {
                        accused: frm.doc.accused,
                        outcome: ['!=',''], //Exclude documents without outcomes
                        name: ['!=', frm.doc.name]  // Exclude the current document
                    },
                    fields: ['name', 'outcome_date', 'outcome']
                },
                callback: function(e) {
                    if (e.message) {
                        // Clear existing child table entries
                        frm.clear_table('previous_disciplinary_outcomes');
                        let action_count = e.message.length;
                        let completed_actions = 0;
                        e.message.forEach(function(row) {
                            frappe.model.with_doc('Disciplinary Action', row.name, function() {
                                let action_doc = frappe.get_doc('Disciplinary Action', row.name);
                                let charges = [];
                                action_doc.final_charges.forEach(function(charge_row) {
                                    charges.push(`(${charge_row.code_item}) ${charge_row.charge}`);
                                });
                                // Add the row to previous_disciplinary_outcomes
                                let child = frm.add_child('previous_disciplinary_outcomes');
                                child.disc_action = action_doc.name;
                                child.date = action_doc.outcome_date;
                                child.sanction = action_doc.outcome;
                                child.charges = charges.join('\n');  // Each charge on a new line
                                completed_actions++;
                                // Refresh the form to show the updated child table when all calls are completed
                                if (completed_actions === action_count) {
                                    frm.refresh_field('previous_disciplinary_outcomes');
                                }
                            });
                        });
                    }
                }
            });
        }