Added customer_outstanding_balance report
This commit is contained in:
parent
f81c0f3ef0
commit
f853b5bb01
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright (c) 2024, Company and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.query_reports["Customer Outstanding Balance"] = {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
fieldname: "from_date",
|
||||||
|
label: __("From Date"),
|
||||||
|
fieldtype: "Date",
|
||||||
|
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "to_date",
|
||||||
|
label: __("To Date"),
|
||||||
|
fieldtype: "Date",
|
||||||
|
default: frappe.datetime.get_today(),
|
||||||
|
reqd: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
formatter: function (value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
|
||||||
|
// Highlight outstanding amount in red if overdue
|
||||||
|
if (column.fieldname == "outstanding" && data && data.outstanding > 0 && data.age > 30) {
|
||||||
|
value = "<span style='color: #d73527; font-weight: bold;'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight age in orange if over 60 days
|
||||||
|
if (column.fieldname == "age" && data && data.age > 60) {
|
||||||
|
value = "<span style='color: #ff6600; font-weight: bold;'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bold formatting for total rows
|
||||||
|
if (data && data.bold) {
|
||||||
|
value = value.bold();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"add_total_row": 1,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2025-07-31 16:21:17.649058",
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"letterhead": null,
|
||||||
|
"modified": "2025-07-31 16:21:17.649058",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Taxes Az",
|
||||||
|
"name": "Customer Outstanding Balance",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Sales Invoice",
|
||||||
|
"report_name": "Customer Outstanding Balance",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "Accounts User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Accounts Manager"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeout": 0
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,215 @@
|
||||||
|
# Copyright (c) 2024, Company and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import flt, getdate, nowdate, date_diff
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
"""Main function for report execution"""
|
||||||
|
columns = get_columns()
|
||||||
|
data = get_data(filters)
|
||||||
|
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
def get_columns():
|
||||||
|
"""Get report columns definition"""
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"label": _("Posting Date"),
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Party Type"),
|
||||||
|
"fieldname": "party_type",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Party"),
|
||||||
|
"fieldname": "party",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"options": "party_type",
|
||||||
|
"width": 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Party Account"),
|
||||||
|
"fieldname": "party_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Account",
|
||||||
|
"width": 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Customer Name"),
|
||||||
|
"fieldname": "customer_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Voucher Type"),
|
||||||
|
"fieldname": "voucher_type",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Voucher No"),
|
||||||
|
"fieldname": "voucher_no",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"options": "voucher_type",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Due Date"),
|
||||||
|
"fieldname": "due_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Invoiced Amount"),
|
||||||
|
"fieldname": "invoiced",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Paid Amount"),
|
||||||
|
"fieldname": "paid",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Credit Note"),
|
||||||
|
"fieldname": "credit_note",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Outstanding Amount"),
|
||||||
|
"fieldname": "outstanding",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Age (Days)"),
|
||||||
|
"fieldname": "age",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"width": 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("0-30"),
|
||||||
|
"fieldname": "range1",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("30-60"),
|
||||||
|
"fieldname": "range2",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("60-90"),
|
||||||
|
"fieldname": "range3",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("90-120"),
|
||||||
|
"fieldname": "range4",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("120-Above"),
|
||||||
|
"fieldname": "range5",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Currency"),
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Currency",
|
||||||
|
"width": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_data(filters):
|
||||||
|
"""Get report data"""
|
||||||
|
conditions = get_conditions(filters)
|
||||||
|
|
||||||
|
# Get Payment Ledger Entries
|
||||||
|
data = frappe.db.sql(f"""
|
||||||
|
SELECT
|
||||||
|
ple.posting_date,
|
||||||
|
ple.party_type,
|
||||||
|
ple.party,
|
||||||
|
ple.account as party_account,
|
||||||
|
COALESCE(c.customer_name, s.supplier_name, ple.party) as customer_name,
|
||||||
|
ple.voucher_type,
|
||||||
|
ple.voucher_no,
|
||||||
|
ple.due_date,
|
||||||
|
ple.account_currency as currency,
|
||||||
|
SUM(CASE WHEN ple.amount > 0 THEN ple.amount ELSE 0 END) as invoiced,
|
||||||
|
SUM(CASE WHEN ple.amount < 0 THEN ABS(ple.amount) ELSE 0 END) as paid,
|
||||||
|
0 as credit_note,
|
||||||
|
SUM(ple.amount) as outstanding
|
||||||
|
FROM
|
||||||
|
`tabPayment Ledger Entry` ple
|
||||||
|
LEFT JOIN
|
||||||
|
`tabCustomer` c ON c.name = ple.party AND ple.party_type = 'Customer'
|
||||||
|
LEFT JOIN
|
||||||
|
`tabSupplier` s ON s.name = ple.party AND ple.party_type = 'Supplier'
|
||||||
|
WHERE
|
||||||
|
ple.delinked = 0
|
||||||
|
{conditions}
|
||||||
|
GROUP BY
|
||||||
|
ple.account, ple.voucher_type, ple.voucher_no, ple.party
|
||||||
|
HAVING
|
||||||
|
ABS(outstanding) >= 0.01
|
||||||
|
ORDER BY
|
||||||
|
ple.posting_date DESC, ple.party
|
||||||
|
""", filters, as_dict=1)
|
||||||
|
|
||||||
|
# Calculate age and age ranges
|
||||||
|
today = getdate(nowdate())
|
||||||
|
|
||||||
|
for row in data:
|
||||||
|
# Calculate age based on due date or posting date
|
||||||
|
age_date = row.due_date or row.posting_date
|
||||||
|
if age_date:
|
||||||
|
row.age = date_diff(today, age_date)
|
||||||
|
else:
|
||||||
|
row.age = 0
|
||||||
|
|
||||||
|
# Initialize age ranges
|
||||||
|
row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0
|
||||||
|
|
||||||
|
# Allocate outstanding amount to appropriate age range
|
||||||
|
outstanding = flt(row.outstanding)
|
||||||
|
if outstanding > 0:
|
||||||
|
if row.age <= 30:
|
||||||
|
row.range1 = outstanding
|
||||||
|
elif row.age <= 60:
|
||||||
|
row.range2 = outstanding
|
||||||
|
elif row.age <= 90:
|
||||||
|
row.range3 = outstanding
|
||||||
|
elif row.age <= 120:
|
||||||
|
row.range4 = outstanding
|
||||||
|
else:
|
||||||
|
row.range5 = outstanding
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_conditions(filters):
|
||||||
|
"""Build WHERE conditions based on filters"""
|
||||||
|
conditions = ""
|
||||||
|
|
||||||
|
if filters.get("from_date"):
|
||||||
|
conditions += " AND ple.posting_date >= %(from_date)s"
|
||||||
|
|
||||||
|
if filters.get("to_date"):
|
||||||
|
conditions += " AND ple.posting_date <= %(to_date)s"
|
||||||
|
|
||||||
|
return conditions
|
||||||
Loading…
Reference in New Issue