-
Notifications
You must be signed in to change notification settings - Fork 7.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: ledger comparison report (#36485)
* feat: Accounting Ledger comparison report * chore: barebones methods * chore: working state * chore: refactor internal logic * chore: working multi select filter on Account * chore: working voucher no filter * chore: remove debugging statements * chore: report with currency symbol * chore: working start and end date filter * test: basic report function * refactor(test): test all filters (cherry picked from commit b86747c)
- Loading branch information
1 parent
7adad42
commit 07f235c
Showing
5 changed files
with
405 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
52 changes: 52 additions & 0 deletions
52
...nts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors | ||
// For license information, please see license.txt | ||
|
||
function get_filters() { | ||
let filters = [ | ||
{ | ||
"fieldname":"company", | ||
"label": __("Company"), | ||
"fieldtype": "Link", | ||
"options": "Company", | ||
"default": frappe.defaults.get_user_default("Company"), | ||
"reqd": 1 | ||
}, | ||
{ | ||
"fieldname":"period_start_date", | ||
"label": __("Start Date"), | ||
"fieldtype": "Date", | ||
"reqd": 1, | ||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1) | ||
}, | ||
{ | ||
"fieldname":"period_end_date", | ||
"label": __("End Date"), | ||
"fieldtype": "Date", | ||
"reqd": 1, | ||
"default": frappe.datetime.get_today() | ||
}, | ||
{ | ||
"fieldname":"account", | ||
"label": __("Account"), | ||
"fieldtype": "MultiSelectList", | ||
"options": "Account", | ||
get_data: function(txt) { | ||
return frappe.db.get_link_options('Account', txt, { | ||
company: frappe.query_report.get_filter_value("company"), | ||
account_type: ['in', ["Receivable", "Payable"]] | ||
}); | ||
} | ||
}, | ||
{ | ||
"fieldname":"voucher_no", | ||
"label": __("Voucher No"), | ||
"fieldtype": "Data", | ||
"width": 100, | ||
}, | ||
] | ||
return filters; | ||
} | ||
|
||
frappe.query_reports["General and Payment Ledger Comparison"] = { | ||
"filters": get_filters() | ||
}; |
32 changes: 32 additions & 0 deletions
32
...s/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
"add_total_row": 0, | ||
"columns": [], | ||
"creation": "2023-08-02 17:30:29.494907", | ||
"disabled": 0, | ||
"docstatus": 0, | ||
"doctype": "Report", | ||
"filters": [], | ||
"idx": 0, | ||
"is_standard": "Yes", | ||
"letterhead": null, | ||
"modified": "2023-08-02 17:30:29.494907", | ||
"modified_by": "Administrator", | ||
"module": "Accounts", | ||
"name": "General and Payment Ledger Comparison", | ||
"owner": "Administrator", | ||
"prepared_report": 0, | ||
"ref_doctype": "GL Entry", | ||
"report_name": "General and Payment Ledger Comparison", | ||
"report_type": "Script Report", | ||
"roles": [ | ||
{ | ||
"role": "Accounts User" | ||
}, | ||
{ | ||
"role": "Accounts Manager" | ||
}, | ||
{ | ||
"role": "Auditor" | ||
} | ||
] | ||
} |
221 changes: 221 additions & 0 deletions
221
...nts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors | ||
# For license information, please see license.txt | ||
|
||
import frappe | ||
from frappe import _, qb | ||
from frappe.query_builder import Criterion | ||
from frappe.query_builder.functions import Sum | ||
|
||
|
||
class General_Payment_Ledger_Comparison(object): | ||
""" | ||
A Utility report to compare Voucher-wise balance between General and Payment Ledger | ||
""" | ||
|
||
def __init__(self, filters=None): | ||
self.filters = filters | ||
self.gle = [] | ||
self.ple = [] | ||
|
||
def get_accounts(self): | ||
receivable_accounts = [ | ||
x[0] | ||
for x in frappe.db.get_all( | ||
"Account", | ||
filters={"company": self.filters.company, "account_type": "Receivable"}, | ||
as_list=True, | ||
) | ||
] | ||
payable_accounts = [ | ||
x[0] | ||
for x in frappe.db.get_all( | ||
"Account", filters={"company": self.filters.company, "account_type": "Payable"}, as_list=True | ||
) | ||
] | ||
|
||
self.account_types = frappe._dict( | ||
{ | ||
"receivable": frappe._dict({"accounts": receivable_accounts, "gle": [], "ple": []}), | ||
"payable": frappe._dict({"accounts": payable_accounts, "gle": [], "ple": []}), | ||
} | ||
) | ||
|
||
def generate_filters(self): | ||
if self.filters.account: | ||
self.account_types.receivable.accounts = [] | ||
self.account_types.payable.accounts = [] | ||
|
||
for acc in frappe.db.get_all( | ||
"Account", filters={"name": ["in", self.filters.account]}, fields=["name", "account_type"] | ||
): | ||
if acc.account_type == "Receivable": | ||
self.account_types.receivable.accounts.append(acc.name) | ||
else: | ||
self.account_types.payable.accounts.append(acc.name) | ||
|
||
def get_gle(self): | ||
gle = qb.DocType("GL Entry") | ||
|
||
for acc_type, val in self.account_types.items(): | ||
if val.accounts: | ||
|
||
filter_criterion = [] | ||
if self.filters.voucher_no: | ||
filter_criterion.append((gle.voucher_no == self.filters.voucher_no)) | ||
|
||
if self.filters.period_start_date: | ||
filter_criterion.append(gle.posting_date.gte(self.filters.period_start_date)) | ||
|
||
if self.filters.period_end_date: | ||
filter_criterion.append(gle.posting_date.lte(self.filters.period_end_date)) | ||
|
||
if acc_type == "receivable": | ||
outstanding = (Sum(gle.debit) - Sum(gle.credit)).as_("outstanding") | ||
else: | ||
outstanding = (Sum(gle.credit) - Sum(gle.debit)).as_("outstanding") | ||
|
||
self.account_types[acc_type].gle = ( | ||
qb.from_(gle) | ||
.select( | ||
gle.company, | ||
gle.account, | ||
gle.voucher_no, | ||
gle.party, | ||
outstanding, | ||
) | ||
.where( | ||
(gle.company == self.filters.company) | ||
& (gle.is_cancelled == 0) | ||
& (gle.account.isin(val.accounts)) | ||
) | ||
.where(Criterion.all(filter_criterion)) | ||
.groupby(gle.company, gle.account, gle.voucher_no, gle.party) | ||
.run() | ||
) | ||
|
||
def get_ple(self): | ||
ple = qb.DocType("Payment Ledger Entry") | ||
|
||
for acc_type, val in self.account_types.items(): | ||
if val.accounts: | ||
|
||
filter_criterion = [] | ||
if self.filters.voucher_no: | ||
filter_criterion.append((ple.voucher_no == self.filters.voucher_no)) | ||
|
||
if self.filters.period_start_date: | ||
filter_criterion.append(ple.posting_date.gte(self.filters.period_start_date)) | ||
|
||
if self.filters.period_end_date: | ||
filter_criterion.append(ple.posting_date.lte(self.filters.period_end_date)) | ||
|
||
self.account_types[acc_type].ple = ( | ||
qb.from_(ple) | ||
.select( | ||
ple.company, ple.account, ple.voucher_no, ple.party, Sum(ple.amount).as_("outstanding") | ||
) | ||
.where( | ||
(ple.company == self.filters.company) | ||
& (ple.delinked == 0) | ||
& (ple.account.isin(val.accounts)) | ||
) | ||
.where(Criterion.all(filter_criterion)) | ||
.groupby(ple.company, ple.account, ple.voucher_no, ple.party) | ||
.run() | ||
) | ||
|
||
def compare(self): | ||
self.gle_balances = set() | ||
self.ple_balances = set() | ||
|
||
# consolidate both receivable and payable balances in one set | ||
for acc_type, val in self.account_types.items(): | ||
self.gle_balances = set(val.gle) | self.gle_balances | ||
self.ple_balances = set(val.ple) | self.ple_balances | ||
|
||
self.diff1 = self.gle_balances.difference(self.ple_balances) | ||
self.diff2 = self.ple_balances.difference(self.gle_balances) | ||
self.diff = frappe._dict({}) | ||
|
||
for x in self.diff1: | ||
self.diff[(x[0], x[1], x[2], x[3])] = frappe._dict({"gl_balance": x[4]}) | ||
|
||
for x in self.diff2: | ||
self.diff[(x[0], x[1], x[2], x[3])].update(frappe._dict({"pl_balance": x[4]})) | ||
|
||
def generate_data(self): | ||
self.data = [] | ||
for key, val in self.diff.items(): | ||
self.data.append( | ||
frappe._dict( | ||
{ | ||
"voucher_no": key[2], | ||
"party": key[3], | ||
"gl_balance": val.gl_balance, | ||
"pl_balance": val.pl_balance, | ||
} | ||
) | ||
) | ||
|
||
def get_columns(self): | ||
self.columns = [] | ||
options = None | ||
self.columns.append( | ||
dict( | ||
label=_("Voucher No"), | ||
fieldname="voucher_no", | ||
fieldtype="Data", | ||
options=options, | ||
width="100", | ||
) | ||
) | ||
|
||
self.columns.append( | ||
dict( | ||
label=_("Party"), | ||
fieldname="party", | ||
fieldtype="Data", | ||
options=options, | ||
width="100", | ||
) | ||
) | ||
|
||
self.columns.append( | ||
dict( | ||
label=_("GL Balance"), | ||
fieldname="gl_balance", | ||
fieldtype="Currency", | ||
options="Company:company:default_currency", | ||
width="100", | ||
) | ||
) | ||
|
||
self.columns.append( | ||
dict( | ||
label=_("Payment Ledger Balance"), | ||
fieldname="pl_balance", | ||
fieldtype="Currency", | ||
options="Company:company:default_currency", | ||
width="100", | ||
) | ||
) | ||
|
||
def run(self): | ||
self.get_accounts() | ||
self.generate_filters() | ||
self.get_gle() | ||
self.get_ple() | ||
self.compare() | ||
self.generate_data() | ||
self.get_columns() | ||
|
||
return self.columns, self.data | ||
|
||
|
||
def execute(filters=None): | ||
columns, data = [], [] | ||
|
||
rpt = General_Payment_Ledger_Comparison(filters) | ||
columns, data = rpt.run() | ||
|
||
return columns, data |
Oops, something went wrong.