Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2026-03-28 02:28:06.918430",
"modified": "2026-05-15 14:33:05.591103",
"modified_by": "Administrator",
"module": "Klarna Kosma Integration",
"name": "Bank Reconciliation Rule",
Expand Down Expand Up @@ -146,4 +146,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

import frappe
from erpnext import get_company_currency, get_default_cost_center
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.accounts.doctype.bank_transaction.bank_transaction import (
get_total_allocated_amount,
)
Expand Down Expand Up @@ -34,6 +37,56 @@

MAX_QUERY_RESULTS = 150

STANDARD_ACCOUNTING_DIMENSION_FIELDS = frozenset({"project", "cost_center"})


def _get_allowed_accounting_dimension_fields() -> set[str]:
"""Return whitelisted dimension fieldnames (custom + project + cost_center)."""
return set(get_accounting_dimensions(as_list=True)) | STANDARD_ACCOUNTING_DIMENSION_FIELDS


def _parse_accounting_dimensions_json(accounting_dimensions: str | None) -> dict:
"""Parse accounting_dimensions JSON from the reco UI; return {} on invalid input."""
if not accounting_dimensions:
return {}

try:
dimensions = json.loads(accounting_dimensions)
except (TypeError, json.JSONDecodeError):
return {}

if not isinstance(dimensions, dict):
return {}

return dimensions


def _merge_accounting_dimensions_into_payment_entry(
payment_entry: Document, accounting_dimensions: str | None
) -> None:
"""Stamp selected accounting dimensions onto Payment Entry header fields."""
allowed = _get_allowed_accounting_dimension_fields()
for key, value in _parse_accounting_dimensions_json(accounting_dimensions).items():
if key not in allowed or not value:
continue
if payment_entry.meta.get_field(key):
payment_entry.set(key, value)


def _merge_accounting_dimensions_into_je_accounts(
account_rows: list[dict], accounting_dimensions: str | None
) -> None:
"""Stamp selected accounting dimensions onto non-bank Journal Entry Account rows."""
allowed = _get_allowed_accounting_dimension_fields()
for key, value in _parse_accounting_dimensions_json(accounting_dimensions).items():
if key not in allowed or not value:
continue
for row in account_rows:
# Do not tag the bank GL line (like Payment Entry): dimensions belong on the other leg only.
if row.get("bank_account"):
continue
row[key] = value


class BankReconciliationToolBeta(Document):
# begin: auto-generated types
Expand Down Expand Up @@ -128,8 +181,13 @@ def create_journal_entry_bts(
party_type: str | None = None,
party: str | None = None,
allow_edit: bool | str = False,
accounting_dimensions: str | None = None,
):
"""Create a new Journal Entry for Reconciling the Bank Transaction"""
"""Create a new Journal Entry for reconciling the Bank Transaction.

:param accounting_dimensions: JSON object mapping dimension fieldnames to values
(applied to non-bank account rows only).
"""
if isinstance(allow_edit, str):
allow_edit = sbool(allow_edit)

Expand Down Expand Up @@ -167,26 +225,25 @@ def create_journal_entry_bts(
"user_remark": bank_transaction.description,
}
)
journal_entry.set(
"accounts",
[
{
"account": second_account,
"credit_in_account_currency": bank_debit_amount,
"debit_in_account_currency": bank_credit_amount,
"party_type": party_type,
"party": party,
"cost_center": get_default_cost_center(company),
},
{
"account": bank_gl_account,
"bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_credit_amount,
"debit_in_account_currency": bank_debit_amount,
"cost_center": get_default_cost_center(company),
},
],
)
account_rows = [
{
"account": second_account,
"credit_in_account_currency": bank_debit_amount,
"debit_in_account_currency": bank_credit_amount,
"party_type": party_type,
"party": party,
"cost_center": get_default_cost_center(company),
},
{
"account": bank_gl_account,
"bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_credit_amount,
"debit_in_account_currency": bank_debit_amount,
"cost_center": get_default_cost_center(company),
},
]
_merge_accounting_dimensions_into_je_accounts(account_rows, accounting_dimensions)
journal_entry.set("accounts", account_rows)

company_currency = get_company_currency(company)
journal_entry.multi_currency = (
Expand Down Expand Up @@ -228,8 +285,14 @@ def create_payment_entry_bts(
mode_of_payment: str | None = None,
project: str | None = None,
cost_center: str | None = None,
accounting_dimensions: str | None = None,
allow_edit: bool = False,
):
"""Create a new Payment Entry for reconciling the Bank Transaction.

:param accounting_dimensions: JSON object mapping dimension fieldnames to values
(applied to Payment Entry header fields).
"""
if isinstance(allow_edit, str):
allow_edit = sbool(allow_edit)

Expand Down Expand Up @@ -262,10 +325,13 @@ def create_payment_entry_bts(

if mode_of_payment:
payment_entry.mode_of_payment = mode_of_payment

_merge_accounting_dimensions_into_payment_entry(payment_entry, accounting_dimensions)
if project:
payment_entry.project = project
if cost_center:
payment_entry.cost_center = cost_center

if payment_type == "Receive":
payment_entry.paid_to = company_account
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

from banking.exceptions import CurrencyMismatchError, FullReconciliationRequiredError
from banking.klarna_kosma_integration.doctype.bank_reconciliation_tool_beta.bank_reconciliation_tool_beta import (
_merge_accounting_dimensions_into_je_accounts,
_merge_accounting_dimensions_into_payment_entry,
auto_reconcile_vouchers,
bulk_reconcile_vouchers,
create_journal_entry_bts,
Expand Down Expand Up @@ -266,6 +268,44 @@ def test_pe_against_transaction(self):
self.assertEqual(len(bt.payment_entries), 1)
self.assertEqual(bt.status, "Reconciled")

def test_merge_accounting_dimensions_into_payment_entry(self):
payment_entry = frappe.new_doc("Payment Entry")
payment_entry.company = "_Test Company"
_merge_accounting_dimensions_into_payment_entry(
payment_entry,
json.dumps(
{
"project": "PROJ-TEST",
"cost_center": "Main - _TC",
"not_a_real_dimension": "ignored",
}
),
)

self.assertEqual(payment_entry.project, "PROJ-TEST")
self.assertEqual(payment_entry.cost_center, "Main - _TC")

def test_merge_accounting_dimensions_into_je_accounts(self):
account_rows = [
{"account": "Debtors - _TC"},
{"account": "Bank - _TC", "bank_account": self.bank_account},
]
_merge_accounting_dimensions_into_je_accounts(
account_rows,
json.dumps(
{
"project": "PROJ-TEST",
"cost_center": "Main - _TC",
"not_a_real_dimension": "ignored",
}
),
)

self.assertEqual(account_rows[0]["project"], "PROJ-TEST")
self.assertEqual(account_rows[0]["cost_center"], "Main - _TC")
self.assertNotIn("project", account_rows[1])
self.assertNotIn("not_a_real_dimension", account_rows[0])

def test_jv_against_transaction(self):
bt = create_bank_transaction(deposit=200, reference_no="abcdef123", bank_account=self.bank_account)
create_journal_entry_bts(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ erpnext.accounts.bank_reconciliation.ActionsPanelManager = class ActionsPanelMan
transaction: this.transaction,
panel_manager: this.panel_manager,
company: this.frm.doc.company,
accounting_dimensions:
this.panel_manager.accounting_dimensions || [],
accounting_dimension_defaults:
this.panel_manager.accounting_dimension_defaults || {},
});
},
},
Expand Down
Loading
Loading