fix(bank-integration): validate required Excel columns per Amount Mode

Importing into Mode 3 with a preset that had no Amount mapping silently
dropped every row and showed a confusing "No transactions found" message.
Two fixes:

- Preset.validate() now requires the Standard Fields the parser needs for
  the chosen mode (Mode 1: Date+Amount; Mode 2: Date+Debit+Credit; Mode 3:
  Date+Amount+Direction). Clear error on save lists exactly what's missing.
- When all rows drop due to unreadable amount, the import dialog now opens
  a helper modal pointing at the preset, instead of the generic empty
  message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ali 2026-05-13 11:23:16 +00:00
parent bd5d773daa
commit 44c3281cb9
2 changed files with 63 additions and 7 deletions

View File

@ -6,11 +6,40 @@ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
# Standard fields the parser needs depending on the chosen amount_mode.
# Date is always required.
_REQUIRED_BY_MODE = {
"Single column with sign": ("Date", "Amount"),
"Separate debit/credit columns": ("Date", "Debit", "Credit"),
"Single column + direction column": ("Date", "Amount", "Direction"),
}
class BankIntegrationExcelPreset(Document): class BankIntegrationExcelPreset(Document):
def validate(self): def validate(self):
if self.amount_mode == "Single column + direction column": self._validate_required_mappings()
if not (self.direction_debit_values or "").strip() and not (self.direction_credit_values or "").strip(): self._validate_direction_values()
frappe.throw(_(
"Mode 'Single column + direction column' requires at least one of " def _validate_required_mappings(self):
"'Debit Values' or 'Credit Values' to be filled (comma-separated)." mode = self.amount_mode or "Separate debit/credit columns"
)) required = _REQUIRED_BY_MODE.get(mode, ())
mapped = {
(row.standard_field or "").strip()
for row in (self.column_mappings or [])
if row.excel_column and row.standard_field
}
missing = [f for f in required if f not in mapped]
if missing:
frappe.throw(_(
"Amount Mode '{0}' requires these Standard Fields to be mapped in the Column Mappings table: {1}. "
"Add a row for each missing field."
).format(mode, ", ".join(missing)))
def _validate_direction_values(self):
if self.amount_mode != "Single column + direction column":
return
if not (self.direction_debit_values or "").strip() and not (self.direction_credit_values or "").strip():
frappe.throw(_(
"Mode 'Single column + direction column' requires at least one of "
"'Debit Values' or 'Credit Values' to be filled (comma-separated)."
))

View File

@ -128,13 +128,17 @@ const BIExcelImport = {
BIExcelImport._showDirectionHelp(droppedDirection, unknownDirections, values.preset); BIExcelImport._showDirectionHelp(droppedDirection, unknownDirections, values.preset);
return; return;
} }
if (droppedAmount > 0 && !txns.length) {
BIExcelImport._showAmountHelp(droppedAmount, values.preset);
return;
}
if (!txns.length) { if (!txns.length) {
frappe.msgprint({ frappe.msgprint({
title: __('Nothing to Import'), title: __('Nothing to Import'),
indicator: 'blue', indicator: 'blue',
message: skipped > 0 message: skipped > 0
? __('All {0} parsed rows are duplicates.', [skipped]) ? __('All {0} parsed rows are duplicates.', [skipped])
: __('No transactions found in the file.'), : __('No transactions found in the file. Check that the Header Row on the preset points at the actual header line.'),
}); });
return; return;
} }
@ -316,6 +320,29 @@ const BIExcelImport = {
d.show(); d.show();
}, },
_showAmountHelp(droppedCount, presetName) {
const openPreset = function () {
frappe.set_route('Form', 'Bank Integration Excel Preset', presetName);
};
const d = new frappe.ui.Dialog({
title: __('Amount Column Missing or Empty'),
fields: [{
fieldtype: 'HTML',
options:
'<div class="alert alert-danger">' +
__('All {0} row(s) were dropped because the amount could not be read.', [droppedCount]) +
'</div>' +
'<p>' + __('Most likely the <b>Amount</b> column (or <b>Debit</b>/<b>Credit</b>, depending on Amount Mode) is not mapped to an Excel header in the preset.') + '</p>' +
'<p>' + __('Open the preset and check the Column Mappings table — Standard Field values must include all columns required by the chosen Amount Mode.') + '</p>',
}],
primary_action_label: __('Open Preset'),
primary_action() { d.hide(); openPreset(); },
secondary_action_label: __('Close'),
secondary_action() { d.hide(); },
});
d.show();
},
_showDirectionHelp(droppedCount, unknownDirections, presetName) { _showDirectionHelp(droppedCount, unknownDirections, presetName) {
const openPreset = function () { const openPreset = function () {
frappe.set_route('Form', 'Bank Integration Excel Preset', presetName); frappe.set_route('Form', 'Bank Integration Excel Preset', presetName);