fix: major refactor to monkey-patch into the QueryReport class
diff --git a/erpnext/accounts/report/tax_detail/tax_detail.js b/erpnext/accounts/report/tax_detail/tax_detail.js
index 8cdce54..6049000 100644
--- a/erpnext/accounts/report/tax_detail/tax_detail.js
+++ b/erpnext/accounts/report/tax_detail/tax_detail.js
@@ -3,6 +3,8 @@
 // Contributed by Case Solved and sponsored by Nulight Studios
 /* eslint-disable */
 
+frappe.provide('frappe.query_reports');
+
 frappe.query_reports["Tax Detail"] = {
 	filters: [
 		{
@@ -50,83 +52,124 @@
 		// Remove Add Column and Save from menu
 		report.page.add_inner_button(__("New Report"), () => new_report(), __("Custom Report"));
 		report.page.add_inner_button(__("Load Report"), () => load_report(), __("Custom Report"));
-		hide_filters();
-	},
-	after_datatable_render: (datatable) => {
-		if (frappe.query_report.report_name == 'Tax Detail') {
-			return;
-		}
-		if (this.taxreport) {
-			this.taxreport.load_report();
-		} else {
-			this.taxreport = new TaxReport();
-		}
+		hide_filters(report);
 	}
 };
 
-function hide_filters() {
-	frappe.query_report.page.page_form[0].querySelectorAll('.form-group.frappe-control').forEach(function setHidden(field) {
+function hide_filters(report) {
+	report.page.page_form[0].querySelectorAll('.form-group.frappe-control').forEach(function setHidden(field) {
 		if (field.dataset.fieldtype == "Read Only") {
 			field.classList.add("hidden");
 		}
 	});
 }
 
-class TaxReport {
-	// construct after datatable is loaded
+erpnext.TaxDetail = class TaxDetail {
 	constructor() {
-		this.qr = frappe.query_report;
-		this.page = frappe.query_report.page;
-		this.create_controls();
+		this.patch();
 		this.load_report();
 	}
-	load_report() {
-		if (this.loaded) {
-			return;
+	// Monkey patch the QueryReport class
+	patch() {
+		this.qr = frappe.query_report;
+		this.super = {
+			refresh_report: this.qr.refresh_report,
+			show_footer_message: this.qr.show_footer_message
 		}
-		const report_name = this.qr.report_name;
-		this.report_name.value = report_name;
-		frappe.call({
-			method: 'erpnext.accounts.report.tax_detail.tax_detail.get_custom_reports',
-			args: {name: report_name},
-			freeze: true
-		}).then((r) => {
-			const data = JSON.parse(r.message[report_name]['json']);
-			this.sections = data.sections || {};
-			this.controls['show_detail'].set_input(data.show_detail);
-			this.set_section();
-		})
-		this.loaded = 1;
+		this.qr.refresh_report = () => this.refresh_report();
+		this.qr.show_footer_message = () => this.show_footer_message();
+	}
+	show_footer_message() {
+		// The last thing to run after datatable_render in refresh()
+		console.log('show_footer_message');
+		this.super.show_footer_message.apply(this.qr);
+		if (this.qr.report_name !== 'Tax Detail') {
+			this.set_value_options();
+			this.show_help();
+			if (this.loading) {
+				this.set_section('');
+			}
+			this.reload_filter();
+		}
+		this.loading = false;
+	}
+	refresh_report() {
+		// Infrequent report build (onload), load filters & data
+		// super function runs a refresh() serially
+		// already run within frappe.run_serially
+		console.log('refresh_report');
+		this.loading = true;
+		this.super.refresh_report.apply(this.qr);
+		if (this.qr.report_name !== 'Tax Detail') {
+			frappe.call({
+				method: 'erpnext.accounts.report.tax_detail.tax_detail.get_custom_reports',
+				args: {name: this.qr.report_name}
+			}).then((r) => {
+				const data = JSON.parse(r.message[this.qr.report_name]['json']);
+				this.create_controls();
+				this.sections = data.sections || {};
+				this.controls['show_detail'].set_input(data.show_detail);
+			});
+		}
+	}
+	load_report() {
+		// One-off report build like titles, menu, etc
+		// Run when this object is created which happens in qr.load_report
+		console.log('load_report');
+		this.qr.menu_items = this.get_menu_items();
+	}
+	get_menu_items() {
+		// Replace save button
+		let new_items = [];
+		const label = __('Save');
+
+		for (let item of this.qr.menu_items) {
+			if (item.label === label) {
+				new_items.push({
+					label: label,
+					action: this.save_report,
+					standard: false
+				});
+			} else {
+				new_items.push(item);
+			}
+		}
+		return new_items;
 	}
 	save_report() {
-		frappe.call({
-			method:'erpnext.accounts.report.tax_detail.tax_detail.save_custom_report',
-			args: {
-				reference_report: 'Tax Detail',
-				report_name: this.qr.report_name,
-				data: {
-					columns: this.qr.get_visible_columns(),
-					sections: this.sections,
-					show_detail: this.controls['show_detail'].get_input_value()
-				}
-			},
-			freeze: true
-		}).then((r) => {
-			this.reload();
-		});
+		if (this.qr.report_name !== 'Tax Detail') {
+			frappe.call({
+				method:'erpnext.accounts.report.tax_detail.tax_detail.save_custom_report',
+				args: {
+					reference_report: 'Tax Detail',
+					report_name: this.qr.report_name,
+					data: {
+						columns: this.qr.get_visible_columns(),
+						sections: this.sections,
+						show_detail: this.controls['show_detail'].get_input_value()
+					}
+				},
+				freeze: true
+			}).then((r) => {
+				this.set_section('');
+			});
+		}
 	}
 	set_value_options() {
-		this.fieldname_lookup = {};
-		this.label_lookup = {};
-		this.qr.columns.forEach((col, index) => {
-			if (col['fieldtype'] == "Currency") {
-				this.fieldname_lookup[col['label']] = col['fieldname'];
-				this.label_lookup[col['fieldname']] = col['label'];
-			}
-		});
-		const options = Object.keys(this.fieldname_lookup);
-		this.controls['value_field'].$wrapper.find("select").empty().add_options(options);
-		this.controls['value_field'].set_input(options[0]);
+		// May be run with no columns or data
+		if (this.qr.columns) {
+			this.fieldname_lookup = {};
+			this.label_lookup = {};
+			this.qr.columns.forEach((col, index) => {
+				if (col['fieldtype'] == "Currency") {
+					this.fieldname_lookup[col['label']] = col['fieldname'];
+					this.label_lookup[col['fieldname']] = col['label'];
+				}
+			});
+			const options = Object.keys(this.fieldname_lookup);
+			this.controls['value_field'].$wrapper.find("select").empty().add_options(options);
+			this.controls['value_field'].set_input(options[0]);
+		}
 	}
 	set_value_label_from_filter() {
 		const section_name = this.controls['section_name'].value;
@@ -158,46 +201,38 @@
 		});
 		dialog.show();
 	}
-	get_filter_controls() {
-		this.qr.filters.forEach(filter => {
-			if (filter['fieldname'] == 'mode') {
-				this.mode = filter;
-			}
-			if (filter['fieldname'] == 'report_name') {
-				this.report_name = filter;
-			}
-		});
-	}
-	set_mode(mode) {
-		this.mode.value = mode;
-	}
-	edit_mode() {
-		return this.mode.value == 'edit';
-	}
 	set_section(name) {
+		// Sets the given section name and then reloads the data
 		if (name && !this.sections[name]) {
 			this.sections[name] = {};
 		}
 		let options = Object.keys(this.sections);
 		options.unshift('');
 		this.controls['section_name'].$wrapper.find("select").empty().add_options(options);
+		const org_mode = this.qr.get_filter_value('mode');
+		let refresh = false;
 		if (name) {
 			this.controls['section_name'].set_input(name);
+			this.qr.set_filter_value('mode', 'edit');
+			if (org_mode === 'run') {
+				refresh = true;
+			}
 		} else {
 			this.controls['section_name'].set_input('');
+			this.qr.set_filter_value('mode', 'run');
+			if (org_mode === 'edit') {
+				refresh = true;
+			}
 		}
-		if (this.controls['section_name'].value) {
-			this.set_mode('edit');
-		} else {
-			this.set_mode('run');
+		this.reload_filter();
+		if (refresh) {
+			this.qr.refresh();
 		}
-		this.controls['filter_index'].set_input('');
-		this.reload();
 	}
 	reload_filter() {
-		const section_name = this.controls['section_name'].value;
+		const section_name = this.controls['section_name'].get_input_value();
 		if (section_name) {
-			let fidx = this.controls['filter_index'].value;
+			let fidx = this.controls['filter_index'].get_input_value();
 			let section = this.sections[section_name];
 			let fidxs = Object.keys(section);
 			fidxs.unshift('');
@@ -207,17 +242,16 @@
 			this.controls['filter_index'].$wrapper.find("select").empty();
 			this.controls['filter_index'].set_input('');
 		}
-		this.set_filters();
+		this.set_table_filters();
 	}
-	set_filters() {
+	set_table_filters() {
 		let filters = {};
-		const section_name = this.controls['section_name'].value;
-		const fidx = this.controls['filter_index'].value;
+		const section_name = this.controls['section_name'].get_input_value();
+		const fidx = this.controls['filter_index'].get_input_value();
 		if (section_name && fidx) {
 			filters = this.sections[section_name][fidx]['filters'];
 		}
 		this.setAppliedFilters(filters);
-		this.qr.datatable.columnmanager.applyFilter(filters);
 		this.set_value_label_from_filter();
 	}
 	setAppliedFilters(filters) {
@@ -229,32 +263,20 @@
 				input.value = null;
 			}
 		});
-	}
-	reload() {
-		// Reloads the data. When the datatable is reloaded, load_report()
-		// will be run by the after_datatable_render event.
-		// TODO: why does this trigger multiple reloads?
-		this.qr.refresh();
-		this.show_help();
-		if (this.edit_mode()) {
-			this.reload_filter();
-		} else {
-			this.controls['filter_index'].$wrapper.find("select").empty();
-		}
+		this.qr.datatable.columnmanager.applyFilter(filters);
 	}
 	delete(name, type) {
 		if (type === 'section') {
 			delete this.sections[name];
-			this.controls['section_name'].$wrapper.find("select").empty().add_options(Object.keys(this.sections));
-			this.controls['section_name'].set_input(Object.keys(this.sections)[0] || '');
-			this.controls['filter_index'].set_input('');
+			const new_section = Object.keys(this.sections)[0] || '';
+			this.set_section(new_section);
 		}
 		if (type === 'filter') {
-			let cur_section = this.controls['section_name'].value;
+			const cur_section = this.controls['section_name'].get_input_value();
 			delete this.sections[cur_section][name];
 			this.controls['filter_index'].set_input('');
+			this.reload_filter();
 		}
-		this.reload();
 	}
 	create_controls() {
 		if (this.controls) {
@@ -262,7 +284,7 @@
 		}
 		let controls = {};
 		// SELECT in data.js
-		controls['section_name'] = this.page.add_field({
+		controls['section_name'] = this.qr.page.add_field({
 			label: __('Section'),
 			fieldtype: 'Select',
 			fieldname: 'section_name',
@@ -271,7 +293,7 @@
 			}
 		});
 		// BUTTON in button.js
-		controls['new_section'] = this.page.add_field({
+		controls['new_section'] = this.qr.page.add_field({
 			label: __('New Section'),
 			fieldtype: 'Button',
 			fieldname: 'new_section',
@@ -279,33 +301,33 @@
 				this.new_section(__('New Section'));
 			}
 		});
-		controls['delete_section'] = this.page.add_field({
+		controls['delete_section'] = this.qr.page.add_field({
 			label: __('Delete Section'),
 			fieldtype: 'Button',
 			fieldname: 'delete_section',
 			click: () => {
-				let cur_section = this.controls['section_name'].value;
+				let cur_section = this.controls['section_name'].get_input_value();
 				if (cur_section) {
 					frappe.confirm(__('Are you sure you want to delete section ') + cur_section + '?',
 					() => {this.delete(cur_section, 'section')});
 				}
 			}
 		});
-		controls['filter_index'] = this.page.add_field({
+		controls['filter_index'] = this.qr.page.add_field({
 			label: __('Filter'),
 			fieldtype: 'Select',
 			fieldname: 'filter_index',
 			change: (e) => {
 				this.controls['filter_index'].set_input(this.controls['filter_index'].get_input_value());
-				this.set_filters();
+				this.set_table_filters();
 			}
 		});
-		controls['add_filter'] = this.page.add_field({
+		controls['add_filter'] = this.qr.page.add_field({
 			label: __('Add Filter'),
 			fieldtype: 'Button',
 			fieldname: 'add_filter',
 			click: () => {
-				let section_name = this.controls['section_name'].value;
+				let section_name = this.controls['section_name'].get_input_value();
 				if (section_name) {
 					let prefix = 'Filter';
 					let data = {
@@ -326,19 +348,19 @@
 				}
 			}
 		});
-		controls['delete_filter'] = this.page.add_field({
+		controls['delete_filter'] = this.qr.page.add_field({
 			label: __('Delete Filter'),
 			fieldtype: 'Button',
 			fieldname: 'delete_filter',
 			click: () => {
-				let cur_filter = this.controls['filter_index'].value;
+				let cur_filter = this.controls['filter_index'].get_input_value();
 				if (cur_filter) {
 					frappe.confirm(__('Are you sure you want to delete filter ') + cur_filter + '?',
 					() => {this.delete(cur_filter, 'filter')});
 				}
 			}
 		});
-		controls['value_field'] = this.page.add_field({
+		controls['value_field'] = this.qr.page.add_field({
 			label: __('Value Column'),
 			fieldtype: 'Select',
 			fieldname: 'value_field',
@@ -346,37 +368,35 @@
 				this.controls['value_field'].set_input(this.controls['value_field'].get_input_value());
 			}
 		});
-		controls['save'] = this.page.add_field({
+		controls['save'] = this.qr.page.add_field({
 			label: __('Save & Run'),
 			fieldtype: 'Button',
 			fieldname: 'save',
 			click: () => {
-				this.controls['section_name'].set_input('');
-				this.set_mode('run');
 				this.save_report();
 			}
 		});
-		controls['show_detail'] = this.page.add_field({
+		controls['show_detail'] = this.qr.page.add_field({
 			label: __('Show Detail'),
 			fieldtype: 'Check',
 			fieldname: 'show_detail',
 			default: 1
 		});
 		this.controls = controls;
-		this.set_value_options();
-		this.get_filter_controls();
-		this.show_help();
 	}
 	show_help() {
 		const help = __(`You can add multiple sections to your custom report using the New Section button above.
-			To specify what data goes in each section, specify column filters below, then save with Add Filter.
-			Each section can have multiple filters added.
-			You can specify which Currency column will be summed for each filter in the final report with the Value Column select box.
-			Once you're done, hit Save & Run.`);
-		this.qr.show_status(help);
+			To specify what data goes in each section, specify column filters in the data table, then save with Add Filter.
+			Each section can have multiple filters added but be careful with the duplicated data rows.
+			You can specify which Currency column will be summed for each filter in the final report with the Value Column
+			select box. Once you're done, hit Save & Run.`);
+		this.qr.$report_footer.append(`<div class="col-md-12">${help}</div>`);
 	}
 }
 
+if (!window.taxdetail) {
+	window.taxdetail = new erpnext.TaxDetail();
+}
 
 function get_reports(cb) {
 	frappe.call({
@@ -387,23 +407,9 @@
 	})
 }
 
-function override_menu() {
-	//TODO: Replace save button
-	this.qr.menu_items.forEach((item, idx) => {
-		if (item['label'] == __('Save')) {
-			delete this.qr.menu_items[idx];
-		}
-	})
-	this.qr.menu_items.push({
-		label: __('Save'),
-		action: this.save_report
-	})
-	this.qr.set_menu_items();
-}
-
 function new_report() {
 	const dialog = new frappe.ui.Dialog({
-		title: __("New Report"),
+		title: __('New Report'),
 		fields: [
 			{
 				fieldname: 'report_name',
@@ -424,7 +430,7 @@
 				},
 				freeze: true
 			}).then((r) => {
-				frappe.set_route("query-report", values.report_name);
+				frappe.set_route('query-report', values.report_name);
 			});
 			dialog.hide();
 		}
@@ -435,7 +441,7 @@
 function load_report() {
 	get_reports(function load_report_cb(reports) {
 		const dialog = new frappe.ui.Dialog({
-			title: __("Load Report"),
+			title: __('Load Report'),
 			fields: [
 				{
 					fieldname: 'report_name',
@@ -447,7 +453,7 @@
 			primary_action_label: __('Load'),
 			primary_action: function load_report_pa(values) {
 				dialog.hide();
-				frappe.set_route("query-report", values.report_name);
+				frappe.set_route('query-report', values.report_name);
 			}
 		});
 		dialog.show();