Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: reconciled advance payment shows up in reconciliation tool under invoices #43396

Closed
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
22 changes: 17 additions & 5 deletions erpnext/accounts/doctype/payment_entry/payment_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -1236,12 +1236,14 @@ def set_remarks(self):

self.set("remarks", "\n".join(remarks))

def build_gl_map(self):
def build_gl_map(self, include_advance_doctype_reference=False):
if self.payment_type in ("Receive", "Pay") and not self.get("party_account_field"):
self.setup_party_account_field()

gl_entries = []
self.add_party_gl_entries(gl_entries)
self.add_party_gl_entries(
gl_entries, include_advance_doctype_reference=include_advance_doctype_reference
)
self.add_bank_gl_entries(gl_entries)
self.add_deductions_gl_entries(gl_entries)
self.add_tax_gl_entries(gl_entries)
Expand All @@ -1259,7 +1261,7 @@ def make_gl_entries(self, cancel=0, adv_adj=0):

self.make_advance_gl_entries(cancel=cancel)

def add_party_gl_entries(self, gl_entries):
def add_party_gl_entries(self, gl_entries, include_advance_doctype_reference=False):
if not self.party_account:
return

Expand Down Expand Up @@ -1308,11 +1310,21 @@ def add_party_gl_entries(self, gl_entries):
{
dr_or_cr: allocated_amount_in_company_currency,
dr_or_cr + "_in_account_currency": d.allocated_amount,
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
"cost_center": cost_center,
}
)

advance_payment_doctypes = frappe.get_hooks(
"advance_payment_receivable_doctypes"
) + frappe.get_hooks("advance_payment_payable_doctypes")
if include_advance_doctype_reference or d.reference_doctype not in advance_payment_doctypes:
gle.update(
{
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
}
)

gl_entries.append(gle)

if self.unallocated_amount:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def test_payment_entry_against_order(self):
pe.submit()

expected_gle = dict(
(d[0], d) for d in [["Debtors - _TC", 0, 1000, so.name], ["_Test Cash - _TC", 1000.0, 0, None]]
(d[0], d) for d in [["Debtors - _TC", 0, 1000, None], ["_Test Cash - _TC", 1000.0, 0, None]]
)

self.validate_gl_entries(pe.name, expected_gle)
Expand Down Expand Up @@ -90,8 +90,7 @@ def test_payment_against_sales_order_usd_to_inr(self):
self.assertEqual(pe.deductions, [])

expected_gle = dict(
(d[0], d)
for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], [pe.paid_to, 5500.0, 0, None]]
(d[0], d) for d in [["_Test Receivable USD - _TC", 0, 5500, None], [pe.paid_to, 5500.0, 0, None]]
)

self.validate_gl_entries(pe.name, expected_gle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,3 +363,54 @@ def test_05_unreconcile_order(self):
self.assertEqual(so.advance_paid, 0)
self.assertEqual(len(pe.references), 0)
self.assertEqual(pe.unallocated_amount, 100)

def test_06_unreconcile_advance_from_payment_entry(self):
self.enable_advance_as_liability()
so1 = self.create_sales_order()
so2 = self.create_sales_order()

pe = self.create_payment_entry()
# Allocation payment against Sales Order
pe.paid_amount = 260
pe.append(
"references",
{"reference_doctype": so1.doctype, "reference_name": so1.name, "allocated_amount": 150},
)
pe.append(
"references",
{"reference_doctype": so2.doctype, "reference_name": so2.name, "allocated_amount": 110},
)
pe.save().submit()

# Assert 'Advance Paid'
so1.reload()
self.assertEqual(so1.advance_paid, 150)
so2.reload()
self.assertEqual(so2.advance_paid, 110)

unreconcile = frappe.get_doc(
{
"doctype": "Unreconcile Payment",
"company": self.company,
"voucher_type": pe.doctype,
"voucher_no": pe.name,
}
)
unreconcile.add_references()
self.assertEqual(len(unreconcile.allocations), 2)
allocations = [(x.reference_name, x.allocated_amount) for x in unreconcile.allocations]
self.assertListEqual(allocations, [(so1.name, 150), (so2.name, 110)])
# unreconcile so2
unreconcile.remove(unreconcile.allocations[0])
unreconcile.save().submit()

# Assert 'Advance Paid'
so1.reload()
so2.reload()
pe.reload()
self.assertEqual(so1.advance_paid, 150)
self.assertEqual(so2.advance_paid, 0)
self.assertEqual(len(pe.references), 1)
self.assertEqual(pe.unallocated_amount, 110)

self.disable_advance_as_liability()
131 changes: 100 additions & 31 deletions erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ def validate(self):
@frappe.whitelist()
def get_allocations_from_payment(self):
allocated_references = []

advance_doctypes = frappe.get_hooks("advance_payment_payable_doctypes") + frappe.get_hooks(
"advance_payment_receivable_doctypes"
)

ple = qb.DocType("Payment Ledger Entry")
allocated_references = (
qb.from_(ple)
Expand All @@ -67,6 +72,18 @@ def get_allocations_from_payment(self):
.run(as_dict=True)
)

if self.voucher_type == "Payment Entry":
res = frappe.db.get_all(
"Payment Entry Reference",
filters={
"docstatus": 1,
"parent": self.voucher_no,
"reference_doctype": ["in", advance_doctypes],
},
fields=["reference_doctype", "reference_name", "allocated_amount"],
)
allocated_references += res

return allocated_references

def add_references(self):
Expand Down Expand Up @@ -100,29 +117,44 @@ def doc_has_references(doctype: str | None = None, docname: str | None = None):
filters={"delinked": 0, "against_voucher_no": docname, "amount": ["<", 0]},
)
else:
return frappe.db.count(
# For Backwards compatibility, check ledger as well.
# On Old PLE records, this can lead to double counting. Thats fine.
# As we are not looking for exact count
ledger_references = frappe.db.count(
"Payment Ledger Entry",
filters={"delinked": 0, "voucher_no": docname, "against_voucher_no": ["!=", docname]},
)

advance_doctypes = frappe.get_hooks("advance_payment_payable_doctypes") + frappe.get_hooks(
"advance_payment_receivable_doctypes"
)
pe_references = len(
frappe.db.get_all(
"Payment Entry Reference",
filters={
"docstatus": 1,
"parent": docname,
"reference_doctype": ["in", advance_doctypes],
},
)
)
return ledger_references + pe_references

@frappe.whitelist()
def get_linked_payments_for_doc(

def get_linked_payments_for_invoices(
company: str | None = None, doctype: str | None = None, docname: str | None = None
) -> list:
res = []
if company and doctype and docname:
_dt = doctype
_dn = docname
ple = qb.DocType("Payment Ledger Entry")
if _dt in ["Sales Invoice", "Purchase Invoice"]:
if doctype in ["Sales Invoice", "Purchase Invoice"]:
criteria = [
(ple.company == company),
(ple.delinked == 0),
(ple.against_voucher_no == _dn),
(ple.against_voucher_no == docname),
(ple.amount < 0),
]

res = (
res.extend(
qb.from_(ple)
.select(
ple.company,
Expand All @@ -136,30 +168,67 @@ def get_linked_payments_for_doc(
.having(qb.Field("allocated_amount") > 0)
.run(as_dict=True)
)
return res
else:
criteria = [
(ple.company == company),
(ple.delinked == 0),
(ple.voucher_no == _dn),
(ple.against_voucher_no != _dn),
]
return res

query = (
qb.from_(ple)
.select(
ple.company,
ple.against_voucher_type.as_("voucher_type"),
ple.against_voucher_no.as_("voucher_no"),
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
ple.account_currency,
)
.where(Criterion.all(criteria))
.groupby(ple.against_voucher_no)

def get_linked_docs_for_payments(
company: str | None = None, doctype: str | None = None, docname: str | None = None
) -> list:
res = []
if company and doctype and docname:
ple = qb.DocType("Payment Ledger Entry")
criteria = [
(ple.company == company),
(ple.delinked == 0),
(ple.voucher_no == docname),
(ple.against_voucher_no != docname),
]

query = (
qb.from_(ple)
.select(
ple.company,
ple.against_voucher_type.as_("voucher_type"),
ple.against_voucher_no.as_("voucher_no"),
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
ple.account_currency,
)
.where(Criterion.all(criteria))
.groupby(ple.against_voucher_no)
)
res.extend(query.run(as_dict=True))

# TODO: handle duplicates due to Old payments storing SO/PO references in Ledger
advance_doctypes = frappe.get_hooks("advance_payment_payable_doctypes") + frappe.get_hooks(
"advance_payment_receivable_doctypes"
)
pe = qb.DocType("Payment Entry Reference")
pe_references = (
qb.from_(pe)
.select(
pe.reference_doctype.as_("voucher_type"),
pe.reference_name.as_("voucher_no"),
pe.allocated_amount,
)
res = query.run(as_dict=True)
return res
return []
.where(pe.docstatus.eq(1) & pe.parent.eq(docname) & pe.reference_doctype.isin(advance_doctypes))
.run(as_dict=True)
)
res.extend(pe_references)
return res


@frappe.whitelist()
def get_linked_payments_for_doc(
company: str | None = None, doctype: str | None = None, docname: str | None = None
) -> list:
references = []
if company and doctype and docname:
get_linked_payments_for_invoices(company, doctype, docname)
if doctype in ["Sales Invoice", "Purchase Invoice"]:
references.extend(get_linked_payments_for_invoices(company, doctype, docname))
else:
references.extend(get_linked_docs_for_payments(company, doctype, docname))
return references


@frappe.whitelist()
Expand Down
30 changes: 30 additions & 0 deletions erpnext/accounts/test/accounts_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,22 @@ def create_company(self, company_name="_Test Company", abbr="_TC"):
"parent_account": "Bank Accounts - " + abbr,
}
),
frappe._dict(
{
"attribute_name": "advance_received",
"account_name": "Advance Received",
"parent_account": "Current Liabilities - " + abbr,
"account_type": "Receivable",
}
),
frappe._dict(
{
"attribute_name": "advance_paid",
"account_name": "Advance Paid",
"parent_account": "Current Assets - " + abbr,
"account_type": "Payable",
}
),
]
for acc in other_accounts:
acc_name = acc.account_name + " - " + abbr
Expand All @@ -107,11 +123,25 @@ def create_company(self, company_name="_Test Company", abbr="_TC"):
"company": self.company,
}
)
new_acc.account_type = acc.get("account_type", None)
new_acc.save()
setattr(self, acc.attribute_name, new_acc.name)

self.identify_default_warehouses()

def enable_advance_as_liability(self):
company = frappe.get_doc("Company", self.company)
company.book_advance_payments_in_separate_party_account = True
company.default_advance_received_account = self.advance_received
company.default_advance_paid_account = self.advance_paid
company.save()

def disable_advance_as_liability(self):
company = frappe.get_doc("Company", self.company)
company.book_advance_payments_in_separate_party_account = False
company.default_advance_paid_account = company.default_advance_received_account = None
company.save()

def identify_default_warehouses(self):
for w in frappe.db.get_all(
"Warehouse", filters={"company": self.company}, fields=["name", "warehouse_name"]
Expand Down
Loading
Loading