fix(payment reconciliation): honour user permissions on accounting dimensions (#55803)

(cherry picked from commit d1ffac36c1)

# Conflicts:
#	erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
#	erpnext/accounts/services/advances.py
This commit is contained in:
NaviN
2026-06-23 13:36:29 +05:30
committed by Mergify
parent be605adbc1
commit f3aaf46b4d
3 changed files with 660 additions and 6 deletions

View File

@@ -6,8 +6,10 @@ import frappe
from frappe import _, msgprint, qb
from frappe.model.document import Document
from frappe.model.meta import get_field_precision
from frappe.permissions import get_allowed_docs_for_doctype, get_user_permissions
from frappe.query_builder import Case, Criterion
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import IfNull
from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today
import erpnext
@@ -73,6 +75,7 @@ class PaymentReconciliation(Document):
self.accounting_dimension_filter_conditions = []
self.ple_posting_date_filter = []
self.dimensions = get_dimensions(with_cost_center_and_project=True)[0]
self.user_permissions = get_user_permissions(frappe.session.user)
def load_from_db(self):
# 'modified' attribute is required for `run_doc_method` to work properly.
@@ -153,6 +156,22 @@ class PaymentReconciliation(Document):
self.add_payment_entries(non_reconciled_payments)
def get_permitted_dimension_values(self, document_type, reference_doctype):
return get_allowed_docs_for_doctype(self.user_permissions.get(document_type, []), reference_doctype)
def validate_permitted_dimension_value(self, document_type, value, allowed):
if value and allowed and value not in allowed:
frappe.throw(
_("You do not have enough permission to access {0}: {1}").format(_(document_type), value),
frappe.PermissionError,
)
def get_user_permission_dimension_condition(self, field, allowed):
value_condition = field.isin(allowed)
if frappe.get_system_settings("apply_strict_user_permissions"):
return value_condition
return (IfNull(field, "") == "") | value_condition
def get_payment_entries(self):
party_account = [self.receivable_payable_account]
@@ -176,8 +195,13 @@ class PaymentReconciliation(Document):
dimensions = {}
for x in self.dimensions:
dimension = x.fieldname
if self.get(dimension):
dimensions.update({dimension: self.get(dimension)})
allowed = self.get_permitted_dimension_values(x.document_type, "Payment Entry")
if value := self.get(dimension):
self.validate_permitted_dimension_value(x.document_type, value, allowed)
dimensions[dimension] = value
elif allowed:
dimensions[dimension] = allowed
condition.update({"accounting_dimensions": dimensions})
payment_entries = get_advance_payment_entries_for_regional(
@@ -201,8 +225,12 @@ class PaymentReconciliation(Document):
# Dimension filters
for x in self.dimensions:
dimension = x.fieldname
if self.get(dimension):
conditions.append(jea[dimension] == self.get(dimension))
allowed = self.get_permitted_dimension_values(x.document_type, "Journal Entry Account")
if value := self.get(dimension):
self.validate_permitted_dimension_value(x.document_type, value, allowed)
conditions.append(jea[dimension] == value)
elif allowed:
conditions.append(self.get_user_permission_dimension_condition(jea[dimension], allowed))
if self.payment_name:
conditions.append(je.name.like(f"%%{self.payment_name}%%"))
@@ -746,8 +774,15 @@ class PaymentReconciliation(Document):
ple = qb.DocType("Payment Ledger Entry")
for x in self.dimensions:
dimension = x.fieldname
if self.get(dimension) and frappe.db.has_column("Payment Ledger Entry", dimension):
self.accounting_dimension_filter_conditions.append(ple[dimension] == self.get(dimension))
if frappe.db.has_column("Payment Ledger Entry", dimension):
allowed = self.get_permitted_dimension_values(x.document_type, "Payment Ledger Entry")
if value := self.get(dimension):
self.validate_permitted_dimension_value(x.document_type, value, allowed)
self.accounting_dimension_filter_conditions.append(ple[dimension] == value)
elif allowed:
self.accounting_dimension_filter_conditions.append(
self.get_user_permission_dimension_condition(ple[dimension], allowed)
)
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
self.common_filter_conditions.clear()

View File

@@ -3,8 +3,12 @@
import frappe
<<<<<<< HEAD
from frappe import qb
from frappe.utils import add_days, add_years, flt, getdate, nowdate, today
=======
from frappe.utils import add_days, add_years, cint, flt, getdate, nowdate, today
>>>>>>> d1ffac36c1 (fix(payment reconciliation): honour user permissions on accounting dimensions (#55803))
from frappe.utils.data import getdate as convert_to_date
from erpnext import get_default_cost_center
@@ -1105,6 +1109,101 @@ class TestPaymentReconciliation(ERPNextTestSuite):
payment_vouchers = [x.get("reference_name") for x in pr.get("payments")]
self.assertCountEqual(payment_vouchers, [je2.name, pe2.name])
def test_user_permission_on_accounting_dimension_filters_vouchers(self):
test_user = "test@example.com"
permitted_ccs = ["_Test Cost Center - _TC", "_Test Cost Center 2 - _TC"]
restricted_cc = "_Test Write Off Cost Center - _TC"
existing_apply_strict_user_permissions = cint(
frappe.db.get_single_value("System Settings", "apply_strict_user_permissions")
)
self.addCleanup(
frappe.db.set_single_value,
"System Settings",
"apply_strict_user_permissions",
existing_apply_strict_user_permissions,
)
transaction_date = nowdate()
rate = 100
def make_invoice(cost_center):
si = self.create_sales_invoice(
qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True
)
si.cost_center = cost_center
for row in si.items:
row.cost_center = cost_center
return si.submit()
def make_payment(cost_center):
pe = self.create_payment_entry(posting_date=transaction_date, amount=rate)
pe.cost_center = cost_center
return pe.save().submit()
def make_journal(cost_center):
je = self.create_journal_entry(
self.bank, self.debit_to, 100, transaction_date, cost_center=cost_center
)
je.accounts[1].party_type = "Customer"
je.accounts[1].party = self.customer
return je.save().submit()
# Vouchers tagged with the two permitted cost centers
si_allowed = make_invoice(permitted_ccs[0])
pe_allowed = make_payment(permitted_ccs[1])
je_allowed = make_journal(permitted_ccs[0])
# Vouchers tagged with the restricted cost center
si_restricted = make_invoice(restricted_cc)
pe_restricted = make_payment(restricted_cc)
je_restricted = make_journal(restricted_cc)
# Payment entry with a BLANK cost center
pe_blank = make_payment(None)
for cc in permitted_ccs:
frappe.permissions.add_user_permission("Cost Center", cc, test_user)
# Without strict user permissions
frappe.db.set_single_value("System Settings", "apply_strict_user_permissions", 0)
with self.set_user(test_user):
pr = self.create_payment_reconciliation()
pr.get_unreconciled_entries()
invoice_numbers = [x.get("invoice_number") for x in pr.get("invoices")]
payment_vouchers = [x.get("reference_name") for x in pr.get("payments")]
self.assertIn(si_allowed.name, invoice_numbers)
self.assertIn(pe_allowed.name, payment_vouchers)
self.assertIn(je_allowed.name, payment_vouchers)
self.assertIn(pe_blank.name, payment_vouchers)
self.assertNotIn(si_restricted.name, invoice_numbers)
self.assertNotIn(pe_restricted.name, payment_vouchers)
self.assertNotIn(je_restricted.name, payment_vouchers)
# With strict user permissions
frappe.db.set_single_value("System Settings", "apply_strict_user_permissions", 1)
with self.set_user(test_user):
pr = self.create_payment_reconciliation()
pr.get_unreconciled_entries()
invoice_numbers = [x.get("invoice_number") for x in pr.get("invoices")]
payment_vouchers = [x.get("reference_name") for x in pr.get("payments")]
self.assertIn(si_allowed.name, invoice_numbers)
self.assertIn(pe_allowed.name, payment_vouchers)
self.assertIn(je_allowed.name, payment_vouchers)
self.assertNotIn(pe_blank.name, payment_vouchers)
self.assertNotIn(si_restricted.name, invoice_numbers)
self.assertNotIn(pe_restricted.name, payment_vouchers)
self.assertNotIn(je_restricted.name, payment_vouchers)
# with restricted dimension as a filter
with self.set_user(test_user):
pr = self.create_payment_reconciliation()
pr.cost_center = restricted_cc
self.assertRaises(frappe.PermissionError, pr.get_unreconciled_entries)
for cc in permitted_ccs:
frappe.permissions.remove_user_permission("Cost Center", cc, test_user)
@ERPNextTestSuite.change_settings(
"Accounts Settings",
{

View File

@@ -0,0 +1,520 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
"""Advance payment query and management functions.
All functions take a `doc` (AccountsController instance) as first argument so
they can be called as module-level functions from any doctype, while keeping
the AccountsController methods as thin shims.
"""
import frappe
from frappe import _
from frappe.query_builder import Criterion
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Abs, IfNull, Max, Sum
from frappe.utils import flt
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_dimensions,
)
from erpnext.accounts.party import get_party_account
from erpnext.accounts.utils import get_account_currency, get_advance_payment_doctypes
from erpnext.setup.utils import get_exchange_rate
def set_advances(doc) -> None:
"""Populate the advances child table from open advance entries."""
res = get_advance_entries(
doc, include_unallocated=not frappe.utils.cint(doc.get("only_include_allocated_payments"))
)
doc.set("advances", [])
advance_allocated = 0
for d in res:
if doc.get("party_account_currency") == doc.company_currency:
amount = doc.get("base_rounded_total") or doc.base_grand_total
else:
amount = doc.get("rounded_total") or doc.grand_total
allocated_amount = min(amount - advance_allocated, d.amount)
advance_allocated += flt(allocated_amount)
advance_row = {
"doctype": doc.doctype + " Advance",
"reference_type": d.reference_type,
"reference_name": d.reference_name,
"reference_row": d.reference_row,
"remarks": d.remarks,
"advance_amount": flt(d.amount),
"allocated_amount": allocated_amount,
"ref_exchange_rate": flt(d.exchange_rate),
"difference_posting_date": doc.posting_date,
}
if d.get("paid_from"):
advance_row["account"] = d.paid_from
if d.get("paid_to"):
advance_row["account"] = d.paid_to
doc.append("advances", advance_row)
def get_advance_entries(doc, include_unallocated: bool = True) -> list:
"""Return advance journal and payment entries applicable to `doc`."""
party_account = []
default_advance_account = None
if doc.doctype in ["Sales Invoice", "POS Invoice"]:
party_type = "Customer"
party = doc.customer
amount_field = "credit_in_account_currency"
order_field = "sales_order"
order_doctype = "Sales Order"
party_account.append(doc.debit_to)
else:
party_type = "Supplier"
party = doc.supplier
amount_field = "debit_in_account_currency"
order_field = "purchase_order"
order_doctype = "Purchase Order"
party_account.append(doc.credit_to)
party_accounts = get_party_account(party_type, party=party, company=doc.company, include_advance=True)
if party_accounts:
party_account.append(party_accounts[0])
default_advance_account = party_accounts[1] if len(party_accounts) == 2 else None
order_list = list(set(d.get(order_field) for d in doc.get("items") if d.get(order_field)))
journal_entries = get_advance_journal_entries(
party_type, party, party_account, amount_field, order_doctype, order_list, include_unallocated
)
payment_entries = get_advance_payment_entries_for_regional(
party_type,
party,
party_account,
order_doctype,
order_list,
default_advance_account,
include_unallocated,
)
return journal_entries + payment_entries
def validate_advance_entries(doc) -> None:
"""Warn if a payment entry linked to the same order is not pulled as advance."""
order_field = "sales_order" if doc.doctype == "Sales Invoice" else "purchase_order"
order_list = list(set(d.get(order_field) for d in doc.get("items") if d.get(order_field)))
if not order_list:
return
advance_entries = get_advance_entries(doc, include_unallocated=False)
if advance_entries:
advance_entries_against_si = [d.reference_name for d in doc.get("advances")]
for d in advance_entries:
if not advance_entries_against_si or d.reference_name not in advance_entries_against_si:
frappe.msgprint(
_(
"Payment Entry {0} is linked against Order {1}, check if it should be pulled as advance in this invoice."
).format(d.reference_name, d.against_order)
)
def set_advance_gain_or_loss(doc) -> None:
"""Compute exchange gain/loss for each allocated advance row."""
if doc.get("conversion_rate") == 1 or not doc.get("advances"):
return
is_purchase_invoice = doc.doctype == "Purchase Invoice"
party_account = doc.credit_to if is_purchase_invoice else doc.debit_to
if get_account_currency(party_account) != doc.currency:
return
for d in doc.get("advances"):
advance_exchange_rate = d.ref_exchange_rate
if d.allocated_amount and doc.conversion_rate != advance_exchange_rate:
base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount
base_allocated_amount_in_inv_rate = doc.conversion_rate * d.allocated_amount
difference = base_allocated_amount_in_ref_rate - base_allocated_amount_in_inv_rate
d.exchange_gain_loss = difference
def calculate_total_advance_from_ledger(doc) -> list:
"""Query the Advance Payment Ledger for the total advance against `doc`."""
adv = frappe.qb.DocType("Advance Payment Ledger Entry")
return (
frappe.qb.from_(adv)
.select(Abs(Sum(adv.amount)).as_("amount"), Max(adv.currency).as_("account_currency"))
.where(adv.company == doc.company)
.where(adv.delinked == 0)
.where(adv.against_voucher_type == doc.doctype)
.where(adv.against_voucher_no == doc.name)
.run(as_dict=True)
)
def set_total_advance_paid(doc) -> None:
"""Update advance_paid field and payment status from the ledger."""
advance = calculate_total_advance_from_ledger(doc)
advance_paid = 0
if advance:
advance = advance[0]
advance_paid = flt(advance.amount, doc.precision("advance_paid"))
if advance.account_currency:
frappe.db.set_value(doc.doctype, doc.name, "party_account_currency", advance.account_currency)
doc.db_set("advance_paid", advance_paid)
set_advance_payment_status(doc)
def set_advance_payment_status(doc) -> None:
"""Sync advance_payment_status with current ledger and Payment Request state."""
new_status = None
PaymentRequest = frappe.qb.DocType("Payment Request")
paid_amount = frappe.get_value(
doctype="Payment Request",
filters={
"reference_doctype": doc.doctype,
"reference_name": doc.name,
"docstatus": 1,
},
fieldname=Sum(PaymentRequest.grand_total - PaymentRequest.outstanding_amount),
)
if not paid_amount:
if doc.doctype in get_advance_payment_doctypes(payment_type="receivable"):
new_status = "Not Requested" if paid_amount is None else "Requested"
elif doc.doctype in get_advance_payment_doctypes(payment_type="payable"):
new_status = "Not Initiated" if paid_amount is None else "Initiated"
else:
total_amount = doc.get("rounded_total") or doc.get("grand_total")
new_status = "Fully Paid" if paid_amount == total_amount else "Partially Paid"
if new_status == doc.advance_payment_status:
return
doc.db_set("advance_payment_status", new_status, update_modified=False)
doc.set_status(update=True)
doc.notify_update()
def delink_advance_entries(doc, linked_doc_name: str) -> None:
"""Remove advance rows linked to `linked_doc_name` and update total_advance."""
total_allocated_amount = 0
for adv in doc.advances:
consider_for_total_advance = True
if adv.reference_name == linked_doc_name:
doctype = frappe.qb.DocType(doc.doctype + " Advance")
frappe.qb.from_(doctype).delete().where(doctype.name == adv.name).run()
consider_for_total_advance = False
if consider_for_total_advance:
total_allocated_amount += flt(adv.allocated_amount, adv.precision("allocated_amount"))
frappe.db.set_value(doc.doctype, doc.name, "total_advance", total_allocated_amount, update_modified=False)
def create_advance_and_reconcile(doc, party_link) -> None:
"""Create a Journal Entry to reconcile a party-link advance."""
secondary_party_type, secondary_party = doc.get_party()
primary_party_type, primary_party = party_link.primary_role, party_link.primary_party
primary_account = get_party_account(primary_party_type, primary_party, doc.company)
secondary_account = get_party_account(secondary_party_type, secondary_party, doc.company)
primary_account_currency = get_account_currency(primary_account)
secondary_account_currency = get_account_currency(secondary_account)
default_currency = erpnext.get_company_currency(doc.company)
multi_currency = (
primary_account_currency != default_currency or secondary_account_currency != default_currency
)
jv = frappe.new_doc("Journal Entry")
jv.voucher_type = "Journal Entry"
jv.posting_date = doc.posting_date
jv.company = doc.company
jv.remark = f"Adjustment for {doc.doctype} {doc.name}"
jv.is_system_generated = True
reconcilation_entry = frappe._dict()
advance_entry = frappe._dict()
reconcilation_entry.account = secondary_account
reconcilation_entry.party_type = secondary_party_type
reconcilation_entry.party = secondary_party
reconcilation_entry.reference_type = doc.doctype
reconcilation_entry.reference_name = doc.name
reconcilation_entry.cost_center = doc.cost_center or erpnext.get_default_cost_center(doc.company)
advance_entry.account = primary_account
advance_entry.party_type = primary_party_type
advance_entry.party = primary_party
advance_entry.cost_center = doc.cost_center or erpnext.get_default_cost_center(doc.company)
advance_entry.is_advance = "No" if doc.is_return else "Yes"
dimensions_dict = frappe._dict()
active_dimensions = get_dimensions()[0]
for dim in active_dimensions:
dimensions_dict[dim.fieldname] = doc.get(dim.fieldname)
reconcilation_entry.update(dimensions_dict)
advance_entry.update(dimensions_dict)
if multi_currency:
exc_rate_primary_to_default = (
1
if primary_account_currency == default_currency
else get_exchange_rate(primary_account_currency, default_currency, doc.posting_date)
)
exc_rate_secondary_to_default = (
1
if secondary_account_currency == default_currency
else get_exchange_rate(secondary_account_currency, default_currency, doc.posting_date)
)
exc_rate_secondary_to_primary = (
1
if secondary_account_currency == primary_account_currency
else get_exchange_rate(secondary_account_currency, primary_account_currency, doc.posting_date)
)
outstanding_amount = abs(doc.outstanding_amount)
os_in_default_currency = outstanding_amount * exc_rate_secondary_to_default
os_in_primary_currency = outstanding_amount * exc_rate_secondary_to_primary
reconciliation_is_credit = (doc.doctype == "Sales Invoice") != bool(doc.is_return)
_set_je_amounts(
reconcilation_entry, outstanding_amount, os_in_default_currency, reconciliation_is_credit
)
_set_je_amounts(
advance_entry, os_in_primary_currency, os_in_default_currency, not reconciliation_is_credit
)
reconcilation_entry.exchange_rate = exc_rate_secondary_to_default
advance_entry.exchange_rate = exc_rate_primary_to_default
else:
outstanding_amount = abs(doc.outstanding_amount)
reconciliation_is_credit = (doc.doctype == "Sales Invoice") != bool(doc.is_return)
_set_je_amounts(reconcilation_entry, outstanding_amount, is_credit=reconciliation_is_credit)
_set_je_amounts(advance_entry, outstanding_amount, is_credit=not reconciliation_is_credit)
jv.multi_currency = multi_currency
jv.append("accounts", reconcilation_entry)
jv.append("accounts", advance_entry)
jv.save()
jv.submit()
def get_advance_journal_entries(
party_type: str,
party: str,
party_account: list,
amount_field: str,
order_doctype: str,
order_list: list,
include_unallocated: bool = True,
) -> list:
"""Return open advance journal entry rows matching the given party and orders."""
journal_entry = frappe.qb.DocType("Journal Entry")
journal_acc = frappe.qb.DocType("Journal Entry Account")
q = (
frappe.qb.from_(journal_entry)
.inner_join(journal_acc)
.on(journal_entry.name == journal_acc.parent)
.select(
ConstantColumn("Journal Entry").as_("reference_type"),
(journal_entry.name).as_("reference_name"),
(journal_entry.remark).as_("remarks"),
(journal_acc[amount_field]).as_("amount"),
(journal_acc.name).as_("reference_row"),
(journal_acc.reference_name).as_("against_order"),
(journal_acc.exchange_rate),
)
.where(
journal_acc.account.isin(party_account)
& (journal_acc.party_type == party_type)
& (journal_acc.party == party)
& (journal_acc.is_advance == "Yes")
& (journal_entry.docstatus == 1)
)
)
if party_type == "Customer":
q = q.where(journal_acc.credit_in_account_currency > 0)
else:
q = q.where(journal_acc.debit_in_account_currency > 0)
reference_or_condition = []
if include_unallocated:
reference_or_condition.append(journal_acc.reference_name.isnull())
reference_or_condition.append(journal_acc.reference_name == "")
if order_list:
reference_or_condition.append(
(journal_acc.reference_type == order_doctype) & ((journal_acc.reference_name).isin(order_list))
)
if reference_or_condition:
q = q.where(Criterion.any(reference_or_condition))
q = q.orderby(journal_entry.posting_date)
return list(q.run(as_dict=True))
@erpnext.allow_regional
def get_advance_payment_entries_for_regional(*args, **kwargs):
return get_advance_payment_entries(*args, **kwargs)
def get_advance_payment_entries(
party_type: str,
party: str,
party_account: list,
order_doctype: str,
order_list: list | None = None,
default_advance_account: str | None = None,
include_unallocated: bool = True,
against_all_orders: bool = False,
limit: int | None = None,
condition: dict | None = None,
) -> list:
"""Return open advance payment entry rows matching the given party and orders."""
payment_entries = []
payment_entry = frappe.qb.DocType("Payment Entry")
if order_list or against_all_orders:
q = get_common_query(party_type, party, party_account, default_advance_account, limit, condition)
payment_ref = frappe.qb.DocType("Payment Entry Reference")
q = q.inner_join(payment_ref).on(payment_entry.name == payment_ref.parent)
q = q.select(
(payment_ref.allocated_amount).as_("amount"),
(payment_ref.name).as_("reference_row"),
(payment_ref.reference_name).as_("against_order"),
(payment_entry.book_advance_payments_in_separate_party_account),
)
q = q.where(payment_ref.reference_doctype == order_doctype)
if order_list:
q = q.where(payment_ref.reference_name.isin(order_list))
payment_entries += list(q.run(as_dict=True))
if include_unallocated:
q = get_common_query(party_type, party, party_account, default_advance_account, limit, condition)
q = q.select((payment_entry.unallocated_amount).as_("amount"))
q = q.where(payment_entry.unallocated_amount > 0)
payment_entries += list(q.run(as_dict=True))
return payment_entries
def get_common_query(
party_type: str,
party: str,
party_account: list,
default_advance_account: str | None,
limit: int | None,
condition: dict | None,
):
"""Build the base Payment Entry query shared by allocated and unallocated advance lookups."""
account_type = frappe.db.get_value("Party Type", party_type, "account_type")
payment_type = "Receive" if account_type == "Receivable" else "Pay"
payment_entry = frappe.qb.DocType("Payment Entry")
q = (
frappe.qb.from_(payment_entry)
.select(
ConstantColumn("Payment Entry").as_("reference_type"),
(payment_entry.name).as_("reference_name"),
payment_entry.posting_date,
(payment_entry.remarks).as_("remarks"),
(payment_entry.book_advance_payments_in_separate_party_account),
)
.where(payment_entry.payment_type == payment_type)
.where(payment_entry.party_type == party_type)
.where(payment_entry.party == party)
.where(payment_entry.docstatus == 1)
)
field = "paid_from" if payment_type == "Receive" else "paid_to"
q = q.select((payment_entry[f"{field}_account_currency"]).as_("currency"))
q = q.select(payment_entry[field])
account_condition = payment_entry[field].isin(party_account)
if default_advance_account:
q = q.where(
account_condition
| (
(payment_entry[field] == default_advance_account)
& (payment_entry.book_advance_payments_in_separate_party_account == 1)
)
)
else:
q = q.where(account_condition)
if payment_type == "Receive":
q = q.select((payment_entry.source_exchange_rate).as_("exchange_rate"))
else:
q = q.select((payment_entry.target_exchange_rate).as_("exchange_rate"))
if condition:
common_filter_conditions = []
common_filter_conditions.append(payment_entry.company == condition["company"])
if condition.get("name", None):
common_filter_conditions.append(payment_entry.name.like(f"%{condition.get('name')}%"))
if condition.get("from_payment_date"):
common_filter_conditions.append(payment_entry.posting_date.gte(condition["from_payment_date"]))
if condition.get("to_payment_date"):
common_filter_conditions.append(payment_entry.posting_date.lte(condition["to_payment_date"]))
if condition.get("get_payments") is True:
if condition.get("cost_center"):
common_filter_conditions.append(payment_entry.cost_center == condition["cost_center"])
if condition.get("accounting_dimensions"):
apply_strict_user_permissions = frappe.get_system_settings("apply_strict_user_permissions")
for field, val in condition.get("accounting_dimensions").items():
if isinstance(val, list | tuple | set):
value_condition = payment_entry[field].isin(val)
if apply_strict_user_permissions:
common_filter_conditions.append(value_condition)
else:
common_filter_conditions.append(
(IfNull(payment_entry[field], "") == "") | value_condition
)
else:
common_filter_conditions.append(payment_entry[field] == val)
if condition.get("minimum_payment_amount"):
common_filter_conditions.append(
payment_entry.unallocated_amount.gte(condition["minimum_payment_amount"])
)
if condition.get("maximum_payment_amount"):
common_filter_conditions.append(
payment_entry.unallocated_amount.lte(condition["maximum_payment_amount"])
)
q = q.where(Criterion.all(common_filter_conditions))
q = q.orderby(payment_entry.posting_date)
q = q.limit(limit) if limit else q
return q
def _set_je_amounts(entry, amount, default_amount=None, is_credit=True):
if is_credit:
entry.credit_in_account_currency = amount
if default_amount is not None:
entry.credit = default_amount
else:
entry.debit_in_account_currency = amount
if default_amount is not None:
entry.debit = default_amount