Scripting For Gate Pass and Gate Inward System in ERPNext

Hi Everyone,
i have managed to create some scripts that have seamlessly worked for me regarding the Gate Pass and Gate Inward System. Here the following scripts that i have used for gate pass and inward system:

----- gate inward client script---------

frappe.ui.form.on(‘Gate Inward’, {
returnable_gate_pass: function(frm) {
if (frm.doc.returnable_gate_pass) {
frappe.call({
method: ‘frappe.client.get’,
args: {
doctype: ‘Gate Pass’,
name: frm.doc.returnable_gate_pass
},
callback: function(r) {
if (r.message) {
// Get existing item codes to avoid duplicates
let existing_items = frm.doc.gate_inward_item.map(item => item.item_code);

                    r.message.gate_pass_item.forEach(item => {
                        // Only add if item doesn't exist AND has remaining balance
                        if (!existing_items.includes(item.item_code) &&
                            (item.balance > 0 || item.balance_2 > 0)) {
                            let row = frm.add_child('gate_inward_item');
                            row.item_group = item.item_group;
                            row.item_code = item.item_code;
                            row.item_name = item.item_name;
                            row.quantity = item.quantity;
                            row.quantity_2 = item.quantity_2;
                            row.uom = item.uom;
                            row.balance = item.balance;
                            row.balance_2 = item.balance_2;
                            row.returned_quantity = 0;
                            row.returned_quantity_2 = 0;
                        }
                    });
                    frm.refresh_field('gate_inward_item');

                    // Show message if no items were added
                    if (!r.message.gate_pass_item.some(item =>
                        (item.balance > 0 || item.balance_2 > 0) &&
                        !existing_items.includes(item.item_code))) {
                        frappe.msgprint(__('No items with remaining balance found in the selected Gate Pass'));
                    }
                }
            }
        });
    }
}

});

frappe.ui.form.on(‘Gate Pass Item’, {
returned_quantity: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
// Only validate if this item is part of the gate pass
if (frm.doc.returnable_gate_pass && row.balance) {
if (row.returned_quantity > row.balance) {
row.returned_quantity = row.balance;
frappe.msgprint((‘Returned quantity cannot exceed balance quantity’));
frm.refresh_field(‘gate_inward_item’);
}
}
},
returned_quantity_2: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
// Only validate if this item is part of the gate pass
if (frm.doc.returnable_gate_pass && row.balance_2) {
if (row.returned_quantity_2 > row.balance_2) {
row.returned_quantity_2 = row.balance_2;
frappe.msgprint(
(‘Returned quantity 2 cannot exceed balance quantity 2’));
frm.refresh_field(‘gate_inward_item’);
}
}
}
});

-------------- gate pass returnable status ---------------

frappe.ui.form.on(‘Gate Pass’, {
is_returnable: function(frm) {
if (frm.doc.is_returnable) {
frm.doc.gate_pass_item.forEach(item => {
item.balance = item.quantity;
item.balance_2 = item.quantity_2;
item.returned_quantity = 0;
item.returned_quantity_2 = 0;
});
frm.refresh_field(‘gate_pass_item’);
}
}
});

frappe.ui.form.on(‘Gate Pass Item’, {
quantity: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
if (frm.doc.is_returnable) {
row.balance = row.quantity;
row.returned_quantity = 0;
frm.refresh_field(‘gate_pass_item’);
}
},
quantity_2: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
if (frm.doc.is_returnable) {
row.balance_2 = row.quantity_2;
row.returned_quantity_2 = 0;
frm.refresh_field(‘gate_pass_item’);
}
}
});

-------------- gate inward server script (After Save)------------

if doc.returnable_gate_pass:
gate_pass = frappe.get_doc(“Gate Pass”, doc.returnable_gate_pass)

# Get list of items in gate pass
gate_pass_items = {item.item_code: item for item in gate_pass.gate_pass_item}

