feat(bank-integration): populate the Data tab subtabs
The Accounts / Customers / Suppliers / Purposes subtabs under "Data" were stuck on "Loading..." — nothing rendered into them. Added get_bi_reference_data_list (paginated rows for each registry, scoped to the Bank Integration record) and the client-side rendering in bank_integration.js (tables with status pills, links to the underlying records and their ERPNext mappings, plus a Refresh button per subtab) — mirroring the kapital_bank_settings Data tab. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2dba6908b0
commit
7e862e48e5
|
|
@ -252,6 +252,69 @@ def _upsert_purpose(purpose, drcr, bank_integration):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_bi_reference_data_list(bank_integration, data_type, limit=100, offset=0):
|
||||||
|
"""Paginated rows for the Bank Integration "Data" tab subtabs.
|
||||||
|
|
||||||
|
data_type ∈ {accounts, customers, suppliers, purposes}.
|
||||||
|
"""
|
||||||
|
limit = cint(limit)
|
||||||
|
offset = cint(offset)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if data_type == "accounts":
|
||||||
|
total = frappe.db.count("Bank Integration Account Mapping", filters={
|
||||||
|
"parent": bank_integration, "parenttype": "Bank Integration"
|
||||||
|
})
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Bank Integration Account Mapping",
|
||||||
|
filters={"parent": bank_integration, "parenttype": "Bank Integration"},
|
||||||
|
fields=["name", "iban", "account_label", "currency", "bank_account", "gl_account"],
|
||||||
|
limit=limit, start=offset, order_by="idx asc",
|
||||||
|
)
|
||||||
|
|
||||||
|
elif data_type == "customers":
|
||||||
|
total = frappe.db.count("Bank Integration Customer", filters={"parent_bank_integration": bank_integration})
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Bank Integration Customer",
|
||||||
|
filters={"parent_bank_integration": bank_integration},
|
||||||
|
fields=["name", "customer_name", "tax_id", "iban", "status", "mapped_customer", "creation"],
|
||||||
|
limit=limit, start=offset, order_by="customer_name asc",
|
||||||
|
)
|
||||||
|
|
||||||
|
elif data_type == "suppliers":
|
||||||
|
total = frappe.db.count("Bank Integration Supplier", filters={"parent_bank_integration": bank_integration})
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Bank Integration Supplier",
|
||||||
|
filters={"parent_bank_integration": bank_integration},
|
||||||
|
fields=["name", "supplier_name", "tax_id", "iban", "status", "mapped_supplier", "creation"],
|
||||||
|
limit=limit, start=offset, order_by="supplier_name asc",
|
||||||
|
)
|
||||||
|
|
||||||
|
elif data_type == "purposes":
|
||||||
|
total = frappe.db.count("Bank Integration Purpose", filters={"parent_bank_integration": bank_integration})
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Bank Integration Purpose",
|
||||||
|
filters={"parent_bank_integration": bank_integration},
|
||||||
|
fields=["name", "purpose_keyword", "direction", "status"],
|
||||||
|
limit=limit, start=offset, order_by="purpose_keyword asc",
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return {"success": False, "message": f"Unknown data_type: {data_type}"}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"data": data,
|
||||||
|
"total_count": total,
|
||||||
|
"has_more": (offset + limit) < total,
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"get_bi_reference_data_list({data_type}): {e}\n{frappe.get_traceback()}", "BI Reference Data")
|
||||||
|
return {"success": False, "message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
@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."""
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,9 @@ frappe.ui.form.on('Bank Integration', {
|
||||||
}, 5),
|
}, 5),
|
||||||
));
|
));
|
||||||
}, __('Purposes'));
|
}, __('Purposes'));
|
||||||
|
|
||||||
|
// === Data tab rendering ===
|
||||||
|
_bi_load_data_tabs(frm);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -226,3 +229,147 @@ function _show_load_data_dialog(frm) {
|
||||||
});
|
});
|
||||||
d.show();
|
d.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
// DATA TAB — registry overviews (Accounts / Customers / Suppliers / Purposes)
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
function _bi_load_data_tabs(frm) {
|
||||||
|
const name = frm.doc.name;
|
||||||
|
_bi_load_tab(name, 'accounts', 'bi-accounts-container', _bi_render_accounts);
|
||||||
|
_bi_load_tab(name, 'customers', 'bi-customers-container', _bi_render_customers);
|
||||||
|
_bi_load_tab(name, 'suppliers', 'bi-suppliers-container', _bi_render_suppliers);
|
||||||
|
_bi_load_tab(name, 'purposes', 'bi-purposes-container', _bi_render_purposes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _bi_load_tab(bank_integration, data_type, container_id, renderer) {
|
||||||
|
$('#' + container_id).html(_bi_loading_html());
|
||||||
|
frappe.call({
|
||||||
|
method: 'jey_erp.bank_integration.import_api.get_bi_reference_data_list',
|
||||||
|
args: { bank_integration, data_type, limit: 100, offset: 0 },
|
||||||
|
callback(r) {
|
||||||
|
if (r.message && r.message.success) {
|
||||||
|
renderer(r.message.data, r.message.total_count, () => _bi_load_tab(bank_integration, data_type, container_id, renderer));
|
||||||
|
} else {
|
||||||
|
$('#' + container_id).html(_bi_error_html(r.message ? r.message.message : __('Unknown error')));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error() { $('#' + container_id).html(_bi_error_html(__('Network error'))); },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _bi_render_accounts(rows, total, reload) {
|
||||||
|
let html = _bi_list_header(__('Accounts'), total, 'bi-refresh-accounts');
|
||||||
|
if (rows && rows.length) {
|
||||||
|
html += `<table class="table table-bordered" style="font-size:13px;">
|
||||||
|
<thead class="grid-heading-row"><tr>
|
||||||
|
<th>${__('IBAN')}</th><th>${__('Label')}</th><th>${__('Currency')}</th>
|
||||||
|
<th>${__('Bank Account')}</th><th>${__('GL Account')}</th>
|
||||||
|
</tr></thead><tbody>`;
|
||||||
|
rows.forEach(a => {
|
||||||
|
const ba = a.bank_account ? `<a href="/app/bank-account/${encodeURIComponent(a.bank_account)}">${a.bank_account}</a>` : '-';
|
||||||
|
const gl = a.gl_account ? `<a href="/app/account/${encodeURIComponent(a.gl_account)}">${a.gl_account}</a>` : '-';
|
||||||
|
html += `<tr><td>${a.iban || '-'}</td><td>${frappe.utils.escape_html(a.account_label || '-')}</td>
|
||||||
|
<td>${a.currency || '-'}</td><td>${ba}</td><td>${gl}</td></tr>`;
|
||||||
|
});
|
||||||
|
html += '</tbody></table>';
|
||||||
|
} else {
|
||||||
|
html += _bi_empty_state(__('No account mappings yet. Add rows in the Mappings tab.'));
|
||||||
|
}
|
||||||
|
$('#bi-accounts-container').html(html);
|
||||||
|
$('#bi-refresh-accounts').on('click', reload);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _bi_render_customers(rows, total, reload) {
|
||||||
|
let html = _bi_list_header(__('Customers'), total, 'bi-refresh-customers');
|
||||||
|
if (rows && rows.length) {
|
||||||
|
html += `<table class="table table-bordered" style="font-size:13px;">
|
||||||
|
<thead class="grid-heading-row"><tr>
|
||||||
|
<th>${__('Customer Name')}</th><th>${__('Tax ID (VOEN)')}</th><th>${__('IBAN')}</th>
|
||||||
|
<th>${__('Status')}</th><th>${__('Mapped To')}</th><th>${__('Created')}</th>
|
||||||
|
</tr></thead><tbody>`;
|
||||||
|
rows.forEach(c => {
|
||||||
|
const pill = c.status === 'Mapped' ? 'green' : 'red';
|
||||||
|
const mapped = c.mapped_customer ? `<a href="/app/customer/${encodeURIComponent(c.mapped_customer)}">${c.mapped_customer}</a>` : '-';
|
||||||
|
const created = c.creation ? frappe.datetime.str_to_user(c.creation) : '-';
|
||||||
|
html += `<tr>
|
||||||
|
<td><a href="/app/bank-integration-customer/${encodeURIComponent(c.name)}">${frappe.utils.escape_html(c.customer_name)}</a></td>
|
||||||
|
<td>${c.tax_id || '-'}</td><td>${c.iban || '-'}</td>
|
||||||
|
<td><span class="indicator-pill ${pill}">${__(c.status)}</span></td>
|
||||||
|
<td>${mapped}</td><td>${created}</td></tr>`;
|
||||||
|
});
|
||||||
|
html += '</tbody></table>';
|
||||||
|
} else {
|
||||||
|
html += _bi_empty_state(__('No customers registered yet. Import a statement (with "Also load counterparties & purposes") or use "Load Data".'));
|
||||||
|
}
|
||||||
|
$('#bi-customers-container').html(html);
|
||||||
|
$('#bi-refresh-customers').on('click', reload);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _bi_render_suppliers(rows, total, reload) {
|
||||||
|
let html = _bi_list_header(__('Suppliers'), total, 'bi-refresh-suppliers');
|
||||||
|
if (rows && rows.length) {
|
||||||
|
html += `<table class="table table-bordered" style="font-size:13px;">
|
||||||
|
<thead class="grid-heading-row"><tr>
|
||||||
|
<th>${__('Supplier Name')}</th><th>${__('Tax ID (VOEN)')}</th><th>${__('IBAN')}</th>
|
||||||
|
<th>${__('Status')}</th><th>${__('Mapped To')}</th><th>${__('Created')}</th>
|
||||||
|
</tr></thead><tbody>`;
|
||||||
|
rows.forEach(s => {
|
||||||
|
const pill = s.status === 'Mapped' ? 'green' : 'red';
|
||||||
|
const mapped = s.mapped_supplier ? `<a href="/app/supplier/${encodeURIComponent(s.mapped_supplier)}">${s.mapped_supplier}</a>` : '-';
|
||||||
|
const created = s.creation ? frappe.datetime.str_to_user(s.creation) : '-';
|
||||||
|
html += `<tr>
|
||||||
|
<td><a href="/app/bank-integration-supplier/${encodeURIComponent(s.name)}">${frappe.utils.escape_html(s.supplier_name)}</a></td>
|
||||||
|
<td>${s.tax_id || '-'}</td><td>${s.iban || '-'}</td>
|
||||||
|
<td><span class="indicator-pill ${pill}">${__(s.status)}</span></td>
|
||||||
|
<td>${mapped}</td><td>${created}</td></tr>`;
|
||||||
|
});
|
||||||
|
html += '</tbody></table>';
|
||||||
|
} else {
|
||||||
|
html += _bi_empty_state(__('No suppliers registered yet.'));
|
||||||
|
}
|
||||||
|
$('#bi-suppliers-container').html(html);
|
||||||
|
$('#bi-refresh-suppliers').on('click', reload);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _bi_render_purposes(rows, total, reload) {
|
||||||
|
let html = _bi_list_header(__('Purposes'), total, 'bi-refresh-purposes');
|
||||||
|
if (rows && rows.length) {
|
||||||
|
html += `<table class="table table-bordered" style="font-size:13px;">
|
||||||
|
<thead class="grid-heading-row"><tr>
|
||||||
|
<th>${__('Purpose Keyword')}</th><th>${__('Direction')}</th><th>${__('Status')}</th>
|
||||||
|
</tr></thead><tbody>`;
|
||||||
|
rows.forEach(p => {
|
||||||
|
const pill = p.status === 'Mapped' ? 'green' : 'red';
|
||||||
|
html += `<tr>
|
||||||
|
<td><a href="/app/bank-integration-purpose/${encodeURIComponent(p.name)}">${frappe.utils.escape_html(p.purpose_keyword || '')}</a></td>
|
||||||
|
<td>${__(p.direction || '-')}</td>
|
||||||
|
<td><span class="indicator-pill ${pill}">${__(p.status)}</span></td></tr>`;
|
||||||
|
});
|
||||||
|
html += '</tbody></table>';
|
||||||
|
} else {
|
||||||
|
html += _bi_empty_state(__('No purpose keywords registered yet.'));
|
||||||
|
}
|
||||||
|
$('#bi-purposes-container').html(html);
|
||||||
|
$('#bi-refresh-purposes').on('click', reload);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _bi_loading_html() {
|
||||||
|
return `<div class="text-center text-muted" style="padding:20px;"><i class="fa fa-spinner fa-spin"></i> ${__('Loading...')}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _bi_empty_state(msg) {
|
||||||
|
return `<div class="text-center text-muted" style="padding:30px;"><p>${msg}</p></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _bi_error_html(msg) {
|
||||||
|
return `<div class="alert alert-warning"><p>${__('Error loading data')}: ${frappe.utils.escape_html(String(msg))}</p></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _bi_list_header(label, total, refresh_id) {
|
||||||
|
return `<div class="clearfix" style="margin-bottom:10px;">
|
||||||
|
<div class="pull-left"><h6 class="text-muted">${label} (${total} ${__('total')})</h6></div>
|
||||||
|
<div class="pull-right"><button class="btn btn-xs btn-default" id="${refresh_id}">
|
||||||
|
<i class="octicon octicon-sync"></i> ${__('Refresh')}</button></div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue