Issue Summary
Getting “Not permitted” error when trying to access custom “Expense Report” DocType, despite having Administrator/System Manager roles assigned.
Environment Details
-
Frappe Version: [frappe: 15.91.2]
-
ERPNext Version: [v15.91.2]
-
Server: [Local]
Problem Description
I created a DocType called “Expense Report” with a client script for generating vehicle expense reports. When trying to access the form, I get a permission error:
Error Message:
Not permitted
You do not have enough permissions to access this resource.
Please contact your manager to get access.
Steps to Reproduce
-
Navigate to:
/app/expense-report/new-expense-report-[docname] -
Form loads but immediately shows permission error dialog
-
Background shows “Loading…” spinner
-
User has Administrator role assigned
Current Permission Settings
Checked Role Permissions Manager for “Expense Report”:
-
Administrator role has: Select, Read, Write, Create, Delete, Print, Email, Report, Export, Share, Import -
System Manager role has same permissions -
Level: 0
-
“Only If Creator” is unchecked
Code Implementation
Client Script: expense_report.js
frappe.ui.form.on('Expense Report', {
refresh: function(frm) {
// Hide report initially
frm.fields_dict.report_html.$wrapper.hide();
// Clear default form buttons
frm.disable_save();
// Add custom buttons
frm.page.set_primary_action(__('Search'), function() {
search_expenses(frm);
});
frm.page.add_menu_item(__('Download'), function() {
download_report(frm);
});
frm.page.add_menu_item(__('Preview'), function() {
preview_report(frm);
});
// Set query for expense type
frm.set_query('expense_type', function() {
return {
filters: {
'group': 'Expense'
}
};
});
},
from_date: function(frm) {
validate_dates(frm);
},
to_date: function(frm) {
validate_dates(frm);
}
});
function validate_dates(frm) {
if (frm.doc.from_date && frm.doc.to_date) {
if (frm.doc.from_date > frm.doc.to_date) {
frappe.msgprint(__('From Date cannot be greater than To Date'));
frm.set_value('to_date', '');
}
}
}
function search_expenses(frm) {
// Validate required fields
if (!frm.doc.from_date || !frm.doc.to_date || !frm.doc.vehicle_no || !frm.doc.expense_type) {
frappe.msgprint({
title: __('Required Fields Missing'),
message: __('Please fill all required fields'),
indicator: 'red'
});
return;
}
// Show loading
frm.fields_dict.report_html.$wrapper.html('<div class="text-center" style="padding: 50px;"><i class="fa fa-spinner fa-spin fa-2x"></i><br><br>Loading...</div>');
frm.fields_dict.report_html.$wrapper.show();
// Fetch data
frappe.call({
method: 'frappe.client.get_list',
args: {
doctype: 'Vehicle Expense',
fields: [
'name',
'voucher_date',
'voucher_no',
'vehicle_no',
'driver',
'km_start',
'km_end',
'km_total',
'kpl'
],
filters: [
['voucher_date', 'between', [frm.doc.from_date, frm.doc.to_date]],
['vehicle_no', '=', frm.doc.vehicle_no],
['docstatus', '<', 2]
],
order_by: 'voucher_date desc',
limit_page_length: 0
},
callback: function(r) {
if (r.message && r.message.length > 0) {
fetch_expense_items(frm, r.message);
} else {
frm.fields_dict.report_html.$wrapper.html('<div class="text-center text-muted" style="padding: 50px;">No records found</div>');
}
}
});
}
function fetch_expense_items(frm, expenses) {
let expense_names = expenses.map(e => e.name);
frappe.call({
method: 'frappe.client.get_list',
args: {
doctype: 'Vehicle Expense Item',
fields: ['parent', 'vehicle_expense_type', 'qty', 'amount'],
filters: [
['parent', 'in', expense_names],
['vehicle_expense_type', '=', frm.doc.expense_type]
],
limit_page_length: 0
},
callback: function(r) {
if (r.message && r.message.length > 0) {
let merged_data = merge_data(expenses, r.message);
display_report(frm, merged_data);
} else {
frm.fields_dict.report_html.$wrapper.html('<div class="text-center text-muted" style="padding: 50px;">No matching expense items found</div>');
}
}
});
}
function merge_data(expenses, items) {
let result = [];
expenses.forEach(expense => {
let matching_items = items.filter(item => item.parent === expense.name);
matching_items.forEach(item => {
result.push({
voucher_date: expense.voucher_date,
voucher_no: expense.voucher_no,
vehicle_no: expense.vehicle_no,
driver: expense.driver || '',
km_start: expense.km_start || 0,
km_end: expense.km_end || 0,
km_total: expense.km_total || 0,
kpl: expense.kpl || 0,
qty: item.qty || 0,
amount: item.amount || 0,
name: expense.name
});
});
});
return result;
}
function display_report(frm, data) {
let html = `
<style>
.expense-report-container {
padding: 0;
margin: 0;
}
.expense-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
background: white;
}
.expense-table thead tr {
background-color: #f8f9fa;
}
.expense-table th {
border: 1px solid #d1d8dd;
padding: 8px 10px;
text-align: left;
font-weight: 600;
color: #36414c;
}
.expense-table td {
border: 1px solid #d1d8dd;
padding: 8px 10px;
color: #36414c;
}
.expense-table tbody tr:hover {
background-color: #f8f9fa;
}
.text-right {
text-align: right !important;
}
.text-center {
text-align: center !important;
}
.voucher-link {
color: #2490ef;
text-decoration: none;
}
.voucher-link:hover {
text-decoration: underline;
}
.report-footer {
padding: 10px;
color: #6c757d;
font-size: 12px;
}
</style>
<div class="expense-report-container">
<table class="expense-table">
<thead>
<tr>
<th class="text-center">No</th>
<th>Date</th>
<th>Voucher No</th>
<th>Vehicle No</th>
<th>Driver</th>
<th class="text-right">KM-Start</th>
<th class="text-right">KM-End</th>
<th class="text-right">KM-Total</th>
<th class="text-right">KPL</th>
<th class="text-right">Qty / Ltr</th>
<th class="text-right">Amount</th>
</tr>
</thead>
<tbody>
`;
data.forEach((row, index) => {
html += `
<tr>
<td class="text-center">${index + 1}</td>
<td>${frappe.datetime.str_to_user(row.voucher_date)}</td>
<td>
<a href="/app/vehicle-expense/${row.name}" class="voucher-link" target="_blank">
${row.voucher_no}
</a>
</td>
<td>${row.vehicle_no}</td>
<td>${row.driver || '-'}</td>
<td class="text-right">${row.km_start}</td>
<td class="text-right">${row.km_end}</td>
<td class="text-right">${row.km_total}</td>
<td class="text-right">${format_float(row.kpl)}</td>
<td class="text-right">${format_float(row.qty)}</td>
<td class="text-right">${format_currency(row.amount)}</td>
</tr>
`;
});
html += `
</tbody>
</table>
<div class="report-footer">
Showing 1 to ${data.length} of ${data.length} entries
</div>
</div>
`;
frm.fields_dict.report_html.$wrapper.html(html);
frm.fields_dict.report_html.$wrapper.show();
// Store data for download/preview
frm._report_data = data;
}
function format_float(value) {
return parseFloat(value || 0).toFixed(2);
}
function format_currency(value) {
return parseFloat(value || 0).toLocaleString('en-IN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
function download_report(frm) {
if (!frm._report_data || frm._report_data.length === 0) {
frappe.msgprint(__('Please search for data first'));
return;
}
let csv = 'No,Date,Voucher No,Vehicle No,Driver,KM-Start,KM-End,KM-Total,KPL,Qty/Ltr,Amount\n';
frm._report_data.forEach((row, index) => {
csv += `${index + 1},`;
csv += `${frappe.datetime.str_to_user(row.voucher_date)},`;
csv += `${row.voucher_no},`;
csv += `${row.vehicle_no},`;
csv += `${row.driver || ''},`;
csv += `${row.km_start},`;
csv += `${row.km_end},`;
csv += `${row.km_total},`;
csv += `${row.kpl},`;
csv += `${row.qty},`;
csv += `${row.amount}\n`;
});
// Create and download file
let blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
let link = document.createElement('a');
let url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `expense_report_${frappe.datetime.now_date()}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
frappe.show_alert({
message: __('Report downloaded successfully'),
indicator: 'green'
}, 3);
}
function preview_report(frm) {
if (!frm._report_data || frm._report_data.length === 0) {
frappe.msgprint(__('Please search for data first'));
return;
}
let print_html = generate_print_html(frm);
let preview_window = window.open('', '_blank', 'width=1200,height=800');
preview_window.document.write(print_html);
preview_window.document.close();
}
function generate_print_html(frm) {
let html = `
<!DOCTYPE html>
<html>
<head>
<title>Expense Report - Preview</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background: white;
}
.header {
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #333;
padding-bottom: 15px;
}
.header h2 {
margin: 0;
color: #333;
}
.filters {
margin-bottom: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 5px;
}
.filters p {
margin: 5px 0;
font-size: 13px;
}
.filters strong {
display: inline-block;
width: 120px;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
}
thead tr {
background-color: #f8f9fa;
}
th, td {
border: 1px solid #d1d8dd;
padding: 8px;
text-align: left;
}
th {
font-weight: 600;
color: #36414c;
}
.text-right {
text-align: right;
}
.text-center {
text-align: center;
}
.footer {
margin-top: 20px;
text-align: center;
font-size: 11px;
color: #666;
}
.no-print {
margin-bottom: 20px;
}
.no-print button {
padding: 10px 20px;
font-size: 14px;
margin-right: 10px;
cursor: pointer;
border: 1px solid #ccc;
background: white;
border-radius: 4px;
}
.no-print button:hover {
background: #f8f9fa;
}
@media print {
.no-print { display: none; }
body { margin: 0; }
}
</style>
</head>
<body>
<div class="no-print">
<button onclick="window.print()">🖨️ Print</button>
<button onclick="window.close()">✖️ Close</button>
</div>
<div class="header">
<h2>EXPENSE REPORT</h2>
</div>
<div class="filters">
<p><strong>Period:</strong> ${frappe.datetime.str_to_user(frm.doc.from_date)} to ${frappe.datetime.str_to_user(frm.doc.to_date)}</p>
<p><strong>Vehicle:</strong> ${frm.doc.vehicle_no}</p>
<p><strong>Expense Type:</strong> ${frm.doc.expense_type}</p>
<p><strong>Generated On:</strong> ${frappe.datetime.now_datetime()}</p>
</div>
<table>
<thead>
<tr>
<th class="text-center">No</th>
<th>Date</th>
<th>Voucher No</th>
<th>Vehicle No</th>
<th>Driver</th>
<th class="text-right">KM-Start</th>
<th class="text-right">KM-End</th>
<th class="text-right">KM-Total</th>
<th class="text-right">KPL</th>
<th class="text-right">Qty / Ltr</th>
<th class="text-right">Amount</th>
</tr>
</thead>
<tbody>
`;
frm._report_data.forEach((row, index) => {
html += `
<tr>
<td class="text-center">${index + 1}</td>
<td>${frappe.datetime.str_to_user(row.voucher_date)}</td>
<td>${row.voucher_no}</td>
<td>${row.vehicle_no}</td>
<td>${row.driver || '-'}</td>
<td class="text-right">${row.km_start}</td>
<td class="text-right">${row.km_end}</td>
<td class="text-right">${row.km_total}</td>
<td class="text-right">${format_float(row.kpl)}</td>
<td class="text-right">${format_float(row.qty)}</td>
<td class="text-right">${format_currency(row.amount)}</td>
</tr>
`;
});
html += `
</tbody>
</table>
<div class="footer">
<p>Report generated from ERPNext - ${frappe.datetime.now_datetime()}</p>
<p>Total Records: ${frm._report_data.length}</p>
</div>
</body>
</html>
`;
return html;
}
Key Functions:
-
search_expenses()- Fetches Vehicle Expense records -
fetch_expense_items()- Fetches related expense items -
display_report()- Shows HTML table with results -
download_report()- Exports to CSV -
preview_report()- Opens print preview
What I’ve Tried
-
Verified user has Administrator and System Manager roles -
Checked Role Permissions Manager - all permissions granted -
Cleared browser cache and reloaded -
Tried with different user accounts -
Ran bench clear-cacheandbench migrate -
Issue persists
Questions
-
Is there a permission check in the client script that might be blocking access?
-
Do I need special permissions for the
report_htmlfield? -
Could
frm.disable_save()be causing permission issues? -
Are there any server-side permission checks I need to configure?
Expected Behavior
Form should load normally, allowing users with appropriate roles to:
-
Select date range, vehicle, and expense type
-
Click “Search” to generate report
-
View results in HTML table
-
Download CSV or preview print version
Actual Behavior
Permission error dialog appears immediately on form load, blocking all access to the form.
Additional Context
-
This is a report-type DocType (not for data entry)
-
Uses HTML field
report_htmlto display results -
Fetches data from existing “Vehicle Expense” DocType
-
No custom Python controller - only client-side JavaScript
Attachments
Any guidance would be greatly appreciated! Thank you! ![]()


