reverse changes push
This commit is contained in:
parent
3c1dee9a72
commit
f1876b6088
|
|
@ -17,17 +17,10 @@ def parse_excel(file_url, 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 = {}
|
||||
for row in preset.column_mappings:
|
||||
if not row.excel_column or not row.standard_field:
|
||||
continue
|
||||
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 row.excel_column and row.standard_field:
|
||||
col_to_standard[row.excel_column.strip()] = row.standard_field
|
||||
|
||||
if not col_to_standard:
|
||||
frappe.throw(f"Preset '{preset_name}' has no column mappings")
|
||||
|
|
|
|||
|
|
@ -244,81 +244,6 @@ def _upsert_purpose(purpose, drcr, bank_integration):
|
|||
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()
|
||||
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."""
|
||||
|
|
|
|||
|
|
@ -9,16 +9,56 @@
|
|||
"date_format": "%Y-%m-%d",
|
||||
"amount_mode": "Separate debit/credit columns",
|
||||
"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", "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"}
|
||||
{
|
||||
"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",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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."}
|
||||
]
|
||||
|
|
@ -158,9 +158,6 @@ fixtures = [
|
|||
{
|
||||
"doctype": "Print Format"
|
||||
},
|
||||
{
|
||||
"doctype": "Bank Integration Standard Field"
|
||||
},
|
||||
{
|
||||
"doctype": "Bank Integration Excel Preset"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@
|
|||
},
|
||||
{
|
||||
"fieldname": "standard_field",
|
||||
"fieldtype": "Link",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"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
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -13,13 +13,8 @@
|
|||
"date_format",
|
||||
"column_break_1",
|
||||
"amount_mode",
|
||||
"sample_section",
|
||||
"sample_file",
|
||||
"sample_help_html",
|
||||
"columns_section",
|
||||
"column_mappings",
|
||||
"preview_section",
|
||||
"preview_html"
|
||||
"column_mappings"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -75,23 +70,6 @@
|
|||
"label": "Amount Mode",
|
||||
"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",
|
||||
"fieldtype": "Section Break",
|
||||
|
|
@ -102,16 +80,6 @@
|
|||
"fieldtype": "Table",
|
||||
"label": "Column Mappings",
|
||||
"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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue