fix: multiple fixes for advance payment accounting (backport #48341) (#48896)

* fix: multiple fixes for advance payment accounting

(cherry picked from commit e70caedddc)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
#	erpnext/accounts/doctype/payment_entry/payment_entry.py
#	erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
#	erpnext/accounts/utils.py
#	erpnext/controllers/accounts_controller.py
#	erpnext/patches/v15_0/create_advance_payment_ledger_records.py

* chore: resolve conflicts

* fix: do not execute patch if no advance doctypes

---------

Co-authored-by: Lakshit Jain <108322669+ljain112@users.noreply.github.com>
Co-authored-by: ljain112 <ljain112@gmail.com>
This commit is contained in:
mergify[bot]
2025-08-01 15:30:30 +05:30
committed by GitHub
parent 5de5a8bfd5
commit cb0addc122
25 changed files with 650 additions and 362 deletions

View File

@@ -12,7 +12,8 @@
"against_voucher_no",
"amount",
"currency",
"event"
"event",
"delinked"
],
"fields": [
{
@@ -68,12 +69,20 @@
"label": "Company",
"options": "Company",
"read_only": 1
},
{
"default": "0",
"fieldname": "delinked",
"fieldtype": "Check",
"label": "DeLinked",
"read_only": 1
}
],
"grid_page_length": 50,
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-11-05 10:31:28.736671",
"modified": "2025-07-29 11:37:42.678556",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Payment Ledger Entry",
@@ -107,7 +116,8 @@
"share": 1
}
],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -1,9 +1,11 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
import frappe
from frappe.model.document import Document
from erpnext.accounts.utils import update_voucher_outstanding
class AdvancePaymentLedgerEntry(Document):
# begin: auto-generated types
@@ -19,9 +21,16 @@ class AdvancePaymentLedgerEntry(Document):
amount: DF.Currency
company: DF.Link | None
currency: DF.Link | None
delinked: DF.Check
event: DF.Data | None
voucher_no: DF.DynamicLink | None
voucher_type: DF.Link | None
# end: auto-generated types
pass
def on_update(self):
if (
self.against_voucher_type in ["Purchase Order", "Sales Order"]
and self.flags.update_outstanding == "Yes"
and not frappe.flags.is_reverse_depr_entry
):
update_voucher_outstanding(self.against_voucher_type, self.against_voucher_no, None, None, None)

View File

@@ -191,8 +191,6 @@ class JournalEntry(AccountsController):
self.validate_cheque_info()
self.check_credit_limit()
self.make_gl_entries()
self.make_advance_payment_ledger_entries()
self.update_advance_paid()
self.update_asset_value()
self.update_inter_company_jv()
self.update_invoice_discounting()
@@ -225,8 +223,6 @@ class JournalEntry(AccountsController):
"Advance Payment Ledger Entry",
)
self.make_gl_entries(1)
self.make_advance_payment_ledger_entries()
self.update_advance_paid()
self.unlink_advance_entry_reference()
self.unlink_asset_reference()
self.unlink_inter_company_jv()
@@ -237,18 +233,6 @@ class JournalEntry(AccountsController):
def get_title(self):
return self.pay_to_recd_from or self.accounts[0].account
def update_advance_paid(self):
advance_paid = frappe._dict()
advance_payment_doctypes = get_advance_payment_doctypes()
for d in self.get("accounts"):
if d.is_advance:
if d.reference_type in advance_payment_doctypes:
advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
for voucher_type, order_list in advance_paid.items():
for voucher_no in list(set(order_list)):
frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid()
def validate_inter_company_accounts(self):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
doc = frappe.db.get_value(
@@ -1094,49 +1078,65 @@ class JournalEntry(AccountsController):
self.transaction_exchange_rate = row.exchange_rate
break
advance_doctypes = get_advance_payment_doctypes()
for d in self.get("accounts"):
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
r = [d.user_remark, self.remark]
r = [x for x in r if x]
remarks = "\n".join(r)
row = {
"account": d.account,
"party_type": d.party_type,
"due_date": self.due_date,
"party": d.party,
"against": d.against_account,
"debit": flt(d.debit, d.precision("debit")),
"credit": flt(d.credit, d.precision("credit")),
"account_currency": d.account_currency,
"debit_in_account_currency": flt(
d.debit_in_account_currency, d.precision("debit_in_account_currency")
),
"credit_in_account_currency": flt(
d.credit_in_account_currency, d.precision("credit_in_account_currency")
),
"transaction_currency": self.transaction_currency,
"transaction_exchange_rate": self.transaction_exchange_rate,
"debit_in_transaction_currency": flt(
d.debit_in_account_currency, d.precision("debit_in_account_currency")
)
if self.transaction_currency == d.account_currency
else flt(d.debit, d.precision("debit")) / self.transaction_exchange_rate,
"credit_in_transaction_currency": flt(
d.credit_in_account_currency, d.precision("credit_in_account_currency")
)
if self.transaction_currency == d.account_currency
else flt(d.credit, d.precision("credit")) / self.transaction_exchange_rate,
"against_voucher_type": d.reference_type,
"against_voucher": d.reference_name,
"remarks": remarks,
"voucher_detail_no": d.reference_detail_no,
"cost_center": d.cost_center,
"project": d.project,
"finance_book": self.finance_book,
"advance_voucher_type": d.advance_voucher_type,
"advance_voucher_no": d.advance_voucher_no,
}
if d.reference_type in advance_doctypes:
row.update(
{
"against_voucher_type": self.doctype,
"against_voucher": self.name,
"advance_voucher_type": d.reference_type,
"advance_voucher_no": d.reference_name,
}
)
gl_map.append(
self.get_gl_dict(
{
"account": d.account,
"party_type": d.party_type,
"due_date": self.due_date,
"party": d.party,
"against": d.against_account,
"debit": flt(d.debit, d.precision("debit")),
"credit": flt(d.credit, d.precision("credit")),
"account_currency": d.account_currency,
"debit_in_account_currency": flt(
d.debit_in_account_currency, d.precision("debit_in_account_currency")
),
"credit_in_account_currency": flt(
d.credit_in_account_currency, d.precision("credit_in_account_currency")
),
"transaction_currency": self.transaction_currency,
"transaction_exchange_rate": self.transaction_exchange_rate,
"debit_in_transaction_currency": flt(
d.debit_in_account_currency, d.precision("debit_in_account_currency")
)
if self.transaction_currency == d.account_currency
else flt(d.debit, d.precision("debit")) / self.transaction_exchange_rate,
"credit_in_transaction_currency": flt(
d.credit_in_account_currency, d.precision("credit_in_account_currency")
)
if self.transaction_currency == d.account_currency
else flt(d.credit, d.precision("credit")) / self.transaction_exchange_rate,
"against_voucher_type": d.reference_type,
"against_voucher": d.reference_name,
"remarks": remarks,
"voucher_detail_no": d.reference_detail_no,
"cost_center": d.cost_center,
"project": d.project,
"finance_book": self.finance_book,
},
row,
item=d,
)
)

View File

@@ -32,6 +32,8 @@
"reference_name",
"reference_due_date",
"reference_detail_no",
"advance_voucher_type",
"advance_voucher_no",
"col_break3",
"is_advance",
"user_remark",
@@ -263,12 +265,28 @@
"label": "Reference Detail No",
"no_copy": 1,
"search_index": 1
},
{
"fieldname": "advance_voucher_type",
"fieldtype": "Link",
"label": "Advance Voucher Type",
"no_copy": 1,
"options": "DocType",
"read_only": 1
},
{
"fieldname": "advance_voucher_no",
"fieldtype": "Dynamic Link",
"label": "Advance Voucher No",
"no_copy": 1,
"options": "advance_voucher_type",
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-02-05 01:10:50.224840",
"modified": "2025-07-25 04:45:28.117715",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",
@@ -279,4 +297,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -17,8 +17,9 @@ class JournalEntryAccount(Document):
account: DF.Link
account_currency: DF.Link | None
account_type: DF.Data | None
advance_voucher_no: DF.DynamicLink | None
advance_voucher_type: DF.Link | None
against_account: DF.Text | None
balance: DF.Currency
bank_account: DF.Link | None
cost_center: DF.Link | None
credit: DF.Currency
@@ -31,7 +32,6 @@ class JournalEntryAccount(Document):
parentfield: DF.Data
parenttype: DF.Data
party: DF.DynamicLink | None
party_balance: DF.Currency
party_type: DF.Link | None
project: DF.Link | None
reference_detail_no: DF.Data | None

View File

@@ -117,12 +117,10 @@ class PaymentEntry(AccountsController):
def on_submit(self):
if self.difference_amount:
frappe.throw(_("Difference Amount must be zero"))
self.update_payment_requests()
self.make_gl_entries()
self.update_outstanding_amounts()
self.update_payment_schedule()
self.update_payment_requests()
self.make_advance_payment_ledger_entries()
self.update_advance_paid() # advance_paid_status depends on the payment request amount
self.set_status()
def validate_for_repost(self):
@@ -222,13 +220,11 @@ class PaymentEntry(AccountsController):
"Advance Payment Ledger Entry",
)
super().on_cancel()
self.update_payment_requests(cancel=True)
self.make_gl_entries(cancel=1)
self.update_outstanding_amounts()
self.delink_advance_entry_references()
self.update_payment_schedule(cancel=1)
self.update_payment_requests(cancel=True)
self.make_advance_payment_ledger_entries()
self.update_advance_paid() # advance_paid_status depends on the payment request amount
self.set_status()
def update_payment_requests(self, cancel=False):
@@ -1366,23 +1362,27 @@ class PaymentEntry(AccountsController):
dr_or_cr + "_in_transaction_currency": d.allocated_amount
if self.transaction_currency == self.party_account_currency
else allocated_amount_in_company_currency / self.transaction_exchange_rate,
"advance_voucher_type": d.advance_voucher_type,
"advance_voucher_no": d.advance_voucher_no,
},
item=self,
)
)
if self.book_advance_payments_in_separate_party_account:
if d.reference_doctype in advance_payment_doctypes:
# Upon reconciliation, whole ledger will be reposted. So, reference to SO/PO is fine
gle.update(
{
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
}
)
else:
# Do not reference Invoices while Advance is in separate party account
gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name})
if d.reference_doctype in advance_payment_doctypes:
# advance reference
gle.update(
{
"against_voucher_type": self.doctype,
"against_voucher": self.name,
"advance_voucher_type": d.reference_doctype,
"advance_voucher_no": d.reference_name,
}
)
elif self.book_advance_payments_in_separate_party_account:
# Do not reference Invoices while Advance is in separate party account
gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name})
else:
gle.update(
{
@@ -1512,6 +1512,8 @@ class PaymentEntry(AccountsController):
{
"against_voucher_type": invoice.reference_doctype,
"against_voucher": invoice.reference_name,
"advance_voucher_type": invoice.advance_voucher_type,
"advance_voucher_no": invoice.advance_voucher_no,
"posting_date": posting_date,
}
)
@@ -1536,6 +1538,8 @@ class PaymentEntry(AccountsController):
{
"against_voucher_type": "Payment Entry",
"against_voucher": self.name,
"advance_voucher_type": invoice.advance_voucher_type,
"advance_voucher_no": invoice.advance_voucher_no,
}
)
gle = self.get_gl_dict(
@@ -1684,17 +1688,6 @@ class PaymentEntry(AccountsController):
return flt(gl_dict.get(field, 0) / (conversion_rate or 1))
def update_advance_paid(self):
if self.payment_type not in ("Receive", "Pay") or not self.party:
return
advance_payment_doctypes = get_advance_payment_doctypes()
for d in self.get("references"):
if d.allocated_amount and d.reference_doctype in advance_payment_doctypes:
frappe.get_doc(
d.reference_doctype, d.reference_name, for_update=True
).set_total_advance_paid()
def on_recurring(self, reference_doc, auto_repeat_doc):
self.reference_no = reference_doc.name
self.reference_date = nowdate()

View File

@@ -52,7 +52,7 @@ class TestPaymentEntry(FrappeTestCase):
self.assertEqual(pe.paid_to_account_type, "Cash")
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, pe.name], ["_Test Cash - _TC", 1000.0, 0, None]]
)
self.validate_gl_entries(pe.name, expected_gle)
@@ -84,7 +84,7 @@ class TestPaymentEntry(FrappeTestCase):
expected_gle = dict(
(d[0], d)
for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], [pe.paid_to, 5500.0, 0, None]]
for d in [["_Test Receivable USD - _TC", 0, 5500, pe.name], [pe.paid_to, 5500.0, 0, None]]
)
self.validate_gl_entries(pe.name, expected_gle)

View File

@@ -22,7 +22,9 @@
"exchange_gain_loss",
"account",
"payment_request",
"payment_request_outstanding"
"payment_request_outstanding",
"advance_voucher_type",
"advance_voucher_no"
],
"fields": [
{
@@ -151,12 +153,28 @@
"fieldtype": "Date",
"label": "Reconcile Effect On",
"read_only": 1
},
{
"columns": 2,
"fieldname": "advance_voucher_type",
"fieldtype": "Link",
"label": "Advance Voucher Type",
"options": "DocType",
"read_only": 1
},
{
"columns": 2,
"fieldname": "advance_voucher_no",
"fieldtype": "Dynamic Link",
"label": "Advance Voucher No",
"options": "advance_voucher_type",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-01-13 15:56:18.895082",
"modified": "2025-07-25 04:32:11.040025",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
@@ -167,4 +185,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -16,6 +16,8 @@ class PaymentEntryReference(Document):
account: DF.Link | None
account_type: DF.Data | None
advance_voucher_no: DF.DynamicLink | None
advance_voucher_type: DF.Link | None
allocated_amount: DF.Float
bill_no: DF.Data | None
due_date: DF.Date | None
@@ -26,7 +28,6 @@ class PaymentEntryReference(Document):
parentfield: DF.Data
parenttype: DF.Data
payment_request: DF.Link | None
payment_request_outstanding: DF.Float
payment_term: DF.Link | None
payment_term_outstanding: DF.Float
payment_type: DF.Data | None

View File

@@ -197,4 +197,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -2206,6 +2206,136 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(len(pr.get("payments")), 0)
self.assertEqual(pr.get("invoices")[0].get("outstanding_amount"), 200)
def test_partial_advance_payment_with_closed_fiscal_year(self):
"""
Test Advance Payment partial reconciliation before period closing and partial after period closing
"""
default_settings = frappe.db.get_value(
"Company",
self.company,
[
"book_advance_payments_in_separate_party_account",
"default_advance_paid_account",
"reconciliation_takes_effect_on",
],
as_dict=True,
)
first_fy_start_date = frappe.db.get_value("Fiscal Year", {"disabled": 0}, "min(year_start_date)")
prev_fy_start_date = add_years(first_fy_start_date, -1)
prev_fy_end_date = add_days(first_fy_start_date, -1)
create_fiscal_year(
company=self.company, year_start_date=prev_fy_start_date, year_end_date=prev_fy_end_date
)
frappe.db.set_value(
"Company",
self.company,
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_paid_account": self.advance_payable_account,
"reconciliation_takes_effect_on": "Oldest Of Invoice Or Advance",
},
)
self.supplier = "_Test Supplier"
# Create advance payment of 1000 (previous FY)
pe = self.create_payment_entry(amount=1000, posting_date=prev_fy_start_date)
pe.party_type = "Supplier"
pe.party = self.supplier
pe.payment_type = "Pay"
pe.paid_from = self.cash
pe.paid_to = self.advance_payable_account
pe.save().submit()
# Create purchase invoice of 600 (previous FY)
pi1 = self.create_purchase_invoice(qty=1, rate=600, do_not_submit=True)
pi1.posting_date = prev_fy_start_date
pi1.set_posting_time = 1
pi1.supplier = self.supplier
pi1.credit_to = self.creditors
pi1.save().submit()
# Reconcile advance payment
pr = self.create_payment_reconciliation(party_is_customer=False)
pr.party = self.supplier
pr.receivable_payable_account = self.creditors
pr.default_advance_account = self.advance_payable_account
pr.from_invoice_date = pr.to_invoice_date = pi1.posting_date
pr.from_payment_date = pr.to_payment_date = pe.posting_date
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.invoices if x.invoice_number == pi1.name]
payments = [x.as_dict() for x in pr.payments if x.reference_name == pe.name]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
# Verify partial reconciliation
pe.reload()
pi1.reload()
self.assertEqual(len(pe.references), 1)
self.assertEqual(pe.references[0].allocated_amount, 600)
self.assertEqual(flt(pe.unallocated_amount), 400)
self.assertEqual(pi1.outstanding_amount, 0)
self.assertEqual(pi1.status, "Paid")
# Close accounting period for March (previous FY)
pcv = make_period_closing_voucher(
company=self.company, cost_center=self.cost_center, posting_date=prev_fy_end_date
)
pcv.reload()
self.assertEqual(pcv.gle_processing_status, "Completed")
# Change reconciliation setting to "Reconciliation Date"
frappe.db.set_value(
"Company",
self.company,
"reconciliation_takes_effect_on",
"Reconciliation Date",
)
# Create new purchase invoice for 400 in new fiscal year
pi2 = self.create_purchase_invoice(qty=1, rate=400, do_not_submit=True)
pi2.posting_date = today()
pi2.set_posting_time = 1
pi2.supplier = self.supplier
pi2.currency = "INR"
pi2.credit_to = self.creditors
pi2.save()
pi2.submit()
# Allocate 600 from advance payment to purchase invoice
pr = self.create_payment_reconciliation(party_is_customer=False)
pr.party = self.supplier
pr.receivable_payable_account = self.creditors
pr.default_advance_account = self.advance_payable_account
pr.from_invoice_date = pr.to_invoice_date = pi2.posting_date
pr.from_payment_date = pr.to_payment_date = pe.posting_date
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.invoices if x.invoice_number == pi2.name]
payments = [x.as_dict() for x in pr.payments if x.reference_name == pe.name]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
pe.reload()
pi2.reload()
# Assert advance payment is fully allocated
self.assertEqual(len(pe.references), 2)
self.assertEqual(flt(pe.unallocated_amount), 0)
# Assert new invoice is fully paid
self.assertEqual(pi2.outstanding_amount, 0)
self.assertEqual(pi2.status, "Paid")
# Verify reconciliation dates are correct based on company setting
self.assertEqual(getdate(pe.references[0].reconcile_effect_on), getdate(pi1.posting_date))
self.assertEqual(getdate(pe.references[1].reconcile_effect_on), getdate(pi2.posting_date))
frappe.db.set_value("Company", self.company, default_settings)
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):

View File

@@ -328,7 +328,7 @@ class TestPaymentRequest(FrappeTestCase):
self.assertEqual(pe.paid_amount, 800) # paid amount set from pr's outstanding amount
self.assertEqual(pe.references[0].allocated_amount, 800)
self.assertEqual(pe.references[0].outstanding_amount, 800) # for Orders it is not zero
self.assertEqual(pe.references[0].outstanding_amount, 0) # Also for orders it will zero
self.assertEqual(pe.references[0].payment_request, pr.name)
so.load_from_db()

View File

@@ -169,6 +169,10 @@ def start_repost(account_repost_doc=str) -> None:
frappe.db.delete(
"Payment Ledger Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name}
)
frappe.db.delete(
"Advance Payment Ledger Entry",
filters={"voucher_type": doc.doctype, "voucher_no": doc.name},
)
if doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
if not repost_doc.delete_cancelled_entries:

View File

@@ -8,7 +8,7 @@ from frappe import _, qb
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from erpnext.accounts.utils import _delete_pl_entries, create_payment_ledger_entry
from erpnext.accounts.utils import _delete_adv_pl_entries, _delete_pl_entries, create_payment_ledger_entry
VOUCHER_TYPES = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
@@ -16,6 +16,7 @@ VOUCHER_TYPES = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal
def repost_ple_for_voucher(voucher_type, voucher_no, gle_map=None):
if voucher_type and voucher_no and gle_map:
_delete_pl_entries(voucher_type, voucher_no)
_delete_adv_pl_entries(voucher_type, voucher_no)
create_payment_ledger_entry(gle_map, cancel=0)

View File

@@ -465,10 +465,26 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
self.assertEqual(len(pr.get("invoices")), 0)
self.assertEqual(len(pr.get("payments")), 0)
# Assert 'Advance Paid'
so.reload()
self.assertEqual(so.advance_paid, 1000)
unreconcile = frappe.get_doc(
{
"doctype": "Unreconcile Payment",
"company": self.company,
"voucher_type": pe.doctype,
"voucher_no": pe.name,
}
)
unreconcile.add_references()
unreconcile.allocations = [x for x in unreconcile.allocations if x.reference_name == si.name]
unreconcile.save().submit()
# after unreconcilaition advance paid will be reduced
# Assert 'Advance Paid'
so.reload()
self.assertEqual(so.advance_paid, 0)
self.disable_advance_as_liability()
def test_unreconcile_advance_from_journal_entry(self):

View File

@@ -12,7 +12,6 @@ from frappe.utils.data import comma_and
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
get_advance_payment_doctypes,
unlink_ref_doc_from_payment_entries,
update_voucher_outstanding,
)
@@ -45,31 +44,12 @@ class UnreconcilePayment(Document):
@frappe.whitelist()
def get_allocations_from_payment(self):
allocated_references = []
ple = qb.DocType("Payment Ledger Entry")
allocated_references = (
qb.from_(ple)
.select(
ple.account,
ple.party_type,
ple.party,
ple.against_voucher_type.as_("reference_doctype"),
ple.against_voucher_no.as_("reference_name"),
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
ple.account_currency,
)
.where(
(ple.docstatus == 1)
& (ple.voucher_type == self.voucher_type)
& (ple.voucher_no == self.voucher_no)
& (ple.voucher_no != ple.against_voucher_no)
)
.groupby(ple.against_voucher_type, ple.against_voucher_no)
.run(as_dict=True)
return get_linked_payments_for_doc(
company=self.company,
doctype=self.voucher_type,
docname=self.voucher_no,
)
return allocated_references
def add_references(self):
allocations = self.get_allocations_from_payment()
@@ -82,42 +62,43 @@ class UnreconcilePayment(Document):
doc = frappe.get_doc(alloc.reference_doctype, alloc.reference_name)
unlink_ref_doc_from_payment_entries(doc, self.voucher_no)
cancel_exchange_gain_loss_journal(doc, self.voucher_type, self.voucher_no)
# update outstanding amounts
update_voucher_outstanding(
alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party
alloc.reference_doctype,
alloc.reference_name,
alloc.account,
alloc.party_type,
alloc.party,
)
if doc.doctype in get_advance_payment_doctypes():
self.make_advance_payment_ledger(alloc)
doc.set_total_advance_paid()
frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True)
def make_advance_payment_ledger(self, alloc):
if alloc.allocated_amount > 0:
doc = frappe.new_doc("Advance Payment Ledger Entry")
doc.company = self.company
doc.voucher_type = self.voucher_type
doc.voucher_no = self.voucher_no
doc.against_voucher_type = alloc.reference_doctype
doc.against_voucher_no = alloc.reference_name
doc.amount = -1 * alloc.allocated_amount
doc.event = "Unreconcile"
doc.currency = alloc.account_currency
doc.flags.ignore_permissions = 1
doc.save()
@frappe.whitelist()
def doc_has_references(doctype: str | None = None, docname: str | None = None):
count = 0
if doctype in ["Sales Invoice", "Purchase Invoice"]:
return frappe.db.count(
count = frappe.db.count(
"Payment Ledger Entry",
filters={"delinked": 0, "against_voucher_no": docname, "amount": ["<", 0]},
)
else:
return frappe.db.count(
count = frappe.db.count(
"Payment Ledger Entry",
filters={"delinked": 0, "voucher_no": docname, "against_voucher_no": ["!=", docname]},
)
count += frappe.db.count(
"Advance Payment Ledger Entry",
filters={
"delinked": 0,
"voucher_no": docname,
"voucher_type": doctype,
"event": ["=", "Submit"],
},
)
return count
@frappe.whitelist()
@@ -139,9 +120,12 @@ def get_linked_payments_for_doc(
res = (
qb.from_(ple)
.select(
ple.account,
ple.party_type,
ple.party,
ple.company,
ple.voucher_type,
ple.voucher_no,
ple.voucher_type.as_("reference_doctype"),
ple.voucher_no.as_("reference_name"),
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
ple.account_currency,
)
@@ -163,19 +147,52 @@ def get_linked_payments_for_doc(
qb.from_(ple)
.select(
ple.company,
ple.against_voucher_type.as_("voucher_type"),
ple.against_voucher_no.as_("voucher_no"),
ple.account,
ple.party_type,
ple.party,
ple.against_voucher_type.as_("reference_doctype"),
ple.against_voucher_no.as_("reference_name"),
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
ple.account_currency,
)
.where(Criterion.all(criteria))
.groupby(ple.against_voucher_no)
)
res = query.run(as_dict=True)
res += get_linked_advances(company, _dn)
return res
return []
def get_linked_advances(company, docname):
adv = qb.DocType("Advance Payment Ledger Entry")
criteria = [
(adv.company == company),
(adv.delinked == 0),
(adv.voucher_no == docname),
(adv.event == "Submit"),
]
return (
qb.from_(adv)
.select(
adv.company,
adv.against_voucher_type.as_("reference_doctype"),
adv.against_voucher_no.as_("reference_name"),
Abs(Sum(adv.amount)).as_("allocated_amount"),
adv.currency,
)
.where(Criterion.all(criteria))
.having(qb.Field("allocated_amount") > 0)
.groupby(adv.against_voucher_no)
.run(as_dict=True)
)
@frappe.whitelist()
def create_unreconcile_doc_for_selection(selections=None):
if selections:

View File

@@ -305,6 +305,8 @@ def get_merge_properties(dimensions=None):
"project",
"finance_book",
"voucher_no",
"advance_voucher_type",
"advance_voucher_no",
]
if dimensions:
merge_properties.extend(dimensions)

View File

@@ -471,42 +471,27 @@ def reconcile_against_document(
reconciled_entries[(row.voucher_type, row.voucher_no)].append(row)
for key, entries in reconciled_entries.items():
voucher_type = key[0]
voucher_no = key[1]
voucher_type, voucher_no = key
# cancel advance entry
doc = frappe.get_doc(voucher_type, voucher_no)
frappe.flags.ignore_party_validation = True
# When Advance is allocated from an Order to an Invoice
# whole ledger must be reposted
repost_whole_ledger = any([x.voucher_detail_no for x in entries])
if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account:
if repost_whole_ledger:
doc.make_gl_entries(cancel=1)
else:
doc.make_advance_gl_entries(cancel=1)
else:
_delete_pl_entries(voucher_type, voucher_no)
reposting_rows = []
for entry in entries:
check_if_advance_entry_modified(entry)
validate_allocated_amount(entry)
dimensions_dict = _build_dimensions_dict_for_exc_gain_loss(entry, active_dimensions)
# update ref in advance entry
if voucher_type == "Journal Entry":
referenced_row, update_advance_paid = update_reference_in_journal_entry(
entry, doc, do_not_save=False
)
referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False)
# advance section in sales/purchase invoice and reconciliation tool,both pass on exchange gain/loss
# amount and account in args
# referenced_row is used to deduplicate gain/loss journal
entry.update({"referenced_row": referenced_row})
entry.update({"referenced_row": referenced_row.name})
doc.make_exchange_gain_loss_journal([entry], dimensions_dict)
else:
referenced_row, update_advance_paid = update_reference_in_payment_entry(
referenced_row = update_reference_in_payment_entry(
entry,
doc,
do_not_save=True,
@@ -515,20 +500,16 @@ def reconcile_against_document(
)
if referenced_row.get("outstanding_amount"):
referenced_row.outstanding_amount -= flt(entry.allocated_amount)
reposting_rows.append(referenced_row)
doc.save(ignore_permissions=True)
# re-submit advance entry
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account:
# When Advance is allocated from an Order to an Invoice
# whole ledger must be reposted
if repost_whole_ledger:
doc.make_gl_entries()
else:
# both ledgers must be posted to for `Advance` in separate account feature
# TODO: find a more efficient way post only for the new linked vouchers
doc.make_advance_gl_entries()
for row in reposting_rows:
doc.make_advance_gl_entries(entry=row)
else:
_delete_pl_entries(voucher_type, voucher_no)
gl_map = doc.build_gl_map()
# Make sure there is no overallocation
from erpnext.accounts.general_ledger import process_debit_credit_difference
@@ -545,11 +526,6 @@ def reconcile_against_document(
entry.party_type,
entry.party,
)
# update advance paid in Advance Receivable/Payable doctypes
if update_advance_paid:
for t, n in update_advance_paid:
frappe.get_doc(t, n).set_total_advance_paid()
frappe.flags.ignore_party_validation = False
@@ -630,12 +606,6 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
"""
jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
# Update Advance Paid in SO/PO since they might be getting unlinked
update_advance_paid = []
if jv_detail.get("reference_type") in ["Sales Order", "Purchase Order"]:
update_advance_paid.append((jv_detail.reference_type, jv_detail.reference_name))
rev_dr_or_cr = (
"debit_in_account_currency"
if d["dr_or_cr"] == "credit_in_account_currency"
@@ -688,6 +658,10 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
new_row.is_advance = cstr(jv_detail.is_advance)
new_row.docstatus = 1
if jv_detail.get("reference_type") in get_advance_payment_doctypes():
new_row.advance_voucher_type = jv_detail.get("reference_type")
new_row.advance_voucher_no = jv_detail.get("reference_name")
# will work as update after submit
journal_entry.flags.ignore_validate_update_after_submit = True
# Ledgers will be reposted by Reconciliation tool
@@ -695,7 +669,7 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
if not do_not_save:
journal_entry.save(ignore_permissions=True)
return new_row.name, update_advance_paid
return new_row
def update_reference_in_payment_entry(
@@ -714,7 +688,8 @@ def update_reference_in_payment_entry(
"account": d.account,
"dimensions": d.dimensions,
}
update_advance_paid = []
advance_payment_doctypes = get_advance_payment_doctypes()
# Update Reconciliation effect date in reference
if payment_entry.book_advance_payments_in_separate_party_account:
@@ -726,10 +701,6 @@ def update_reference_in_payment_entry(
if d.voucher_detail_no:
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
# Update Advance Paid in SO/PO since they are getting unlinked
if existing_row.get("reference_doctype") in ["Sales Order", "Purchase Order"]:
update_advance_paid.append((existing_row.reference_doctype, existing_row.reference_name))
if d.allocated_amount <= existing_row.allocated_amount:
existing_row.allocated_amount -= d.allocated_amount
@@ -737,7 +708,13 @@ def update_reference_in_payment_entry(
new_row.docstatus = 1
for field in list(reference_details):
new_row.set(field, reference_details[field])
if existing_row.reference_doctype in advance_payment_doctypes:
new_row.advance_voucher_type = existing_row.reference_doctype
new_row.advance_voucher_no = existing_row.reference_name
row = new_row
else:
new_row = payment_entry.append("references")
new_row.docstatus = 1
@@ -771,7 +748,8 @@ def update_reference_in_payment_entry(
payment_entry.flags.ignore_reposting_on_reconciliation = True
if not do_not_save:
payment_entry.save(ignore_permissions=True)
return row, update_advance_paid
return row
def get_reconciliation_effect_date(against_voucher_type, against_voucher, company, posting_date):
@@ -949,6 +927,24 @@ def update_accounting_ledgers_after_reference_removal(
ple_update_query = ple_update_query.where(ple.voucher_no == payment_name)
ple_update_query.run()
# Advance Payment
adv = qb.DocType("Advance Payment Ledger Entry")
adv_ple = (
qb.update(adv)
.set(adv.delinked, 1)
.set(adv.modified, now())
.set(adv.modified_by, frappe.session.user)
.where(adv.delinked == 0)
.where(
((adv.against_voucher_type == ref_type) & (adv.against_voucher_no == ref_no))
| ((adv.voucher_type == ref_type) & (adv.voucher_no == ref_no))
)
)
if payment_name:
adv_ple = adv_ple.where(adv.voucher_no == payment_name)
adv_ple.run()
def remove_ref_from_advance_section(ref_doc: object = None):
# TODO: this might need some testing
@@ -985,6 +981,8 @@ def remove_ref_doc_link_from_jv(
qb.update(jea)
.set(jea.reference_type, None)
.set(jea.reference_name, None)
.set(jea.advance_voucher_type, None)
.set(jea.advance_voucher_no, None)
.set(jea.modified, now())
.set(jea.modified_by, frappe.session.user)
.where((jea.reference_type == ref_type) & (jea.reference_name == ref_no))
@@ -1495,6 +1493,11 @@ def _delete_pl_entries(voucher_type, voucher_no):
qb.from_(ple).delete().where((ple.voucher_type == voucher_type) & (ple.voucher_no == voucher_no)).run()
def _delete_adv_pl_entries(voucher_type, voucher_no):
adv = qb.DocType("Advance Payment Ledger Entry")
qb.from_(adv).delete().where((adv.voucher_type == voucher_type) & (adv.voucher_no == voucher_no)).run()
def _delete_gl_entries(voucher_type, voucher_no):
gle = qb.DocType("GL Entry")
qb.from_(gle).delete().where((gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no)).run()
@@ -1814,6 +1817,11 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
dr_or_cr *= -1
dr_or_cr_account_currency *= -1
against_voucher_type = (
gle.against_voucher_type if gle.against_voucher_type else gle.voucher_type
)
against_voucher_no = gle.against_voucher if gle.against_voucher else gle.voucher_no
ple = frappe._dict(
doctype="Payment Ledger Entry",
posting_date=gle.posting_date,
@@ -1828,14 +1836,12 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
voucher_type=gle.voucher_type,
voucher_no=gle.voucher_no,
voucher_detail_no=gle.voucher_detail_no,
against_voucher_type=gle.against_voucher_type
if gle.against_voucher_type
else gle.voucher_type,
against_voucher_no=gle.against_voucher if gle.against_voucher else gle.voucher_no,
against_voucher_type=against_voucher_type,
against_voucher_no=against_voucher_no,
account_currency=gle.account_currency,
amount=dr_or_cr,
amount_in_account_currency=dr_or_cr_account_currency,
delinked=True if cancel else False,
delinked=cancel,
remarks=gle.remarks,
)
@@ -1844,10 +1850,40 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
for dimension in dimensions_and_defaults[0]:
ple[dimension.fieldname] = gle.get(dimension.fieldname)
if gle.advance_voucher_no:
# create advance entry
adv = get_advance_ledger_entry(
gle, against_voucher_type, against_voucher_no, dr_or_cr_account_currency, cancel
)
ple_map.append(adv)
ple_map.append(ple)
return ple_map
def get_advance_ledger_entry(gle, against_voucher_type, against_voucher_no, amount, cancel):
event = (
"Submit"
if (against_voucher_type == gle.voucher_type and against_voucher_no == gle.voucher_no)
else "Adjustment"
)
return frappe._dict(
doctype="Advance Payment Ledger Entry",
company=gle.company,
voucher_type=gle.voucher_type,
voucher_no=gle.voucher_no,
voucher_detail_no=gle.voucher_detail_no,
against_voucher_type=gle.advance_voucher_type,
against_voucher_no=gle.advance_voucher_no,
amount=amount,
currency=gle.account_currency,
event=event,
delinked=cancel,
)
def create_payment_ledger_entry(
gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0, partial_cancel=False
):
@@ -1868,6 +1904,14 @@ def create_payment_ledger_entry(
def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, party):
if not voucher_type or not voucher_no:
return
if voucher_type in ["Purchase Order", "Sales Order"]:
ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no)
ref_doc.set_total_advance_paid()
return
ple = frappe.qb.DocType("Payment Ledger Entry")
vouchers = [frappe._dict({"voucher_type": voucher_type, "voucher_no": voucher_no})]
common_filter = []
@@ -1910,7 +1954,27 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
def delink_original_entry(pl_entry, partial_cancel=False):
if pl_entry:
if not pl_entry:
return
if pl_entry.doctype == "Advance Payment Ledger Entry":
adv = qb.DocType("Advance Payment Ledger Entry")
(
qb.update(adv)
.set(adv.delinked, 1)
.set(adv.event, "Cancel")
.set(adv.modified, now())
.set(adv.modified_by, frappe.session.user)
.where(adv.voucher_type == pl_entry.voucher_type)
.where(adv.voucher_no == pl_entry.voucher_no)
.where(adv.against_voucher_type == pl_entry.against_voucher_type)
.where(adv.against_voucher_no == pl_entry.against_voucher_no)
.where(adv.event == pl_entry.event)
.run()
)
else:
ple = qb.DocType("Payment Ledger Entry")
query = (
qb.update(ple)

View File

@@ -505,6 +505,7 @@ class PurchaseOrder(BuyingController):
self.ignore_linked_doctypes = (
"GL Entry",
"Payment Ledger Entry",
"Advance Payment Ledger Entry",
"Unreconcile Payment",
"Unreconcile Payment Entries",
)

View File

@@ -397,7 +397,6 @@ class AccountsController(TransactionBase):
def on_trash(self):
from erpnext.accounts.utils import delete_exchange_gain_loss_journal
self._remove_advance_payment_ledger_entries()
self._remove_references_in_repost_doctypes()
self._remove_references_in_unreconcile()
self.remove_serial_and_batch_bundle()
@@ -426,6 +425,8 @@ class AccountsController(TransactionBase):
(sle.voucher_type == self.doctype) & (sle.voucher_no == self.name)
).run()
self._remove_advance_payment_ledger_entries()
def remove_serial_and_batch_bundle(self):
bundles = frappe.get_all(
"Serial and Batch Bundle",
@@ -2233,54 +2234,30 @@ class AccountsController(TransactionBase):
def calculate_total_advance_from_ledger(self):
adv = frappe.qb.DocType("Advance Payment Ledger Entry")
advance = (
frappe.qb.from_(adv)
.select(adv.currency.as_("account_currency"), Abs(Sum(adv.amount)).as_("amount"))
.where(
(adv.against_voucher_type == self.doctype)
& (adv.against_voucher_no == self.name)
& (adv.company == self.company)
)
return (
qb.from_(adv)
.select(Abs(Sum(adv.amount)).as_("amount"), adv.currency.as_("account_currency"))
.where(adv.company == self.company)
.where(adv.delinked == 0)
.where(adv.against_voucher_type == self.doctype)
.where(adv.against_voucher_no == self.name)
.run(as_dict=True)
)
return advance
def set_total_advance_paid(self):
advance = self.calculate_total_advance_from_ledger()
advance_paid, order_total = None, None
advance_paid = 0
if advance:
advance = advance[0]
advance_paid = flt(advance.amount, self.precision("advance_paid"))
formatted_advance_paid = fmt_money(
advance_paid, precision=self.precision("advance_paid"), currency=advance.account_currency
)
if advance.account_currency:
frappe.db.set_value(
self.doctype, self.name, "party_account_currency", advance.account_currency
)
if advance.account_currency == self.currency:
order_total = self.get("rounded_total") or self.grand_total
precision = "rounded_total" if self.get("rounded_total") else "grand_total"
else:
order_total = self.get("base_rounded_total") or self.base_grand_total
precision = "base_rounded_total" if self.get("base_rounded_total") else "base_grand_total"
formatted_order_total = fmt_money(
order_total, precision=self.precision(precision), currency=advance.account_currency
)
if self.currency == self.company_currency and advance_paid > order_total:
frappe.throw(
_(
"Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})"
).format(formatted_advance_paid, self.name, formatted_order_total)
)
self.db_set("advance_paid", advance_paid)
self.db_set("advance_paid", advance_paid)
@property
def company_abbr(self):
@@ -2924,64 +2901,6 @@ class AccountsController(TransactionBase):
def get_advance_payment_doctypes(self) -> list:
return _get_advance_payment_doctypes()
def make_advance_payment_ledger_for_journal(self):
advance_payment_doctypes = self.get_advance_payment_doctypes()
advance_doctype_references = [
x for x in self.accounts if x.reference_type in advance_payment_doctypes
]
for x in advance_doctype_references:
# Looking for payments
dr_or_cr = (
"credit_in_account_currency"
if x.account_type == "Receivable"
else "debit_in_account_currency"
)
amount = x.get(dr_or_cr)
if amount > 0:
doc = frappe.new_doc("Advance Payment Ledger Entry")
doc.company = self.company
doc.voucher_type = self.doctype
doc.voucher_no = self.name
doc.against_voucher_type = x.reference_type
doc.against_voucher_no = x.reference_name
doc.amount = amount if self.docstatus == 1 else -1 * amount
doc.event = "Submit" if self.docstatus == 1 else "Cancel"
doc.currency = x.account_currency
doc.flags.ignore_permissions = 1
doc.save()
def make_advance_payment_ledger_for_payment(self):
advance_payment_doctypes = self.get_advance_payment_doctypes()
advance_doctype_references = [
x for x in self.references if x.reference_doctype in advance_payment_doctypes
]
currency = (
self.paid_from_account_currency
if self.payment_type == "Receive"
else self.paid_to_account_currency
)
for x in advance_doctype_references:
doc = frappe.new_doc("Advance Payment Ledger Entry")
doc.company = self.company
doc.voucher_type = self.doctype
doc.voucher_no = self.name
doc.against_voucher_type = x.reference_doctype
doc.against_voucher_no = x.reference_name
doc.amount = x.allocated_amount if self.docstatus == 1 else -1 * x.allocated_amount
doc.currency = currency
doc.event = "Submit" if self.docstatus == 1 else "Cancel"
doc.flags.ignore_permissions = 1
doc.save()
def make_advance_payment_ledger_entries(self):
if self.docstatus != 0:
if self.doctype == "Journal Entry":
self.make_advance_payment_ledger_for_journal()
elif self.doctype == "Payment Entry":
self.make_advance_payment_ledger_for_payment()
def set_transaction_currency_and_rate_in_gl_map(self, gl_entries):
for x in gl_entries:
x["transaction_currency"] = self.currency

View File

@@ -361,7 +361,7 @@ erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes
erpnext.patches.v14_0.update_flag_for_return_invoices #2024-03-22
erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request
erpnext.patches.v14_0.update_pos_return_ledger_entries #2024-08-16
erpnext.patches.v15_0.create_advance_payment_ledger_records
erpnext.patches.v15_0.create_advance_payment_ledger_records #2025-07-04
# below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
@@ -412,6 +412,7 @@ erpnext.patches.v15_0.drop_sle_indexes
erpnext.patches.v15_0.update_pick_list_fields
erpnext.patches.v15_0.update_pegged_currencies
erpnext.patches.v15_0.set_company_on_pos_inv_merge_log
erpnext.patches.v15_0.update_payment_ledger_entries_against_advance_doctypes
erpnext.patches.v15_0.rename_price_list_to_buying_price_list
erpnext.patches.v15_0.patch_missing_buying_price_list_in_material_request
erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice

View File

@@ -1,63 +1,28 @@
import frappe
from frappe import qb
from frappe.query_builder.custom import ConstantColumn
from frappe.model.naming import _generate_random_string
from frappe.query_builder import Case
from frappe.utils import now_datetime
from erpnext.accounts.utils import get_advance_payment_doctypes
def get_advance_doctypes() -> list:
return frappe.get_hooks("advance_payment_doctypes")
DOCTYPE = "Advance Payment Ledger Entry"
def get_payments_with_so_po_reference() -> list:
advance_payment_entries = []
advance_doctypes = get_advance_doctypes()
per = qb.DocType("Payment Entry Reference")
payments_with_reference = (
qb.from_(per)
.select(per.parent)
.distinct()
.where(per.reference_doctype.isin(advance_doctypes) & per.docstatus.eq(1))
.run()
)
if payments_with_reference:
pe = qb.DocType("Payment Entry")
advance_payment_entries = (
qb.from_(pe)
.select(ConstantColumn("Payment Entry").as_("doctype"))
.select(pe.name)
.where(pe.name.isin(payments_with_reference) & pe.docstatus.eq(1))
.run(as_dict=True)
)
return advance_payment_entries
def get_journals_with_so_po_reference() -> list:
advance_journal_entries = []
advance_doctypes = get_advance_doctypes()
jea = qb.DocType("Journal Entry Account")
journals_with_reference = (
qb.from_(jea)
.select(jea.parent)
.distinct()
.where(jea.reference_type.isin(advance_doctypes) & jea.docstatus.eq(1))
.run()
)
if journals_with_reference:
je = qb.DocType("Journal Entry")
advance_journal_entries = (
qb.from_(je)
.select(ConstantColumn("Journal Entry").as_("doctype"))
.select(je.name)
.where(je.name.isin(journals_with_reference) & je.docstatus.eq(1))
.run(as_dict=True)
)
return advance_journal_entries
def make_advance_ledger_entries(vouchers: list):
for x in vouchers:
frappe.get_doc(x.doctype, x.name).make_advance_payment_ledger_entries()
FIELDS = [
"name",
"creation",
"modified",
"owner",
"modified_by",
"company",
"voucher_type",
"voucher_no",
"against_voucher_type",
"against_voucher_no",
"amount",
"currency",
"event",
"delinked",
]
def execute():
@@ -65,9 +30,102 @@ def execute():
Description:
Create Advance Payment Ledger Entry for all Payments made against Sales / Purchase Orders
"""
frappe.db.truncate("Advance Payment Ledger Entry")
payment_entries = get_payments_with_so_po_reference()
make_advance_ledger_entries(payment_entries)
frappe.db.truncate(DOCTYPE)
advance_doctpyes = get_advance_payment_doctypes()
journals = get_journals_with_so_po_reference()
make_advance_ledger_entries(journals)
if not advance_doctpyes:
return
make_advance_ledger_entries_for_payment_entries(advance_doctpyes)
make_advance_ledger_entries_for_journal_entries(advance_doctpyes)
def make_advance_ledger_entries_for_payment_entries(advance_doctpyes) -> list:
pe = frappe.qb.DocType("Payment Entry")
per = frappe.qb.DocType("Payment Entry Reference")
entries = (
frappe.qb.from_(per)
.inner_join(pe)
.on(pe.name == per.parent)
.select(
pe.company,
per.parenttype.as_("voucher_type"),
per.parent.as_("voucher_no"),
per.reference_doctype.as_("against_voucher_type"),
per.reference_name.as_("against_voucher_no"),
per.allocated_amount.as_("amount"),
Case()
.when(pe.payment_type == "Receive", pe.paid_from_account_currency)
.else_(pe.paid_to_account_currency)
.as_("currency"),
)
.where(per.reference_doctype.isin(advance_doctpyes) & per.docstatus.eq(1))
.run(as_dict=True)
)
if not entries:
return
bulk_insert_advance_entries(entries)
def make_advance_ledger_entries_for_journal_entries(advance_doctpyes) -> list:
je = frappe.qb.DocType("Journal Entry")
jea = frappe.qb.DocType("Journal Entry Account")
entries = (
frappe.qb.from_(jea)
.inner_join(je)
.on(je.name == jea.parent)
.select(
je.company,
jea.parenttype.as_("voucher_type"),
jea.parent.as_("voucher_no"),
jea.reference_type.as_("against_voucher_type"),
jea.reference_name.as_("against_voucher_no"),
Case()
.when(jea.account_type == "Receivable", jea.credit_in_account_currency)
.else_(jea.debit_in_account_currency)
.as_("amount"),
jea.account_currency.as_("currency"),
)
.where(jea.reference_type.isin(advance_doctpyes) & jea.docstatus.eq(1))
.run(as_dict=True)
)
if not entries:
return
bulk_insert_advance_entries(entries)
def bulk_insert_advance_entries(entries):
details = []
user = frappe.session.user
now = now_datetime()
for entry in entries:
if entry.amount < 0:
continue
details.append(get_values(user, now, entry))
frappe.db.bulk_insert(DOCTYPE, fields=FIELDS, values=details)
def get_values(user, now, entry):
return (
_generate_random_string(10),
now,
now,
user,
user,
entry.company,
entry.voucher_type,
entry.voucher_no,
entry.against_voucher_type,
entry.against_voucher_no,
entry.amount * -1,
entry.currency,
"Submit",
0,
)

View File

@@ -0,0 +1,25 @@
import frappe
from erpnext.accounts.utils import get_advance_payment_doctypes
DOCTYPE = "Payment Ledger Entry"
def execute():
"""
Description:
Set against_voucher as entry for Payment Ledger Entry against advance vouchers.
"""
advance_payment_doctypes = get_advance_payment_doctypes()
if not advance_payment_doctypes:
return
ple = frappe.qb.DocType(DOCTYPE)
(
frappe.qb.update(ple)
.set(ple.against_voucher_type, ple.voucher_type)
.set(ple.against_voucher_no, ple.voucher_no)
.where(ple.against_voucher_type.isin(advance_payment_doctypes))
.run()
)

View File

@@ -42,8 +42,8 @@ erpnext.accounts.unreconcile_payment = {
selection_map = selections.map(function (elem) {
return {
company: elem.company,
voucher_type: elem.voucher_type,
voucher_no: elem.voucher_no,
voucher_type: elem.reference_doctype,
voucher_no: elem.reference_name,
against_voucher_type: frm.doc.doctype,
against_voucher_no: frm.doc.name,
};
@@ -54,8 +54,8 @@ erpnext.accounts.unreconcile_payment = {
company: elem.company,
voucher_type: frm.doc.doctype,
voucher_no: frm.doc.name,
against_voucher_type: elem.voucher_type,
against_voucher_no: elem.voucher_no,
against_voucher_type: elem.reference_doctype,
against_voucher_no: elem.reference_name,
};
});
}
@@ -69,7 +69,7 @@ erpnext.accounts.unreconcile_payment = {
let child_table_fields = [
{
label: __("Voucher Type"),
fieldname: "voucher_type",
fieldname: "reference_doctype",
fieldtype: "Link",
options: "DocType",
in_list_view: 1,
@@ -77,9 +77,9 @@ erpnext.accounts.unreconcile_payment = {
},
{
label: __("Voucher No"),
fieldname: "voucher_no",
fieldname: "reference_name",
fieldtype: "Dynamic Link",
options: "voucher_type",
options: "reference_doctype",
in_list_view: 1,
read_only: 1,
},

View File

@@ -444,6 +444,7 @@ class SalesOrder(SellingController):
"GL Entry",
"Stock Ledger Entry",
"Payment Ledger Entry",
"Advance Payment Ledger Entry",
"Unreconcile Payment",
"Unreconcile Payment Entries",
)