Skip to content

Commit

Permalink
fix: include description match in rank (alyf-de#132)
Browse files Browse the repository at this point in the history
  • Loading branch information
barredterra authored Nov 18, 2024
1 parent 5acb21d commit 623bbf7
Showing 1 changed file with 93 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Coalesce
from frappe.query_builder.functions import Coalesce, CustomFunction
from frappe.utils import cint, flt, sbool

from erpnext import get_company_currency, get_default_cost_center
Expand All @@ -17,9 +17,11 @@
get_total_allocated_amount,
)
from erpnext.accounts.utils import get_account_currency

from pypika import Order

MAX_QUERY_RESULTS = 150
Instr = CustomFunction("INSTR", ["a", "b"])
RegExpReplace = CustomFunction("REGEXP_REPLACE", ["a", "b", "c"])


class BankReconciliationToolBeta(Document):
Expand Down Expand Up @@ -488,6 +490,10 @@ def check_matching(

if transaction.description:
for voucher in matching_vouchers:
if "name_in_desc_match" in voucher:
# already covered in DB query
continue

# higher rank if voucher name is in bank transaction
reference_no = voucher["reference_no"]
if reference_no and (reference_no.strip() in transaction.description):
Expand Down Expand Up @@ -560,6 +566,7 @@ def get_matching_queries(
is_deposit = transaction.deposit > 0.0

common_filters.exact_party_match = "exact_party_match" in (document_types or [])
common_filters.description = transaction.description

if "payment_entry" in document_types:
frappe.has_permission("Payment Entry", throw=True)
Expand Down Expand Up @@ -660,11 +667,12 @@ def get_bt_matching_query(
)
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
amount_condition = amount_equality if exact_match else getattr(bt, field) > 0.0
rank_expression = ref_rank + amount_rank + party_rank + unallocated_rank + 1

query = (
frappe.qb.from_(bt)
.select(
(ref_rank + amount_rank + party_rank + unallocated_rank + 1).as_("rank"),
rank_expression.as_("rank"),
ConstantColumn("Bank Transaction").as_("doctype"),
bt.name,
bt.unallocated_amount.as_("paid_amount"),
Expand All @@ -684,6 +692,7 @@ def get_bt_matching_query(
.where(bt.bank_account == common_filters.bank_account)
.where(amount_condition)
.where(bt.docstatus == 1)
.orderby(rank_expression, order=Order.desc)
.limit(MAX_QUERY_RESULTS)
)

Expand All @@ -710,10 +719,12 @@ def get_ld_matching_query(exact_match: bool, common_filters: frappe._dict):
reference_rank = frappe.qb.terms.Case().when(matching_reference, 1).else_(0)
party_rank = frappe.qb.terms.Case().when(matching_party, 1).else_(0)

rank_expression = reference_rank + party_rank + date_rank + 1

query = (
frappe.qb.from_(loan_disbursement)
.select(
(reference_rank + party_rank + date_rank + 1).as_("rank"),
rank_expression.as_("rank"),
ConstantColumn("Loan Disbursement").as_("doctype"),
loan_disbursement.name,
loan_disbursement.disbursed_amount.as_("paid_amount"),
Expand All @@ -730,6 +741,7 @@ def get_ld_matching_query(exact_match: bool, common_filters: frappe._dict):
.where(loan_disbursement.docstatus == 1)
.where(loan_disbursement.clearance_date.isnull())
.where(loan_disbursement.disbursement_account == common_filters.bank_account)
.orderby(rank_expression, order=Order.desc)
.limit(MAX_QUERY_RESULTS)
)

Expand Down Expand Up @@ -758,10 +770,12 @@ def get_lr_matching_query(exact_match: bool, common_filters: frappe._dict):
reference_rank = frappe.qb.terms.Case().when(matching_reference, 1).else_(0)
party_rank = frappe.qb.terms.Case().when(matching_party, 1).else_(0)

rank_expression = reference_rank + party_rank + date_rank + 1

query = (
frappe.qb.from_(loan_repayment)
.select(
(reference_rank + party_rank + date_rank + 1).as_("rank"),
rank_expression.as_("rank"),
ConstantColumn("Loan Repayment").as_("doctype"),
loan_repayment.name,
loan_repayment.amount_paid.as_("paid_amount"),
Expand All @@ -778,6 +792,7 @@ def get_lr_matching_query(exact_match: bool, common_filters: frappe._dict):
.where(loan_repayment.docstatus == 1)
.where(loan_repayment.clearance_date.isnull())
.where(loan_repayment.payment_account == common_filters.bank_account)
.orderby(rank_expression, order=Order.desc)
.limit(MAX_QUERY_RESULTS)
)

Expand Down Expand Up @@ -828,10 +843,12 @@ def get_pe_matching_query(
date_condition = Coalesce(pe.reference_date, pe.posting_date) == common_filters.date
date_rank = frappe.qb.terms.Case().when(date_condition, 1).else_(0)

rank_expression = ref_rank + amount_rank + party_rank + date_rank + 1

query = (
frappe.qb.from_(pe)
.select(
(ref_rank + amount_rank + party_rank + date_rank + 1).as_("rank"),
rank_expression.as_("rank"),
ConstantColumn("Payment Entry").as_("doctype"),
pe.name,
pe.paid_amount,
Expand All @@ -853,7 +870,7 @@ def get_pe_matching_query(
.where(getattr(pe, account_from_to) == common_filters.bank_account)
.where(amount_condition)
.where(filter_by_date)
.orderby(pe.reference_date if cint(filter_by_reference_date) else pe.posting_date)
.orderby(rank_expression, order=Order.desc)
.limit(MAX_QUERY_RESULTS)
)

Expand Down Expand Up @@ -896,12 +913,14 @@ def get_je_matching_query(
date_condition = Coalesce(je.cheque_date, je.posting_date) == common_filters.date
date_rank = frappe.qb.terms.Case().when(date_condition, 1).else_(0)

rank_expression = ref_rank + amount_rank + date_rank + 1

query = (
frappe.qb.from_(jea)
.join(je)
.on(jea.parent == je.name)
.select(
(ref_rank + amount_rank + date_rank + 1).as_("rank"),
rank_expression.as_("rank"),
ConstantColumn("Journal Entry").as_("doctype"),
je.name,
getattr(jea, amount_field).as_("paid_amount"),
Expand All @@ -922,7 +941,7 @@ def get_je_matching_query(
.where(amount_equality if exact_match else getattr(jea, amount_field) > 0.0)
.where(je.docstatus == 1)
.where(filter_by_date)
.orderby(je.cheque_date if cint(filter_by_reference_date) else je.posting_date)
.orderby(rank_expression, order=Order.desc)
.limit(MAX_QUERY_RESULTS)
)

Expand Down Expand Up @@ -951,12 +970,18 @@ def get_si_matching_query(
date_condition = si.posting_date == common_filters.date
date_rank = frappe.qb.terms.Case().when(date_condition, 1).else_(0)

description_match = get_description_match_condition(
common_filters.description, si.name
)

rank_expression = party_rank + amount_rank + date_rank + description_match + 1

query = (
frappe.qb.from_(sip)
.join(si)
.on(sip.parent == si.name)
.select(
(party_rank + amount_rank + date_rank + 1).as_("rank"),
rank_expression.as_("rank"),
ConstantColumn("Sales Invoice").as_("doctype"),
si.name,
sip.amount.as_("paid_amount"),
Expand All @@ -969,12 +994,14 @@ def get_si_matching_query(
party_rank.as_("party_match"),
amount_rank.as_("amount_match"),
date_rank.as_("date_match"),
description_match.as_("name_in_desc_match"),
)
.where(si.docstatus == 1)
.where(sip.clearance_date.isnull())
.where(sip.account == common_filters.bank_account)
.where(amount_condition)
.where(si.currency == currency)
.orderby(rank_expression, order=Order.desc)
.limit(MAX_QUERY_RESULTS)
)

Expand All @@ -1000,11 +1027,15 @@ def get_unpaid_si_matching_query(
sales_invoice.outstanding_amount == common_filters.amount
)
amount_match = frappe.qb.terms.Case().when(outstanding_amount_condition, 1).else_(0)
description_match = get_description_match_condition(
common_filters.description, sales_invoice.name
)
rank_expression = party_match + amount_match + description_match + 1

query = (
frappe.qb.from_(sales_invoice)
.select(
(party_match + amount_match + 1).as_("rank"),
rank_expression.as_("rank"),
ConstantColumn("Sales Invoice").as_("doctype"),
sales_invoice.name.as_("name"),
sales_invoice.outstanding_amount.as_("paid_amount"),
Expand All @@ -1017,11 +1048,13 @@ def get_unpaid_si_matching_query(
sales_invoice.currency,
party_match.as_("party_match"),
amount_match.as_("amount_match"),
description_match.as_("name_in_desc_match"),
)
.where(sales_invoice.docstatus == 1)
.where(sales_invoice.company == company) # because we do not have bank account check
.where(sales_invoice.outstanding_amount != 0.0)
.where(sales_invoice.currency == currency)
.orderby(rank_expression, order=Order.desc)
.limit(MAX_QUERY_RESULTS)
)

Expand Down Expand Up @@ -1059,10 +1092,16 @@ def get_pi_matching_query(
)
date_rank = frappe.qb.terms.Case().when(date_condition, 1).else_(0)

description_match = get_description_match_condition(
common_filters.description, purchase_invoice.name
)

rank_expression = party_rank + amount_rank + date_rank + description_match + 1

query = (
frappe.qb.from_(purchase_invoice)
.select(
(party_rank + amount_rank + date_rank + 1).as_("rank"),
rank_expression.as_("rank"),
ConstantColumn("Purchase Invoice").as_("doctype"),
purchase_invoice.name,
purchase_invoice.paid_amount,
Expand All @@ -1076,13 +1115,15 @@ def get_pi_matching_query(
party_rank.as_("party_match"),
amount_rank.as_("amount_match"),
date_rank.as_("date_match"),
description_match.as_("name_in_desc_match"),
)
.where(purchase_invoice.docstatus == 1)
.where(purchase_invoice.is_paid == 1)
.where(purchase_invoice.clearance_date.isnull())
.where(purchase_invoice.cash_bank_account == common_filters.bank_account)
.where(amount_condition)
.where(purchase_invoice.currency == currency)
.orderby(rank_expression, order=Order.desc)
.limit(MAX_QUERY_RESULTS)
)

Expand All @@ -1108,13 +1149,17 @@ def get_unpaid_pi_matching_query(
purchase_invoice.outstanding_amount == common_filters.amount
)
amount_match = frappe.qb.terms.Case().when(outstanding_amount_condition, 1).else_(0)
description_match = get_description_match_condition(
common_filters.description, purchase_invoice.name
)
rank_expression = party_match + amount_match + description_match + 1

# We skip date rank as the date of an unpaid bill is mostly
# earlier than the date of the bank transaction
query = (
frappe.qb.from_(purchase_invoice)
.select(
(party_match + amount_match + 1).as_("rank"),
rank_expression.as_("rank"),
ConstantColumn("Purchase Invoice").as_("doctype"),
purchase_invoice.name.as_("name"),
purchase_invoice.outstanding_amount.as_("paid_amount"),
Expand All @@ -1127,12 +1172,14 @@ def get_unpaid_pi_matching_query(
purchase_invoice.currency,
party_match.as_("party_match"),
amount_match.as_("amount_match"),
description_match.as_("name_in_desc_match"),
)
.where(purchase_invoice.docstatus == 1)
.where(purchase_invoice.company == company)
.where(purchase_invoice.outstanding_amount != 0.0)
.where(purchase_invoice.is_paid == 0)
.where(purchase_invoice.currency == currency)
.orderby(rank_expression, order=Order.desc)
.limit(MAX_QUERY_RESULTS)
)

Expand All @@ -1147,7 +1194,10 @@ def get_unpaid_pi_matching_query(


def get_unpaid_ec_matching_query(
exact_match: bool, currency: str, common_filters: frappe._dict, company: str
exact_match: bool,
currency: str,
common_filters: frappe._dict,
company: str,
):
if currency != get_company_currency(company):
# Expense claims are always in company currency
Expand All @@ -1166,11 +1216,16 @@ def get_unpaid_ec_matching_query(
)
outstanding_amount_condition = outstanding_amount == common_filters.amount
amount_match = frappe.qb.terms.Case().when(outstanding_amount_condition, 1).else_(0)
description_match = get_description_match_condition(
common_filters.description, expense_claim.name
)

rank_expression = party_match + amount_match + description_match + 1

query = (
frappe.qb.from_(expense_claim)
.select(
(party_match + amount_match + 1).as_("rank"),
rank_expression.as_("rank"),
ConstantColumn("Expense Claim").as_("doctype"),
expense_claim.name.as_("name"),
outstanding_amount.as_("paid_amount"),
Expand All @@ -1183,11 +1238,13 @@ def get_unpaid_ec_matching_query(
ConstantColumn(currency).as_("currency"),
party_match.as_("party_match"),
amount_match.as_("amount_match"),
description_match.as_("name_in_desc_match"),
)
.where(expense_claim.docstatus == 1)
.where(expense_claim.company == company)
.where(outstanding_amount > 0.0)
.where(expense_claim.status == "Unpaid")
.orderby(rank_expression, order=Order.desc)
.limit(MAX_QUERY_RESULTS)
)

Expand Down Expand Up @@ -1225,3 +1282,24 @@ def get_invoice_function_map(document_types: list, is_deposit: bool):
for doctype in order
if (doctype in document_types and fn_map[doctype])
}


def get_description_match_condition(description: str, name_column):
"""Get the description match condition for a document name.
Args:
description: The bank transaction description to search in
name_column: The document name column to match against (e.g., expense_claim.name)
Returns:
A query condition that will be 1 if the description contains the document number
and 0 otherwise.
"""
return (
frappe.qb.terms.Case()
.when(
Instr(description or "", RegExpReplace(name_column, r"^[^0-9]*", "")) > 0,
1,
)
.else_(0)
)

0 comments on commit 623bbf7

Please sign in to comment.