Merge pull request #32651 from ruthra-kumar/report_payment_ledger
feat: Payment Ledger report
This commit is contained in:
commit
eedf7e44a2
0
erpnext/accounts/report/payment_ledger/__init__.py
Normal file
0
erpnext/accounts/report/payment_ledger/__init__.py
Normal file
59
erpnext/accounts/report/payment_ledger/payment_ledger.js
Normal file
59
erpnext/accounts/report/payment_ledger/payment_ledger.js
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
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")
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"voucher_no",
|
||||
"label": __("Voucher No"),
|
||||
"fieldtype": "Data",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"fieldname":"against_voucher_no",
|
||||
"label": __("Against Voucher No"),
|
||||
"fieldtype": "Data",
|
||||
"width": 100,
|
||||
},
|
||||
|
||||
]
|
||||
return filters;
|
||||
}
|
||||
|
||||
frappe.query_reports["Payment Ledger"] = {
|
||||
"filters": get_filters()
|
||||
};
|
32
erpnext/accounts/report/payment_ledger/payment_ledger.json
Normal file
32
erpnext/accounts/report/payment_ledger/payment_ledger.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2022-06-06 08:50:43.933708",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2022-06-06 08:50:43.933708",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Ledger",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Payment Ledger Entry",
|
||||
"report_name": "Payment Ledger",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Auditor"
|
||||
}
|
||||
]
|
||||
}
|
222
erpnext/accounts/report/payment_ledger/payment_ledger.py
Normal file
222
erpnext/accounts/report/payment_ledger/payment_ledger.py
Normal file
@ -0,0 +1,222 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb
|
||||
from frappe.query_builder import Criterion
|
||||
|
||||
|
||||
class PaymentLedger(object):
|
||||
def __init__(self, filters=None):
|
||||
self.filters = filters
|
||||
self.columns, self.data = [], []
|
||||
self.voucher_dict = OrderedDict()
|
||||
self.voucher_amount = []
|
||||
self.ple = qb.DocType("Payment Ledger Entry")
|
||||
|
||||
def init_voucher_dict(self):
|
||||
|
||||
if self.voucher_amount:
|
||||
s = set()
|
||||
# build a set of unique vouchers
|
||||
for ple in self.voucher_amount:
|
||||
key = (ple.voucher_type, ple.voucher_no, ple.party)
|
||||
s.add(key)
|
||||
|
||||
# for each unique vouchers, initialize +/- list
|
||||
for key in s:
|
||||
self.voucher_dict[key] = frappe._dict(increase=list(), decrease=list())
|
||||
|
||||
# for each ple, using against voucher and amount, assign it to +/- list
|
||||
# group by against voucher
|
||||
for ple in self.voucher_amount:
|
||||
against_key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||
target = None
|
||||
if self.voucher_dict.get(against_key):
|
||||
if ple.amount > 0:
|
||||
target = self.voucher_dict.get(against_key).increase
|
||||
else:
|
||||
target = self.voucher_dict.get(against_key).decrease
|
||||
|
||||
# this if condition will lose unassigned ple entries(against_voucher doc doesn't have ple)
|
||||
# need to somehow include the stray entries as well.
|
||||
if target is not None:
|
||||
entry = frappe._dict(
|
||||
company=ple.company,
|
||||
account=ple.account,
|
||||
party_type=ple.party_type,
|
||||
party=ple.party,
|
||||
voucher_type=ple.voucher_type,
|
||||
voucher_no=ple.voucher_no,
|
||||
against_voucher_type=ple.against_voucher_type,
|
||||
against_voucher_no=ple.against_voucher_no,
|
||||
amount=ple.amount,
|
||||
currency=ple.account_currency,
|
||||
)
|
||||
|
||||
if self.filters.include_account_currency:
|
||||
entry["amount_in_account_currency"] = ple.amount_in_account_currency
|
||||
|
||||
target.append(entry)
|
||||
|
||||
def build_data(self):
|
||||
self.data.clear()
|
||||
|
||||
for value in self.voucher_dict.values():
|
||||
voucher_data = []
|
||||
if value.increase != []:
|
||||
voucher_data.extend(value.increase)
|
||||
if value.decrease != []:
|
||||
voucher_data.extend(value.decrease)
|
||||
|
||||
if voucher_data:
|
||||
# balance row
|
||||
total = 0
|
||||
total_in_account_currency = 0
|
||||
|
||||
for x in voucher_data:
|
||||
total += x.amount
|
||||
if self.filters.include_account_currency:
|
||||
total_in_account_currency += x.amount_in_account_currency
|
||||
|
||||
entry = frappe._dict(
|
||||
against_voucher_no="Outstanding:",
|
||||
amount=total,
|
||||
currency=voucher_data[0].currency,
|
||||
)
|
||||
|
||||
if self.filters.include_account_currency:
|
||||
entry["amount_in_account_currency"] = total_in_account_currency
|
||||
|
||||
voucher_data.append(entry)
|
||||
|
||||
# empty row
|
||||
voucher_data.append(frappe._dict())
|
||||
self.data.extend(voucher_data)
|
||||
|
||||
def build_conditions(self):
|
||||
self.conditions = []
|
||||
|
||||
if self.filters.company:
|
||||
self.conditions.append(self.ple.company == self.filters.company)
|
||||
|
||||
if self.filters.account:
|
||||
self.conditions.append(self.ple.account.isin(self.filters.account))
|
||||
|
||||
if self.filters.period_start_date:
|
||||
self.conditions.append(self.ple.posting_date.gte(self.filters.period_start_date))
|
||||
|
||||
if self.filters.period_end_date:
|
||||
self.conditions.append(self.ple.posting_date.lte(self.filters.period_end_date))
|
||||
|
||||
if self.filters.voucher_no:
|
||||
self.conditions.append(self.ple.voucher_no == self.filters.voucher_no)
|
||||
|
||||
if self.filters.against_voucher_no:
|
||||
self.conditions.append(self.ple.against_voucher_no == self.filters.against_voucher_no)
|
||||
|
||||
def get_data(self):
|
||||
ple = self.ple
|
||||
|
||||
self.build_conditions()
|
||||
|
||||
# fetch data from table
|
||||
self.voucher_amount = (
|
||||
qb.from_(ple)
|
||||
.select(ple.star)
|
||||
.where(ple.delinked == 0)
|
||||
.where(Criterion.all(self.conditions))
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
def get_columns(self):
|
||||
options = None
|
||||
self.columns.append(
|
||||
dict(label=_("Company"), fieldname="company", fieldtype="data", options=options, width="100")
|
||||
)
|
||||
|
||||
self.columns.append(
|
||||
dict(label=_("Account"), fieldname="account", fieldtype="data", options=options, width="100")
|
||||
)
|
||||
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Party Type"), fieldname="party_type", 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=_("Voucher Type"),
|
||||
fieldname="voucher_type",
|
||||
fieldtype="data",
|
||||
options=options,
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Voucher No"), fieldname="voucher_no", fieldtype="data", options=options, width="100"
|
||||
)
|
||||
)
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Against Voucher Type"),
|
||||
fieldname="against_voucher_type",
|
||||
fieldtype="data",
|
||||
options=options,
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Against Voucher No"),
|
||||
fieldname="against_voucher_no",
|
||||
fieldtype="data",
|
||||
options=options,
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Amount"),
|
||||
fieldname="amount",
|
||||
fieldtype="Currency",
|
||||
options="Company:company:default_currency",
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
|
||||
if self.filters.include_account_currency:
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=_("Amount in Account Currency"),
|
||||
fieldname="amount_in_account_currency",
|
||||
fieldtype="Currency",
|
||||
options="currency",
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
self.columns.append(
|
||||
dict(label=_("Currency"), fieldname="currency", fieldtype="Currency", hidden=True)
|
||||
)
|
||||
|
||||
def run(self):
|
||||
self.get_columns()
|
||||
self.get_data()
|
||||
|
||||
# initialize dictionary and group using against voucher
|
||||
self.init_voucher_dict()
|
||||
|
||||
# convert dictionary to list and add balance rows
|
||||
self.build_data()
|
||||
|
||||
return self.columns, self.data
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
return PaymentLedger(filters).run()
|
@ -0,0 +1,65 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.report.payment_ledger.payment_ledger import execute
|
||||
|
||||
|
||||
class TestPaymentLedger(FrappeTestCase):
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.cleanup()
|
||||
|
||||
def cleanup(self):
|
||||
doctypes = []
|
||||
doctypes.append(qb.DocType("GL Entry"))
|
||||
doctypes.append(qb.DocType("Payment Ledger Entry"))
|
||||
doctypes.append(qb.DocType("Sales Invoice"))
|
||||
doctypes.append(qb.DocType("Payment Entry"))
|
||||
|
||||
for doctype in doctypes:
|
||||
qb.from_(doctype).delete().where(doctype.company == self.company).run()
|
||||
|
||||
def create_company(self):
|
||||
name = "Test Payment Ledger"
|
||||
company = None
|
||||
if frappe.db.exists("Company", name):
|
||||
company = frappe.get_doc("Company", name)
|
||||
else:
|
||||
company = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Company",
|
||||
"company_name": name,
|
||||
"country": "India",
|
||||
"default_currency": "INR",
|
||||
"create_chart_of_accounts_based_on": "Standard Template",
|
||||
"chart_of_accounts": "Standard",
|
||||
}
|
||||
)
|
||||
company = company.save()
|
||||
self.company = company.name
|
||||
self.cost_center = company.cost_center
|
||||
self.warehouse = "All Warehouses" + " - " + company.abbr
|
||||
self.income_account = company.default_income_account
|
||||
self.expense_account = company.default_expense_account
|
||||
self.debit_to = company.default_receivable_account
|
||||
|
||||
def test_unpaid_invoice_outstanding(self):
|
||||
sinv = create_sales_invoice(
|
||||
company=self.company,
|
||||
debit_to=self.debit_to,
|
||||
expense_account=self.expense_account,
|
||||
cost_center=self.cost_center,
|
||||
income_account=self.income_account,
|
||||
warehouse=self.warehouse,
|
||||
)
|
||||
pe = get_payment_entry(sinv.doctype, sinv.name).save().submit()
|
||||
|
||||
filters = frappe._dict({"company": self.company})
|
||||
columns, data = execute(filters=filters)
|
||||
outstanding = [x for x in data if x.get("against_voucher_no") == "Outstanding:"]
|
||||
self.assertEqual(outstanding[0].get("amount"), 0)
|
Loading…
Reference in New Issue
Block a user