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.
  • Loading branch information
ruthra-kumar committed Jun 9, 2022
1 parent 23fa81b commit cc13b42
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 11 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
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
30 changes: 30 additions & 0 deletions erpnext/accounts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,36 @@ def get_account_type(account):
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})]
common_filter = []
if account:
common_filter.append(ple.account == account)

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

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

ple_query = QueryPaymentLedger()

# on cancellation outstanding can be an empty list
voucher_outstanding = ple_query.get_voucher_outstandings(vouchers, common_filter=common_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 cc13b42

Please sign in to comment.