feat: enrich card transaction descriptions and add duplicate warning

Build full description from operationName, fullRemark, shortRemark, and
description fields for card statement transactions. Show a warning banner
when card transactions are present since duplicate detection across card
and account statements is not possible due to different reference numbers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ali 2026-03-27 17:06:07 +04:00
parent 677c1492e7
commit dd5ffc0bed
3 changed files with 32 additions and 5 deletions

View File

@ -1412,6 +1412,14 @@ def get_card_statement_transactions(from_date, to_date, card_account_number, log
# Purpose (operationName serves as both purpose and counterparty description) # Purpose (operationName serves as both purpose and counterparty description)
operation_name = (txn.get("operationName") or "").strip() operation_name = (txn.get("operationName") or "").strip()
# Build enriched description from all available remark fields
desc_parts = [operation_name] if operation_name else []
for field in ("fullRemark", "shortRemark", "description"):
val = (txn.get(field) or "").strip()
if val and val not in desc_parts:
desc_parts.append(val)
full_description = " || ".join(desc_parts)
transactions.append({ transactions.append({
"ref_no": txn_id, "ref_no": txn_id,
"date": str(parsed_date), "date": str(parsed_date),
@ -1421,6 +1429,7 @@ def get_card_statement_transactions(from_date, to_date, card_account_number, log
"currency": currency, "currency": currency,
"drcr": dr_cr, "drcr": dr_cr,
"purpose": operation_name, "purpose": operation_name,
"full_description": full_description,
"source_type": "card", "source_type": "card",
"source_label": f"Card: {pan}", "source_label": f"Card: {pan}",
}) })
@ -2202,7 +2211,7 @@ def _import_one_bank_transaction(txn_data, settings, voen_to_party, name_to_part
bt.deposit = amount if dr_cr == "C" else 0 bt.deposit = amount if dr_cr == "C" else 0
bt.withdrawal = amount if dr_cr == "D" else 0 bt.withdrawal = amount if dr_cr == "D" else 0
bt.currency = currency bt.currency = currency
bt.description = purpose bt.description = txn_data.get("full_description") or purpose
bt.reference_number = ref_no bt.reference_number = ref_no
bt.transaction_id = ref_no bt.transaction_id = ref_no
bt.company = settings.default_company bt.company = settings.default_company

View File

@ -527,7 +527,16 @@ KBBTImport.import = {
}, },
showTransactionSelection: function(txns) { showTransactionSelection: function(txns) {
let txnTable = '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered kb-bt-txn-table" style="width: 100%; table-layout: fixed;">'; const hasCardTxns = txns.some(t => t.source_type === 'card');
let txnTable = '';
if (hasCardTxns) {
txnTable += '<div style="margin-bottom: 10px; padding: 10px 12px; background: var(--red-50, #fff5f5); border: 1px solid var(--red-200, #fecaca); border-radius: var(--border-radius); font-size: var(--text-base); color: var(--red-600, #dc2626);">';
txnTable += '⚠️ ' + __('Card transactions may already be imported from other accounts. The bank uses different reference numbers for card and account statements, so duplicates cannot be detected automatically.');
txnTable += '</div>';
}
txnTable += '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered kb-bt-txn-table" style="width: 100%; table-layout: fixed;">';
txnTable += '<thead style="position: sticky; top: 0; z-index: 1; background: var(--bg-color, #fff);"><tr>' + txnTable += '<thead style="position: sticky; top: 0; z-index: 1; background: var(--bg-color, #fff);"><tr>' +
'<th style="width: 4%;"><input type="checkbox" class="kb-bt-select-all-txns"></th>' + '<th style="width: 4%;"><input type="checkbox" class="kb-bt-select-all-txns"></th>' +
'<th style="width: 14%;">' + __('Ref No') + '</th>' + '<th style="width: 14%;">' + __('Ref No') + '</th>' +
@ -555,7 +564,7 @@ KBBTImport.import = {
'<td style="word-break: break-word;">' + (txn.counterparty || '') + '</td>' + '<td style="word-break: break-word;">' + (txn.counterparty || '') + '</td>' +
'<td style="text-align: right;">' + amountFmt + ' ' + (txn.currency || '') + '</td>' + '<td style="text-align: right;">' + amountFmt + ' ' + (txn.currency || '') + '</td>' +
'<td style="text-align: center;">' + drcrLabel + '</td>' + '<td style="text-align: center;">' + drcrLabel + '</td>' +
'<td style="word-break: break-word; font-size: 0.9em;">' + (txn.purpose || '') + '</td>' + '<td style="word-break: break-word; font-size: 0.9em;">' + (txn.full_description || txn.purpose || '') + '</td>' +
'</tr>'; '</tr>';
}); });

View File

@ -485,7 +485,16 @@ KBImport.import = {
}, },
showTransactionSelection: function(txns) { showTransactionSelection: function(txns) {
let txnTable = '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered kb-txn-table" style="width: 100%; table-layout: fixed;">'; const hasCardTxns = txns.some(t => t.source_type === 'card');
let txnTable = '';
if (hasCardTxns) {
txnTable += '<div style="margin-bottom: 10px; padding: 10px 12px; background: var(--red-50, #fff5f5); border: 1px solid var(--red-200, #fecaca); border-radius: var(--border-radius); font-size: var(--text-base); color: var(--red-600, #dc2626);">';
txnTable += '⚠️ ' + __('Card transactions may already be imported from other accounts. The bank uses different reference numbers for card and account statements, so duplicates cannot be detected automatically.');
txnTable += '</div>';
}
txnTable += '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered kb-txn-table" style="width: 100%; table-layout: fixed;">';
txnTable += '<thead style="position: sticky; top: 0; z-index: 1; background: var(--bg-color, #fff);"><tr>' + txnTable += '<thead style="position: sticky; top: 0; z-index: 1; background: var(--bg-color, #fff);"><tr>' +
'<th style="width: 4%;"><input type="checkbox" class="kb-select-all-txns"></th>' + '<th style="width: 4%;"><input type="checkbox" class="kb-select-all-txns"></th>' +
'<th style="width: 14%;">' + __('Ref No') + '</th>' + '<th style="width: 14%;">' + __('Ref No') + '</th>' +
@ -513,7 +522,7 @@ KBImport.import = {
'<td style="word-break: break-word;">' + (txn.counterparty || '') + '</td>' + '<td style="word-break: break-word;">' + (txn.counterparty || '') + '</td>' +
'<td style="text-align: right;">' + amountFmt + ' ' + (txn.currency || '') + '</td>' + '<td style="text-align: right;">' + amountFmt + ' ' + (txn.currency || '') + '</td>' +
'<td style="text-align: center;">' + drcrLabel + '</td>' + '<td style="text-align: center;">' + drcrLabel + '</td>' +
'<td style="word-break: break-word; font-size: 0.9em;">' + (txn.purpose || '') + '</td>' + '<td style="word-break: break-word; font-size: 0.9em;">' + (txn.full_description || txn.purpose || '') + '</td>' +
'</tr>'; '</tr>';
}); });