reverse changes push

This commit is contained in:
Ali 2026-05-11 12:45:28 +00:00
parent 3c1dee9a72
commit f1876b6088
11 changed files with 55 additions and 351 deletions

View File

@ -17,17 +17,10 @@ def parse_excel(file_url, preset_name):
preset = frappe.get_doc("Bank Integration Excel Preset", preset_name) preset = frappe.get_doc("Bank Integration Excel Preset", preset_name)
# row.standard_field is a Link to Bank Integration Standard Field whose
# `field_name` holds the technical key (date / reference_number / debit / ...).
col_to_standard = {} col_to_standard = {}
for row in preset.column_mappings: for row in preset.column_mappings:
if not row.excel_column or not row.standard_field: if row.excel_column and row.standard_field:
continue col_to_standard[row.excel_column.strip()] = row.standard_field
field_name = frappe.db.get_value(
"Bank Integration Standard Field", row.standard_field, "field_name"
)
if field_name:
col_to_standard[row.excel_column.strip()] = field_name
if not col_to_standard: if not col_to_standard:
frappe.throw(f"Preset '{preset_name}' has no column mappings") frappe.throw(f"Preset '{preset_name}' has no column mappings")

View File

@ -244,81 +244,6 @@ def _upsert_purpose(purpose, drcr, bank_integration):
return False return False
@frappe.whitelist()
def detect_columns_from_sample(file_url, header_row=1):
"""Read column headers + first 5 data rows from a sample Excel file.
Returns:
{success, headers: [...], preview_rows: [[...], ...]}
The frontend uses this to populate column_mappings and the preview HTML.
After detection the frontend deletes the sample File doc.
"""
from openpyxl import load_workbook
header_row = int(header_row or 1)
if not file_url:
frappe.throw(_("Sample file is required"))
file_doc = frappe.get_doc("File", {"file_url": file_url})
file_path = file_doc.get_full_path()
wb = load_workbook(file_path, data_only=True, read_only=True)
try:
ws = wb.active
all_rows = list(ws.iter_rows(values_only=True))
header_idx = header_row - 1
if header_idx < 0 or header_idx >= len(all_rows):
return {"success": False, "message": _("Header row {0} is out of range (file has {1} rows)").format(header_row, len(all_rows))}
headers_raw = all_rows[header_idx]
headers = [str(c).strip() if c is not None else "" for c in headers_raw]
# Trim trailing empty header columns
while headers and not headers[-1]:
headers.pop()
if not headers:
return {"success": False, "message": _("No column headers found in row {0}").format(header_row)}
preview_rows = []
for raw in all_rows[header_idx + 1: header_idx + 6]:
row_cells = []
for i in range(len(headers)):
val = raw[i] if i < len(raw) else None
if val is None:
row_cells.append("")
elif hasattr(val, "isoformat"):
row_cells.append(val.isoformat())
else:
row_cells.append(str(val))
preview_rows.append(row_cells)
return {
"success": True,
"headers": headers,
"preview_rows": preview_rows,
}
finally:
wb.close()
@frappe.whitelist()
def delete_sample_file(file_url):
"""Remove a Sample Excel File after detection. Idempotent."""
if not file_url:
return {"success": True}
try:
name = frappe.db.get_value("File", {"file_url": file_url}, "name")
if name:
frappe.delete_doc("File", name, ignore_permissions=True, force=True)
frappe.db.commit()
except Exception as e:
frappe.log_error(f"delete_sample_file failed for {file_url}: {e}", "BI Sample Cleanup")
return {"success": True}
@frappe.whitelist() @frappe.whitelist()
def update_bank_account_bi_default(bank_account, bi_type, bi_name): def update_bank_account_bi_default(bank_account, bi_type, bi_name):
"""Save the user's BRT-time choice to the BA's hidden fields.""" """Save the user's BRT-time choice to the BA's hidden fields."""

View File

@ -9,16 +9,56 @@
"date_format": "%Y-%m-%d", "date_format": "%Y-%m-%d",
"amount_mode": "Separate debit/credit columns", "amount_mode": "Separate debit/credit columns",
"column_mappings": [ "column_mappings": [
{"doctype": "Bank Integration Excel Column Mapping", "excel_column": "Date", "standard_field": "Date"}, {
{"doctype": "Bank Integration Excel Column Mapping", "excel_column": "Reference", "standard_field": "Reference Number"}, "doctype": "Bank Integration Excel Column Mapping",
{"doctype": "Bank Integration Excel Column Mapping", "excel_column": "Counterparty", "standard_field": "Counterparty"}, "excel_column": "Date",
{"doctype": "Bank Integration Excel Column Mapping", "excel_column": "Tax ID", "standard_field": "Counterparty Tax ID"}, "standard_field": "date"
{"doctype": "Bank Integration Excel Column Mapping", "excel_column": "IBAN", "standard_field": "Counterparty IBAN"}, },
{"doctype": "Bank Integration Excel Column Mapping", "excel_column": "Debit", "standard_field": "Debit"}, {
{"doctype": "Bank Integration Excel Column Mapping", "excel_column": "Credit", "standard_field": "Credit"}, "doctype": "Bank Integration Excel Column Mapping",
{"doctype": "Bank Integration Excel Column Mapping", "excel_column": "Currency", "standard_field": "Currency"}, "excel_column": "Reference",
{"doctype": "Bank Integration Excel Column Mapping", "excel_column": "Purpose", "standard_field": "Purpose"}, "standard_field": "reference_number"
{"doctype": "Bank Integration Excel Column Mapping", "excel_column": "Description", "standard_field": "Description"} },
{
"doctype": "Bank Integration Excel Column Mapping",
"excel_column": "Counterparty",
"standard_field": "counterparty"
},
{
"doctype": "Bank Integration Excel Column Mapping",
"excel_column": "Tax ID",
"standard_field": "counterparty_tax_id"
},
{
"doctype": "Bank Integration Excel Column Mapping",
"excel_column": "IBAN",
"standard_field": "counterparty_iban"
},
{
"doctype": "Bank Integration Excel Column Mapping",
"excel_column": "Debit",
"standard_field": "debit"
},
{
"doctype": "Bank Integration Excel Column Mapping",
"excel_column": "Credit",
"standard_field": "credit"
},
{
"doctype": "Bank Integration Excel Column Mapping",
"excel_column": "Currency",
"standard_field": "currency"
},
{
"doctype": "Bank Integration Excel Column Mapping",
"excel_column": "Purpose",
"standard_field": "purpose"
},
{
"doctype": "Bank Integration Excel Column Mapping",
"excel_column": "Description",
"standard_field": "description"
}
] ]
} }
] ]

View File

@ -1,14 +0,0 @@
[
{"doctype": "Bank Integration Standard Field", "name": "Date", "field_name": "date", "description": "Transaction date (parsed using preset's date_format)."},
{"doctype": "Bank Integration Standard Field", "name": "Reference Number", "field_name": "reference_number", "description": "Unique reference / document number from the bank statement."},
{"doctype": "Bank Integration Standard Field", "name": "Counterparty", "field_name": "counterparty", "description": "Sender or receiver name."},
{"doctype": "Bank Integration Standard Field", "name": "Counterparty Tax ID", "field_name": "counterparty_tax_id", "description": "Counterparty's VOEN / TIN / INN."},
{"doctype": "Bank Integration Standard Field", "name": "Counterparty IBAN", "field_name": "counterparty_iban", "description": "Counterparty's bank account / IBAN."},
{"doctype": "Bank Integration Standard Field", "name": "Amount", "field_name": "amount", "description": "Transaction amount (use with 'Single column with sign' or 'Single column + direction column' modes)."},
{"doctype": "Bank Integration Standard Field", "name": "Debit", "field_name": "debit", "description": "Debit / outgoing amount column (use with 'Separate debit/credit columns' mode)."},
{"doctype": "Bank Integration Standard Field", "name": "Credit", "field_name": "credit", "description": "Credit / incoming amount column (use with 'Separate debit/credit columns' mode)."},
{"doctype": "Bank Integration Standard Field", "name": "Direction", "field_name": "direction", "description": "D/C / Debit/Credit indicator (use with 'Single column + direction column' mode)."},
{"doctype": "Bank Integration Standard Field", "name": "Currency", "field_name": "currency", "description": "Currency code (AZN, USD, EUR, etc.)."},
{"doctype": "Bank Integration Standard Field", "name": "Purpose", "field_name": "purpose", "description": "Payment purpose / narrative."},
{"doctype": "Bank Integration Standard Field", "name": "Description", "field_name": "description", "description": "Additional info / notes."}
]

View File

@ -158,9 +158,6 @@ fixtures = [
{ {
"doctype": "Print Format" "doctype": "Print Format"
}, },
{
"doctype": "Bank Integration Standard Field"
},
{ {
"doctype": "Bank Integration Excel Preset" "doctype": "Bank Integration Excel Preset"
}, },

View File

@ -19,10 +19,10 @@
}, },
{ {
"fieldname": "standard_field", "fieldname": "standard_field",
"fieldtype": "Link", "fieldtype": "Select",
"in_list_view": 1, "in_list_view": 1,
"label": "Standard Field", "label": "Standard Field",
"options": "Bank Integration Standard Field", "options": "\ndate\nreference_number\ncounterparty\ncounterparty_tax_id\ncounterparty_iban\namount\ndebit\ncredit\ndirection\ncurrency\npurpose\ndescription",
"reqd": 1 "reqd": 1
}, },
{ {

View File

@ -1,135 +0,0 @@
// Bank Integration Excel Preset form: "Detect Columns from Sample" button.
// Workflow:
// 1. User uploads a sample of their bank's xlsx into Sample File.
// 2. Clicks "Detect Columns from Sample" → backend reads the header row
// and first 5 data rows.
// 3. Column Mappings table is repopulated: one row per Excel header,
// Standard Field left empty for the user to fill in.
// 4. Preview HTML below shows the first 5 rows so the user can check
// they picked the right header row / skip rows.
// 5. Sample File is deleted; field is cleared.
frappe.ui.form.on("Bank Integration Excel Preset", {
refresh(frm) {
frm.add_custom_button(__("Detect Columns from Sample"), () => {
BIPresetDetect.run(frm);
});
// Render existing preview content if any (kept in memory, not persisted)
if (frm._bi_preview_html) {
BIPresetDetect.renderPreview(frm, frm._bi_preview_html);
}
},
sample_file(frm) {
// User changed sample file — clear stale preview
if (!frm.doc.sample_file) {
frm._bi_preview_html = "";
BIPresetDetect.renderPreview(frm, "");
}
},
});
const BIPresetDetect = {
run(frm) {
if (!frm.doc.sample_file) {
frappe.msgprint({
title: __("Sample File Required"),
indicator: "orange",
message: __("Upload a sample Excel file first (the 'Sample Excel File' field)."),
});
return;
}
const headerRow = frm.doc.header_row || 1;
frappe.show_alert({ message: __("Detecting columns..."), indicator: "blue" });
frappe.call({
method: "jey_erp.bank_integration.import_api.detect_columns_from_sample",
args: {
file_url: frm.doc.sample_file,
header_row: headerRow,
},
callback(r) {
if (!r.message || !r.message.success) {
frappe.msgprint({
title: __("Detection Failed"),
indicator: "red",
message: (r.message && r.message.message) || __("Could not parse sample file."),
});
return;
}
const headers = r.message.headers || [];
const previewRows = r.message.preview_rows || [];
BIPresetDetect.populateMappings(frm, headers);
BIPresetDetect.buildAndShowPreview(frm, headers, previewRows);
BIPresetDetect.cleanupSample(frm);
frappe.show_alert({
message: __("Detected {0} column(s). Pick a Standard Field for each.", [headers.length]),
indicator: "green",
}, 6);
},
});
},
populateMappings(frm, headers) {
// Replace existing column_mappings rows with new ones (one per header).
frm.clear_table("column_mappings");
headers.forEach((h) => {
if (!h) return;
const row = frm.add_child("column_mappings");
row.excel_column = h;
row.standard_field = "";
});
frm.refresh_field("column_mappings");
},
buildAndShowPreview(frm, headers, rows) {
let html = '<div style="max-height:300px; overflow:auto;">';
html += '<table class="table table-bordered table-condensed" style="font-size:0.85em;">';
html += '<thead><tr>';
headers.forEach((h) => {
html += '<th style="white-space:nowrap;">' + frappe.utils.escape_html(h || "") + '</th>';
});
html += '</tr></thead><tbody>';
rows.forEach((r) => {
html += '<tr>';
for (let i = 0; i < headers.length; i++) {
const cell = (i < r.length ? r[i] : "");
html += '<td>' + frappe.utils.escape_html(cell || "") + '</td>';
}
html += '</tr>';
});
html += '</tbody></table></div>';
frm._bi_preview_html = html;
BIPresetDetect.renderPreview(frm, html);
},
renderPreview(frm, html) {
const field = frm.get_field("preview_html");
if (!field) return;
field.html(html || '<div class="text-muted">' + __("No preview yet — upload a sample and click Detect.") + '</div>');
},
cleanupSample(frm) {
const fileUrl = frm.doc.sample_file;
if (!fileUrl) return;
// Clear field locally; if the doc is already saved, also persist
frm.set_value("sample_file", "");
frappe.call({
method: "jey_erp.bank_integration.import_api.delete_sample_file",
args: { file_url: fileUrl },
});
// If preset is already saved, persist the cleared field
if (!frm.is_new()) {
frm.save();
}
},
};

View File

@ -13,13 +13,8 @@
"date_format", "date_format",
"column_break_1", "column_break_1",
"amount_mode", "amount_mode",
"sample_section",
"sample_file",
"sample_help_html",
"columns_section", "columns_section",
"column_mappings", "column_mappings"
"preview_section",
"preview_html"
], ],
"fields": [ "fields": [
{ {
@ -75,23 +70,6 @@
"label": "Amount Mode", "label": "Amount Mode",
"options": "Single column with sign\nSeparate debit/credit columns\nSingle column + direction column" "options": "Single column with sign\nSeparate debit/credit columns\nSingle column + direction column"
}, },
{
"fieldname": "sample_section",
"fieldtype": "Section Break",
"label": "Sample File (auto-detect helper)",
"collapsible": 1
},
{
"fieldname": "sample_file",
"fieldtype": "Attach",
"label": "Sample Excel File",
"description": "Upload a sample of your bank's Excel export. Click 'Detect Columns from Sample' to populate Column Mappings and preview the first 5 parsed rows. The file is removed after detection."
},
{
"fieldname": "sample_help_html",
"fieldtype": "HTML",
"options": "<div class=\"text-muted\" style=\"margin-top:6px;\">After upload, click <b>Detect Columns from Sample</b> in the top-right menu. The button reads the header row, fills the table below with empty Standard Field rows, and shows a 5-row preview at the bottom. Then pick a Standard Field for each column.</div>"
},
{ {
"fieldname": "columns_section", "fieldname": "columns_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@ -102,16 +80,6 @@
"fieldtype": "Table", "fieldtype": "Table",
"label": "Column Mappings", "label": "Column Mappings",
"options": "Bank Integration Excel Column Mapping" "options": "Bank Integration Excel Column Mapping"
},
{
"fieldname": "preview_section",
"fieldtype": "Section Break",
"label": "Sample Preview"
},
{
"fieldname": "preview_html",
"fieldtype": "HTML",
"label": "Preview"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,

View File

@ -1,62 +0,0 @@
{
"actions": [],
"autoname": "Prompt",
"creation": "2026-05-08 00:00:00.000000",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"field_name",
"description"
],
"fields": [
{
"fieldname": "field_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Internal Field Name",
"reqd": 1,
"unique": 1,
"description": "Technical key used by the parser (date, reference_number, debit, etc.)"
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-05-08 00:00:00.000000",
"modified_by": "Administrator",
"module": "Jey Erp",
"name": "Bank Integration Standard Field",
"naming_rule": "By script",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1
}
],
"sort_field": "modified",
"sort_order": "ASC",
"states": [],
"track_changes": 0
}

View File

@ -1,8 +0,0 @@
# Copyright (c) 2026, JeyERP and contributors
# For license information, please see license.txt
from frappe.model.document import Document
class BankIntegrationStandardField(Document):
pass