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:
parent
54ac6288fd
commit
381aebdbdb
|
|
@ -124,7 +124,7 @@ frappe.ui.form.on('Bank Statement Importer', {
|
||||||
},
|
},
|
||||||
|
|
||||||
sample_file(frm) {
|
sample_file(frm) {
|
||||||
BIFileFormat.applySampleHeaders(frm);
|
BIFileFormat.onSampleFileChange(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
header_row(frm) {
|
header_row(frm) {
|
||||||
|
|
@ -134,10 +134,6 @@ frappe.ui.form.on('Bank Statement Importer', {
|
||||||
preview_sample_btn(frm) {
|
preview_sample_btn(frm) {
|
||||||
BIFileFormat.showPreview(frm);
|
BIFileFormat.showPreview(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
auto_detect_btn(frm) {
|
|
||||||
BIFileFormat.autoDetect(frm);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── File Format helpers ─────────────────────────────────────────────────────
|
// ─── File Format helpers ─────────────────────────────────────────────────────
|
||||||
|
|
@ -172,11 +168,10 @@ const BIFileFormat = {
|
||||||
},
|
},
|
||||||
callback: (r) => {
|
callback: (r) => {
|
||||||
if (!r.message || !r.message.success) {
|
if (!r.message || !r.message.success) {
|
||||||
frappe.msgprint({
|
frappe.show_alert({
|
||||||
title: __('Sample File Error'),
|
|
||||||
indicator: 'red',
|
|
||||||
message: (r.message && r.message.message) || __('Could not read sample file.'),
|
message: (r.message && r.message.message) || __('Could not read sample file.'),
|
||||||
});
|
indicator: 'red',
|
||||||
|
}, 5);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const headers = r.message.headers || [];
|
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]),
|
message: __("No headers found in row {0}. Adjust 'Header Row'.", [frm.doc.header_row || 1]),
|
||||||
indicator: 'orange',
|
indicator: 'orange',
|
||||||
}, 5);
|
}, 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) {
|
setColumnOptions(frm, headers) {
|
||||||
const grid = frm.fields_dict.column_mappings && frm.fields_dict.column_mappings.grid;
|
const grid = frm.fields_dict.column_mappings && frm.fields_dict.column_mappings.grid;
|
||||||
if (!grid) return;
|
if (!grid) return;
|
||||||
|
|
@ -286,72 +286,48 @@ const BIFileFormat = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
autoDetect(frm) {
|
autoDetect(frm, silent) {
|
||||||
if (!frm.doc.sample_file) {
|
if (!frm.doc.sample_file) return;
|
||||||
frappe.msgprint(__('Upload a Sample Excel File first.'));
|
frappe.call({
|
||||||
return;
|
method: 'jey_erp.bank_integration.excel_parser.auto_detect_column_mappings',
|
||||||
}
|
args: {
|
||||||
const existing = (frm.doc.column_mappings || []).filter(r => r.excel_column || r.standard_field);
|
file_url: frm.doc.sample_file,
|
||||||
const proceed = () => {
|
header_row: frm.doc.header_row || 1,
|
||||||
frappe.call({
|
},
|
||||||
method: 'jey_erp.bank_integration.excel_parser.auto_detect_column_mappings',
|
callback: (r) => {
|
||||||
args: {
|
if (!r.message || !r.message.success) {
|
||||||
file_url: frm.doc.sample_file,
|
frappe.show_alert({
|
||||||
header_row: frm.doc.header_row || 1,
|
message: (r.message && r.message.message) || __('Could not read sample file.'),
|
||||||
},
|
indicator: 'red',
|
||||||
callback: (r) => {
|
}, 5);
|
||||||
if (!r.message || !r.message.success) {
|
return;
|
||||||
frappe.msgprint({
|
}
|
||||||
title: __('Auto-detect Error'),
|
const mappings = r.message.mappings || [];
|
||||||
indicator: 'red',
|
if (!mappings.length) {
|
||||||
message: (r.message && r.message.message) || __('Could not read sample file.'),
|
frappe.show_alert({
|
||||||
});
|
message: __("No headers found in row {0}. Adjust 'Header Row'.", [frm.doc.header_row || 1]),
|
||||||
return;
|
indicator: 'orange',
|
||||||
}
|
}, 5);
|
||||||
const mappings = r.message.mappings || [];
|
return;
|
||||||
if (!mappings.length) {
|
}
|
||||||
frappe.msgprint({
|
frm.clear_table('column_mappings');
|
||||||
title: __('No Headers Found'),
|
mappings.forEach(m => {
|
||||||
indicator: 'orange',
|
const row = frm.add_child('column_mappings');
|
||||||
message: __("No headers found in row {0}. Adjust 'Header Row'.", [frm.doc.header_row || 1]),
|
row.excel_column = m.excel_column;
|
||||||
});
|
row.standard_field = m.standard_field || '';
|
||||||
return;
|
});
|
||||||
}
|
frm.refresh_field('column_mappings');
|
||||||
// Replace the table contents in-place.
|
frm.dirty();
|
||||||
frm.clear_table('column_mappings');
|
|
||||||
mappings.forEach(m => {
|
|
||||||
const row = frm.add_child('column_mappings');
|
|
||||||
row.excel_column = m.excel_column;
|
|
||||||
row.standard_field = m.standard_field || '';
|
|
||||||
});
|
|
||||||
frm.refresh_field('column_mappings');
|
|
||||||
frm.dirty();
|
|
||||||
|
|
||||||
const matched = r.message.matched_count || 0;
|
const matched = r.message.matched_count || 0;
|
||||||
const total = r.message.total_headers || mappings.length;
|
const total = r.message.total_headers || mappings.length;
|
||||||
const unmatched = r.message.unmatched || [];
|
const indicator = matched === total ? 'green' : (matched > 0 ? 'orange' : 'red');
|
||||||
let msg = __('Mapped {0} of {1} column(s).', [matched, total]);
|
frappe.show_alert({
|
||||||
if (unmatched.length) {
|
message: __('Auto-mapped {0} of {1} columns.', [matched, total]),
|
||||||
msg += '<br><br>' + __('Unmapped columns (set Standard Field manually if needed):') +
|
indicator,
|
||||||
'<br><code>' + unmatched.map(frappe.utils.escape_html).join(', ') + '</code>';
|
}, silent ? 4 : 6);
|
||||||
}
|
},
|
||||||
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,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if (existing.length) {
|
|
||||||
frappe.confirm(
|
|
||||||
__('This will replace the {0} existing Column Mapping row(s). Continue?', [existing.length]),
|
|
||||||
proceed,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
proceed();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,6 @@
|
||||||
"sample_file",
|
"sample_file",
|
||||||
"ff_sample_actions_section",
|
"ff_sample_actions_section",
|
||||||
"preview_sample_btn",
|
"preview_sample_btn",
|
||||||
"ff_sample_actions_col",
|
|
||||||
"auto_detect_btn",
|
|
||||||
"ff_format_section",
|
"ff_format_section",
|
||||||
"header_row",
|
"header_row",
|
||||||
"amount_mode",
|
"amount_mode",
|
||||||
|
|
@ -180,22 +178,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.sample_file",
|
"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",
|
"fieldname": "preview_sample_btn",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"label": "Preview Sample"
|
"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",
|
"fieldname": "ff_format_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue