Skip to content

Commit

Permalink
refactor: update voucher outstanding from payment ledger
Browse files Browse the repository at this point in the history
Outstanding amount is updated from payment ledger, only for
receivable/payable accounts. For remaining account types, update happens
from GL Entry.

separate method for creating payment ledger has been added in payment
entry and journal entry.
  • Loading branch information
ruthra-kumar committed Jun 2, 2022
1 parent 54be392 commit 171c317
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 18 deletions.
24 changes: 14 additions & 10 deletions erpnext/accounts/doctype/gl_entry/gl_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,20 @@ def on_update(self):
validate_balance_type(self.account, adv_adj)
validate_frozen_account(self.account, adv_adj)

# Update outstanding amt on against voucher
if (
self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
and self.against_voucher
and self.flags.update_outstanding == "Yes"
and not frappe.flags.is_reverse_depr_entry
):
update_outstanding_amt(
self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher
)
if frappe.db.get_value("Account", self.account, "account_type") not in [
"Receivable",
"Payable",
]:
# Update outstanding amt on against voucher
if (
self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
and self.against_voucher
and self.flags.update_outstanding == "Yes"
and not frappe.flags.is_reverse_depr_entry
):
update_outstanding_amt(
self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher
)

def check_mandatory(self):
mandatory = ["account", "voucher_type", "voucher_no", "company"]
Expand Down
4 changes: 2 additions & 2 deletions erpnext/accounts/doctype/journal_entry/journal_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -838,11 +838,11 @@ def build_gl_map(self, cancel=0):
)
return gl_map

def make_payment_ledger_entries(self, cancel=0):
def make_payment_ledger_entries(self, cancel=0, adv_adj=0):
from erpnext.accounts.utils import create_payment_ledger_entry

gl_map = self.build_gl_map(cancel)
create_payment_ledger_entry(gl_map, cancel)
create_payment_ledger_entry(gl_map, cancel=cancel, adv_adj=adv_adj)

def make_gl_entries(self, cancel=0, adv_adj=0):
from erpnext.accounts.general_ledger import make_gl_entries
Expand Down
4 changes: 2 additions & 2 deletions erpnext/accounts/doctype/payment_entry/payment_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,11 +801,11 @@ def build_gl_map(self):
self.add_tax_gl_entries(gl_entries)
return gl_entries

def make_payment_ledger_entries(self, cancel=0):
def make_payment_ledger_entries(self, cancel=0, adv_adj=0):
# used only on reconciliation
gl_entries = self.build_gl_map()
gl_entries = process_gl_map(gl_entries)
create_payment_ledger_entry(gl_entries, cancel)
create_payment_ledger_entry(gl_entries, cancel=cancel, adv_adj=adv_adj)

def make_gl_entries(self, cancel=0, adv_adj=0):
gl_entries = self.build_gl_map()
Expand Down
127 changes: 127 additions & 0 deletions erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@
from frappe import _
from frappe.model.document import Document

from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
get_dimension_filter_map,
)
from erpnext.accounts.doctype.gl_entry.gl_entry import (
validate_balance_type,
validate_frozen_account,
)
from erpnext.accounts.utils import update_voucher_outstanding
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError


class PaymentLedgerEntry(Document):
def validate_account(self):
Expand All @@ -18,5 +31,119 @@ def validate_account(self):
if not valid_account:
frappe.throw(_("{0} account is not of type {1}").format(self.account, self.account_type))

def validate_account_details(self):
"""Account must be ledger, active and not freezed"""

ret = frappe.db.sql(
"""select is_group, docstatus, company
from tabAccount where name=%s""",
self.account,
as_dict=1,
)[0]

if ret.is_group == 1:
frappe.throw(
_(
"""{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions"""
).format(self.voucher_type, self.voucher_no, self.account)
)

if ret.docstatus == 2:
frappe.throw(
_("{0} {1}: Account {2} is inactive").format(self.voucher_type, self.voucher_no, self.account)
)

if ret.company != self.company:
frappe.throw(
_("{0} {1}: Account {2} does not belong to Company {3}").format(
self.voucher_type, self.voucher_no, self.account, self.company
)
)

def validate_allowed_dimensions(self):
dimension_filter_map = get_dimension_filter_map()
for key, value in dimension_filter_map.items():
dimension = key[0]
account = key[1]

if self.account == account:
if value["is_mandatory"] and not self.get(dimension):
frappe.throw(
_("{0} is mandatory for account {1}").format(
frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)
),
MandatoryAccountDimensionError,
)

if value["allow_or_restrict"] == "Allow":
if self.get(dimension) and self.get(dimension) not in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(self.account),
),
InvalidAccountDimensionError,
)
else:
if self.get(dimension) and self.get(dimension) in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(self.account),
),
InvalidAccountDimensionError,
)

def validate_dimensions_for_pl_and_bs(self):
account_type = frappe.db.get_value("Account", self.account, "report_type")

for dimension in get_checks_for_pl_and_bs_accounts():
if (
account_type == "Profit and Loss"
and self.company == dimension.company
and dimension.mandatory_for_pl
and not dimension.disabled
):
if not self.get(dimension.fieldname):
frappe.throw(
_("Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}.").format(
dimension.label, self.account
)
)

if (
account_type == "Balance Sheet"
and self.company == dimension.company
and dimension.mandatory_for_bs
and not dimension.disabled
):
if not self.get(dimension.fieldname):
frappe.throw(
_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.").format(
dimension.label, self.account
)
)

def validate(self):
self.validate_account()

def on_update(self):
adv_adj = self.flags.adv_adj
if not self.flags.from_repost:
self.validate_account_details()
self.validate_dimensions_for_pl_and_bs()
self.validate_allowed_dimensions()
validate_balance_type(self.account, adv_adj)
validate_frozen_account(self.account, adv_adj)

# update outstanding amount
if (
self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
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, self.account, self.party_type, self.party
)
11 changes: 10 additions & 1 deletion erpnext/accounts/general_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ def make_gl_entries(
validate_disabled_accounts(gl_map)
gl_map = process_gl_map(gl_map, merge_entries)
if gl_map and len(gl_map) > 1:
create_payment_ledger_entry(gl_map)
create_payment_ledger_entry(
gl_map,
cancel=0,
adv_adj=adv_adj,
update_outstanding=update_outstanding,
from_repost=from_repost,
)
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
# Post GL Map proccess there may no be any GL Entries
elif gl_map:
Expand Down Expand Up @@ -482,6 +488,9 @@ def make_reverse_gl_entries(

if gl_entries:
create_payment_ledger_entry(gl_entries, cancel=1)
create_payment_ledger_entry(
gl_entries, cancel=1, adv_adj=adv_adj, update_outstanding=update_outstanding
)
validate_accounting_period(gl_entries)
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
Expand Down
43 changes: 40 additions & 3 deletions erpnext/accounts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ def reconcile_against_document(args):
# cancel advance entry
doc = frappe.get_doc(voucher_type, voucher_no)
frappe.flags.ignore_party_validation = True
doc.make_payment_ledger_entries(cancel=1)
doc.make_payment_ledger_entries(cancel=1, adv_adj=1)

for entry in entries:
check_if_advance_entry_modified(entry)
Expand All @@ -440,7 +440,7 @@ def reconcile_against_document(args):
doc.save(ignore_permissions=True)
# re-submit advance entry
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
doc.make_payment_ledger_entries(cancel=0)
doc.make_payment_ledger_entries(cancel=0, adv_adj=1)
frappe.flags.ignore_party_validation = False

if entry.voucher_type in ("Payment Entry", "Journal Entry"):
Expand Down Expand Up @@ -1309,7 +1309,9 @@ def check_and_delete_linked_reports(report):
frappe.delete_doc("Desktop Icon", icon)


def create_payment_ledger_entry(gl_entries, cancel=0):
def create_payment_ledger_entry(
gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0
):
if gl_entries:
ple = None

Expand Down Expand Up @@ -1382,9 +1384,44 @@ def get_account_type(account):
if cancel:
delink_original_entry(ple)
ple.flags.ignore_permissions = 1
ple.flags.adv_adj = adv_adj
ple.flags.from_repost = from_repost
ple.flags.update_outstanding = update_outstanding
ple.submit()


def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, party):
ple = frappe.qb.DocType("Payment Ledger Entry")
vouchers = [frappe._dict({"voucher_type": voucher_type, "voucher_no": voucher_no})]
selection_filter = []
if account:
selection_filter.append(ple.account == account)

if party_type:
selection_filter.append(ple.party_type == party_type)

if party:
selection_filter.append(ple.party == party)

ple_query = QueryPaymentLedger()

# on cancellation the outstanding can give empty list
voucher_outstanding = ple_query.get_voucher_outstandings(
vouchers, selection_filter=selection_filter
)
if voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"] and voucher_outstanding:
outstanding = voucher_outstanding[0]
ref_doc = frappe.get_doc(voucher_type, voucher_no)

# Didn't use db_set for optimisation purpose
ref_doc.outstanding_amount = outstanding["outstanding_in_account_currency"]
frappe.db.set_value(
voucher_type, voucher_no, "outstanding_amount", outstanding["outstanding_in_account_currency"]
)

ref_doc.set_status(update=True)


def delink_original_entry(pl_entry):
if pl_entry:
ple = qb.DocType("Payment Ledger Entry")
Expand Down

0 comments on commit 171c317

Please sign in to comment.