# Update balances for each item that exists in gate pass
for inward_item in doc.gate_inward_item:
    if inward_item.item_code in gate_pass_items:
        gp_item = gate_pass_items[inward_item.item_code]

        if inward_item.returned_quantity > 0:
            gp_item.balance = gp_item.balance - inward_item.returned_quantity
            gp_item.returned_quantity = gp_item.returned_quantity + inward_item.returned_quantity

        if inward_item.returned_quantity_2 > 0:
            gp_item.balance_2 = gp_item.balance_2 - inward_item.returned_quantity_2
            gp_item.returned_quantity_2 = gp_item.returned_quantity_2 + inward_item.returned_quantity_2

# Check if ALL items in gate pass are fully returned
all_items_returned = True
for gp_item in gate_pass.gate_pass_item:
    if gp_item.balance > 0 or gp_item.balance_2 > 0:
        all_items_returned = False
        break

# Update Gate Pass status
current_date = str(frappe.utils.today())
if all_items_returned:
    gate_pass.custom_status = "Returned"
elif gate_pass.return_date and str(gate_pass.return_date) < current_date:
    gate_pass.custom_status = "Not Returned"
else:
    gate_pass.custom_status = "To Return"

gate_pass.save(ignore_permissions=True)

----------- gate inwarrd server script (Before Save)-----------

if doc.returnable_gate_pass:
gate_pass = frappe.get_doc(“Gate Pass”, doc.returnable_gate_pass)
gate_pass_items = {item.item_code: item for item in gate_pass.gate_pass_item}

# Validate returned quantities only for items that exist in gate pass
for inward_item in doc.gate_inward_item:
    if inward_item.item_code in gate_pass_items:
        gp_item = gate_pass_items[inward_item.item_code]

        if inward_item.returned_quantity > gp_item.balance:
            frappe.throw(
                f"Returned quantity ({inward_item.returned_quantity}) cannot exceed balance quantity ({gp_item.balance}) for item {inward_item.item_code}"
            )
        if inward_item.returned_quantity_2 > gp_item.balance_2:
            frappe.throw(
                f"Returned quantity 2 ({inward_item.returned_quantity_2}) cannot exceed balance quantity 2 ({gp_item.balance_2}) for item {inward_item.item_code}"
            )

------------ gate inward server script (Before Delete)----------------

if doc.returnable_gate_pass:
gate_pass = frappe.get_doc(“Gate Pass”, doc.returnable_gate_pass)

# Restore balances
for inward_item in doc.gate_inward_item:
    for gp_item in gate_pass.gate_pass_item:
        if (inward_item.item_code == gp_item.item_code):
            if inward_item.returned_quantity > 0:
                gp_item.balance = gp_item.balance + inward_item.returned_quantity
                gp_item.returned_quantity = gp_item.returned_quantity - inward_item.returned_quantity

            if inward_item.returned_quantity_2 > 0:
                gp_item.balance_2 = gp_item.balance_2 + inward_item.returned_quantity_2
                gp_item.returned_quantity_2 = gp_item.returned_quantity_2 - inward_item.returned_quantity_2

# Update status based on current balance
all_items_pending = all(item.balance == item.quantity and item.balance_2 == item.quantity_2
                      for item in gate_pass.gate_pass_item)
current_date = str(frappe.utils.today())

if all_items_pending:
    if gate_pass.return_date and str(gate_pass.return_date) < current_date:
        gate_pass.custom_status = "Not Returned"
    else:
        gate_pass.custom_status = "To Return"

gate_pass.save(ignore_permissions=True)

now i am trying to make a reconciliation system where the subcontracted items are marked as returnables and returns in either different form, quality or units.
One may argue that such features are not necessary, but where i come from, Gate Pass System has become a part of security measures taken to avoid any risks. and especially since it was a demand from one of our clients.

any further insight will be deeply appreciated.

Regards,
Muhammad Shouib Saeed