fix(file-format): auto-detect columns on sample upload, quiet alerts

Two changes that drop the manual step from the column-mapping setup:

- Auto-detect now runs implicitly when the user attaches a Sample Excel
  File. The Auto-detect Columns Button field is removed (Preview Sample
  stays — it answers a different question). Auto-detect only fires when
  the Column Mappings table is empty; a user-edited table is never
  overwritten silently.
- Replaced the modal "Auto-detect Complete" / "Sample File Error" /
  "Empty Header Row" msgprints with small bottom-right show_alert
  toasts. The result is one line — "Auto-mapped 9 of 11 columns" —
  with green/orange/red indicator instead of a full-screen dialog.

The synonym dictionary used by detection lives in code at
jey_erp/bank_integration/excel_parser.py (_HEADER_SYNONYMS).
This commit is contained in:
Ali 2026-05-18 12:50:31 +00:00
parent 54ac6288fd
commit 381aebdbdb
2 changed files with 56 additions and 93 deletions

View File

@ -124,7 +124,7 @@ frappe.ui.form.on('Bank Statement Importer', {
},
sample_file(frm) {
BIFileFormat.applySampleHeaders(frm);
BIFileFormat.onSampleFileChange(frm);
},
header_row(frm) {
@ -134,10 +134,6 @@ frappe.ui.form.on('Bank Statement Importer', {
preview_sample_btn(frm) {
BIFileFormat.showPreview(frm);
},
auto_detect_btn(frm) {
BIFileFormat.autoDetect(frm);
},
});
// ─── File Format helpers ─────────────────────────────────────────────────────
@ -172,11 +168,10 @@ const BIFileFormat = {
},
callback: (r) => {
if (!r.message || !r.message.success) {
frappe.msgprint({
title: __('Sample File Error'),
indicator: 'red',
frappe.show_alert({
message: (r.message && r.message.message) || __('Could not read sample file.'),
});
indicator: 'red',
}, 5);
return;
}
const headers = r.message.headers || [];
@ -189,16 +184,21 @@ const BIFileFormat = {
message: __("No headers found in row {0}. Adjust 'Header Row'.", [frm.doc.header_row || 1]),
indicator: 'orange',
}, 5);
} else {
frappe.show_alert({
message: __('{0} column header(s) loaded.', [headers.length]),
indicator: 'green',
}, 3);
}
},
});
},
// Called when the user attaches / replaces the Sample Excel File. Refresh
// the Excel Column dropdown and, if the table is still empty, run
// auto-detect silently. We never overwrite a user-filled table.
onSampleFileChange(frm) {
this.applySampleHeaders(frm, /*force*/ true);
const hasRows = (frm.doc.column_mappings || []).some(r => r.excel_column || r.standard_field);
if (!frm.doc.sample_file || hasRows) return;
this.autoDetect(frm, /*silent*/ true);
},
setColumnOptions(frm, headers) {
const grid = frm.fields_dict.column_mappings && frm.fields_dict.column_mappings.grid;
if (!grid) return;
@ -286,13 +286,8 @@ const BIFileFormat = {
});
},
autoDetect(frm) {
if (!frm.doc.sample_file) {
frappe.msgprint(__('Upload a Sample Excel File first.'));
return;
}
const existing = (frm.doc.column_mappings || []).filter(r => r.excel_column || r.standard_field);
const proceed = () => {
autoDetect(frm, silent) {
if (!frm.doc.sample_file) return;
frappe.call({
method: 'jey_erp.bank_integration.excel_parser.auto_detect_column_mappings',
args: {
@ -301,23 +296,20 @@ const BIFileFormat = {
},
callback: (r) => {
if (!r.message || !r.message.success) {
frappe.msgprint({
title: __('Auto-detect Error'),
indicator: 'red',
frappe.show_alert({
message: (r.message && r.message.message) || __('Could not read sample file.'),
});
indicator: 'red',
}, 5);
return;
}
const mappings = r.message.mappings || [];
if (!mappings.length) {
frappe.msgprint({
title: __('No Headers Found'),
indicator: 'orange',
frappe.show_alert({
message: __("No headers found in row {0}. Adjust 'Header Row'.", [frm.doc.header_row || 1]),
});
indicator: 'orange',
}, 5);
return;
}
// Replace the table contents in-place.
frm.clear_table('column_mappings');
mappings.forEach(m => {
const row = frm.add_child('column_mappings');
@ -329,29 +321,13 @@ const BIFileFormat = {
const matched = r.message.matched_count || 0;
const total = r.message.total_headers || mappings.length;
const unmatched = r.message.unmatched || [];
let msg = __('Mapped {0} of {1} column(s).', [matched, total]);
if (unmatched.length) {
msg += '<br><br>' + __('Unmapped columns (set Standard Field manually if needed):') +
'<br><code>' + unmatched.map(frappe.utils.escape_html).join(', ') + '</code>';
}
msg += '<br><br>' + __('Review the table, then <b>Save</b> the form to confirm.');
frappe.msgprint({
title: __('Auto-detect Complete'),
indicator: matched === total ? 'green' : (matched > 0 ? 'orange' : 'red'),
message: msg,
});
const indicator = matched === total ? 'green' : (matched > 0 ? 'orange' : 'red');
frappe.show_alert({
message: __('Auto-mapped {0} of {1} columns.', [matched, total]),
indicator,
}, silent ? 4 : 6);
},
});
};
if (existing.length) {
frappe.confirm(
__('This will replace the {0} existing Column Mapping row(s). Continue?', [existing.length]),
proceed,
);
} else {
proceed();
}
},
};

View File

@ -25,8 +25,6 @@
"sample_file",
"ff_sample_actions_section",
"preview_sample_btn",
"ff_sample_actions_col",
"auto_detect_btn",
"ff_format_section",
"header_row",
"amount_mode",
@ -180,22 +178,11 @@
},
{
"depends_on": "eval:doc.sample_file",
"description": "Show the first 20 data rows from the sample file with mapping status per column.",
"description": "Show the first 20 data rows from the sample file with mapping status per column. Column mappings are auto-detected when you attach a file — re-attach to re-run detection.",
"fieldname": "preview_sample_btn",
"fieldtype": "Button",
"label": "Preview Sample"
},
{
"fieldname": "ff_sample_actions_col",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.sample_file",
"description": "Replace the Column Mappings table with a best-effort guess based on the sample file's headers.",
"fieldname": "auto_detect_btn",
"fieldtype": "Button",
"label": "Auto-detect Columns"
},
{
"fieldname": "ff_format_section",
"fieldtype": "Section Break",