Remade customer balance report
This commit is contained in:
parent
6a8f424671
commit
b498e7100c
|
|
@ -3,6 +3,14 @@
|
||||||
|
|
||||||
frappe.query_reports["Customer Outstanding Balance"] = {
|
frappe.query_reports["Customer Outstanding Balance"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"label": __("Company"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1,
|
||||||
|
"default": frappe.defaults.get_user_default("Company")
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "from_date",
|
"fieldname": "from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
|
|
@ -11,40 +19,51 @@ frappe.query_reports["Customer Outstanding Balance"] = {
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "to_date",
|
"fieldname": "to_date",
|
||||||
"label": __("To Date"),
|
"label": __("To Date"),
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"default": frappe.datetime.get_today(),
|
"default": frappe.datetime.get_today(),
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "company",
|
"fieldname": "customer",
|
||||||
"label": __("Company"),
|
"label": __("Customer"),
|
||||||
|
"fieldtype": "MultiSelectList",
|
||||||
|
"get_data": function (txt) {
|
||||||
|
return frappe.db.get_link_options("Customer", txt);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_group",
|
||||||
|
"label": __("Customer Group"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Company",
|
"options": "Customer Group"
|
||||||
"reqd": 1,
|
|
||||||
"default": frappe.defaults.get_user_default("Company")
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"formatter": function (value, row, column, data, default_formatter) {
|
"formatter": function (value, row, column, data, default_formatter) {
|
||||||
value = default_formatter(value, row, column, data);
|
value = default_formatter(value, row, column, data);
|
||||||
|
|
||||||
// Highlight outstanding amounts in red if overdue
|
// Highlight opening balance in blue
|
||||||
if (column.fieldname == "outstanding" && data && data.outstanding > 0 && data.age > 30) {
|
if (column.fieldname == "opening_balance" && data && data.opening_balance > 0) {
|
||||||
value = "<span style='color: #d32f2f; font-weight: bold;'>" + value + "</span>";
|
value = "<span style='color: #1f77b4; font-weight: bold;'>" + value + "</span>";
|
||||||
}
|
|
||||||
|
|
||||||
// Highlight age in orange if > 60 days
|
|
||||||
if (column.fieldname == "age" && data && data.age > 60) {
|
|
||||||
value = "<span style='color: #f57c00; font-weight: bold;'>" + value + "</span>";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bold totals
|
// Highlight payments in green
|
||||||
if (data && data.bold) {
|
if (column.fieldname == "payments" && data && data.payments > 0) {
|
||||||
value = value.bold();
|
value = "<span style='color: #2ca02c; font-weight: bold;'>" + value + "</span>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Highlight new invoices in orange
|
||||||
|
if (column.fieldname == "new_invoices" && data && data.new_invoices > 0) {
|
||||||
|
value = "<span style='color: #ff7f0e; font-weight: bold;'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight closing balance in dark red
|
||||||
|
if (column.fieldname == "closing_balance" && data && data.closing_balance > 0) {
|
||||||
|
value = "<span style='color: #d62728; font-weight: bold;'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": "Yes",
|
"is_standard": "Yes",
|
||||||
"letterhead": null,
|
"letterhead": null,
|
||||||
"modified": "2025-07-31 19:49:06.707566",
|
"modified": "2025-07-31 19:36:23.704645",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Taxes Az",
|
"module": "Taxes Az",
|
||||||
"name": "Customer Outstanding Balance",
|
"name": "Customer Outstanding Balance",
|
||||||
|
|
@ -25,5 +25,6 @@
|
||||||
{
|
{
|
||||||
"role": "Accounts Manager"
|
"role": "Accounts Manager"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"timeout": 0
|
||||||
}
|
}
|
||||||
|
|
@ -2,19 +2,10 @@
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, qb
|
from frappe import _
|
||||||
from frappe.utils import cint, flt, getdate, nowdate
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
"""Main function for report execution"""
|
"""Main function for report execution"""
|
||||||
if not filters:
|
|
||||||
filters = {}
|
|
||||||
|
|
||||||
# Set default company if not provided
|
|
||||||
if not filters.get("company"):
|
|
||||||
filters["company"] = frappe.db.get_single_value("Global Defaults", "default_company")
|
|
||||||
|
|
||||||
columns = get_columns()
|
columns = get_columns()
|
||||||
data = get_data(filters)
|
data = get_data(filters)
|
||||||
|
|
||||||
|
|
@ -24,14 +15,8 @@ def get_columns():
|
||||||
"""Get report columns definition"""
|
"""Get report columns definition"""
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"label": _("Posting Date"),
|
"label": _("Customer"),
|
||||||
"fieldname": "posting_date",
|
"fieldname": "customer",
|
||||||
"fieldtype": "Date",
|
|
||||||
"width": 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Party"),
|
|
||||||
"fieldname": "party",
|
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Customer",
|
"options": "Customer",
|
||||||
"width": 150
|
"width": 150
|
||||||
|
|
@ -40,303 +25,196 @@ def get_columns():
|
||||||
"label": _("Customer Name"),
|
"label": _("Customer Name"),
|
||||||
"fieldname": "customer_name",
|
"fieldname": "customer_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"width": 150
|
"width": 200
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Voucher Type"),
|
"label": _("Opening Balance"),
|
||||||
"fieldname": "voucher_type",
|
"fieldname": "opening_balance",
|
||||||
"fieldtype": "Data",
|
|
||||||
"width": 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Voucher No"),
|
|
||||||
"fieldname": "voucher_no",
|
|
||||||
"fieldtype": "Dynamic Link",
|
|
||||||
"options": "voucher_type",
|
|
||||||
"width": 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Due Date"),
|
|
||||||
"fieldname": "due_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"width": 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Invoiced Amount"),
|
|
||||||
"fieldname": "invoiced",
|
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"width": 120
|
"width": 140
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Paid Amount"),
|
"label": _("Payments (Decrease)"),
|
||||||
"fieldname": "paid",
|
"fieldname": "payments",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"width": 120
|
"width": 140
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Credit Note"),
|
"label": _("New Invoices (Increase)"),
|
||||||
"fieldname": "credit_note",
|
"fieldname": "new_invoices",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"width": 120
|
"width": 140
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Outstanding Amount"),
|
"label": _("Closing Balance"),
|
||||||
"fieldname": "outstanding",
|
"fieldname": "closing_balance",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"width": 130
|
"width": 140
|
||||||
},
|
|
||||||
{
|
|
||||||
"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+"),
|
|
||||||
"fieldname": "range4",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"width": 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Currency"),
|
|
||||||
"fieldname": "currency",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"options": "Currency",
|
|
||||||
"width": 80
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_data(filters):
|
def get_data(filters):
|
||||||
"""Get report data"""
|
"""Get report data"""
|
||||||
# Get Payment Ledger Entries
|
conditions = get_conditions(filters)
|
||||||
ple_entries = get_ple_entries(filters)
|
|
||||||
|
|
||||||
# Build voucher balance
|
# Get opening balance (before from_date)
|
||||||
voucher_balance = build_voucher_balance(ple_entries)
|
opening_balance_data = frappe.db.sql(f"""
|
||||||
|
SELECT
|
||||||
|
ple.party as customer,
|
||||||
|
SUM(ple.amount) as opening_balance
|
||||||
|
FROM
|
||||||
|
`tabPayment Ledger Entry` ple
|
||||||
|
LEFT JOIN
|
||||||
|
`tabAccount` acc ON acc.name = ple.account
|
||||||
|
WHERE
|
||||||
|
ple.delinked = 0
|
||||||
|
AND ple.posting_date < %(from_date)s
|
||||||
|
AND acc.account_type = 'Receivable'
|
||||||
|
AND ple.company = %(company)s
|
||||||
|
{conditions}
|
||||||
|
GROUP BY
|
||||||
|
ple.party
|
||||||
|
HAVING
|
||||||
|
ABS(opening_balance) >= 0.01
|
||||||
|
""", filters, as_dict=1)
|
||||||
|
|
||||||
# Get additional details
|
# Get movements during the period
|
||||||
get_invoice_details(voucher_balance, filters)
|
period_data = frappe.db.sql(f"""
|
||||||
get_customer_details(voucher_balance)
|
SELECT
|
||||||
|
ple.party as customer,
|
||||||
|
SUM(CASE WHEN ple.amount > 0 THEN ple.amount ELSE 0 END) as new_invoices,
|
||||||
|
SUM(CASE WHEN ple.amount < 0 THEN ABS(ple.amount) ELSE 0 END) as payments
|
||||||
|
FROM
|
||||||
|
`tabPayment Ledger Entry` ple
|
||||||
|
LEFT JOIN
|
||||||
|
`tabAccount` acc ON acc.name = ple.account
|
||||||
|
WHERE
|
||||||
|
ple.delinked = 0
|
||||||
|
AND ple.posting_date >= %(from_date)s
|
||||||
|
AND ple.posting_date <= %(to_date)s
|
||||||
|
AND acc.account_type = 'Receivable'
|
||||||
|
AND ple.company = %(company)s
|
||||||
|
{conditions}
|
||||||
|
GROUP BY
|
||||||
|
ple.party
|
||||||
|
""", filters, as_dict=1)
|
||||||
|
|
||||||
# Build final data
|
# Get closing balance (up to to_date)
|
||||||
data = []
|
closing_balance_data = frappe.db.sql(f"""
|
||||||
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
|
SELECT
|
||||||
|
ple.party as customer,
|
||||||
|
SUM(ple.amount) as closing_balance
|
||||||
|
FROM
|
||||||
|
`tabPayment Ledger Entry` ple
|
||||||
|
LEFT JOIN
|
||||||
|
`tabAccount` acc ON acc.name = ple.account
|
||||||
|
WHERE
|
||||||
|
ple.delinked = 0
|
||||||
|
AND ple.posting_date <= %(to_date)s
|
||||||
|
AND acc.account_type = 'Receivable'
|
||||||
|
AND ple.company = %(company)s
|
||||||
|
{conditions}
|
||||||
|
GROUP BY
|
||||||
|
ple.party
|
||||||
|
HAVING
|
||||||
|
ABS(closing_balance) >= 0.01
|
||||||
|
""", filters, as_dict=1)
|
||||||
|
|
||||||
for key, row in voucher_balance.items():
|
# Combine all data
|
||||||
# Calculate outstanding
|
result = combine_data(opening_balance_data, period_data, closing_balance_data)
|
||||||
row.outstanding = flt(row.invoiced - row.paid - row.credit_note, 2)
|
|
||||||
|
|
||||||
# Only include rows with outstanding amount
|
|
||||||
if abs(row.outstanding) >= 0.01:
|
|
||||||
# Set aging
|
|
||||||
set_ageing(row, filters)
|
|
||||||
|
|
||||||
# Set currency
|
|
||||||
row.currency = company_currency
|
|
||||||
|
|
||||||
data.append(row)
|
|
||||||
|
|
||||||
# Sort by posting date and party
|
# Get customer names and other details
|
||||||
data = sorted(data, key=lambda x: (x.get('posting_date'), x.get('party')))
|
add_customer_details(result)
|
||||||
|
|
||||||
return data
|
return result
|
||||||
|
|
||||||
def get_ple_entries(filters):
|
def get_conditions(filters):
|
||||||
"""Get Payment Ledger Entries"""
|
"""Build WHERE conditions based on filters"""
|
||||||
ple = qb.DocType("Payment Ledger Entry")
|
conditions = ""
|
||||||
|
|
||||||
# Build query conditions
|
if filters.get("customer"):
|
||||||
conditions = [
|
customer_list = "', '".join(filters.get("customer"))
|
||||||
ple.delinked == 0,
|
conditions += f" AND ple.party IN ('{customer_list}')"
|
||||||
ple.company == filters.get("company"),
|
|
||||||
ple.party_type == "Customer" # Only customers for receivables
|
|
||||||
]
|
|
||||||
|
|
||||||
# Date filter
|
if filters.get("customer_group"):
|
||||||
if filters.get("from_date"):
|
conditions += " AND ple.party IN (SELECT name FROM `tabCustomer` WHERE customer_group = %(customer_group)s)"
|
||||||
conditions.append(ple.posting_date >= filters.get("from_date"))
|
|
||||||
if filters.get("to_date"):
|
|
||||||
conditions.append(ple.posting_date <= filters.get("to_date"))
|
|
||||||
|
|
||||||
# Get receivable accounts
|
return conditions
|
||||||
receivable_accounts = frappe.db.get_all(
|
|
||||||
"Account",
|
|
||||||
filters={
|
|
||||||
"account_type": "Receivable",
|
|
||||||
"company": filters.get("company")
|
|
||||||
},
|
|
||||||
pluck="name"
|
|
||||||
)
|
|
||||||
|
|
||||||
if receivable_accounts:
|
|
||||||
conditions.append(ple.account.isin(receivable_accounts))
|
|
||||||
|
|
||||||
# Execute query
|
|
||||||
query = qb.from_(ple).select(
|
|
||||||
ple.account,
|
|
||||||
ple.voucher_type,
|
|
||||||
ple.voucher_no,
|
|
||||||
ple.against_voucher_type,
|
|
||||||
ple.against_voucher_no,
|
|
||||||
ple.party_type,
|
|
||||||
ple.party,
|
|
||||||
ple.posting_date,
|
|
||||||
ple.due_date,
|
|
||||||
ple.amount,
|
|
||||||
ple.account_currency
|
|
||||||
).orderby(ple.posting_date, ple.party)
|
|
||||||
|
|
||||||
# Apply conditions
|
|
||||||
for condition in conditions:
|
|
||||||
query = query.where(condition)
|
|
||||||
|
|
||||||
return query.run(as_dict=True)
|
|
||||||
|
|
||||||
def build_voucher_balance(ple_entries):
|
def combine_data(opening_data, period_data, closing_data):
|
||||||
"""Build voucher balance from PLE entries"""
|
"""Combine all datasets into final result"""
|
||||||
voucher_balance = OrderedDict()
|
result_dict = {}
|
||||||
|
|
||||||
for ple in ple_entries:
|
# Add opening balances
|
||||||
# Create key for grouping - use against_voucher for payments
|
for row in opening_data:
|
||||||
if ple.against_voucher_no and ple.voucher_no != ple.against_voucher_no:
|
customer = row['customer']
|
||||||
# This is a payment against an invoice
|
result_dict[customer] = {
|
||||||
key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
'customer': customer,
|
||||||
voucher_type = ple.against_voucher_type
|
'opening_balance': row['opening_balance'] or 0.0,
|
||||||
voucher_no = ple.against_voucher_no
|
'payments': 0.0,
|
||||||
else:
|
'new_invoices': 0.0,
|
||||||
# This is an invoice or standalone entry
|
'closing_balance': 0.0
|
||||||
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
|
}
|
||||||
voucher_type = ple.voucher_type
|
|
||||||
voucher_no = ple.voucher_no
|
|
||||||
|
|
||||||
if key not in voucher_balance:
|
|
||||||
voucher_balance[key] = frappe._dict({
|
|
||||||
'account': ple.account,
|
|
||||||
'voucher_type': voucher_type,
|
|
||||||
'voucher_no': voucher_no,
|
|
||||||
'party': ple.party,
|
|
||||||
'party_type': ple.party_type,
|
|
||||||
'posting_date': ple.posting_date,
|
|
||||||
'due_date': ple.due_date,
|
|
||||||
'account_currency': ple.account_currency,
|
|
||||||
'invoiced': 0.0,
|
|
||||||
'paid': 0.0,
|
|
||||||
'credit_note': 0.0,
|
|
||||||
'outstanding': 0.0
|
|
||||||
})
|
|
||||||
|
|
||||||
row = voucher_balance[key]
|
|
||||||
amount = ple.amount
|
|
||||||
|
|
||||||
# Update balances based on transaction type
|
|
||||||
if amount > 0:
|
|
||||||
# Positive amount - invoice or debit entry
|
|
||||||
if ple.voucher_type in ["Journal Entry", "Payment Entry"] and ple.voucher_no != ple.against_voucher_no:
|
|
||||||
row.paid -= amount # Payment received against invoice
|
|
||||||
else:
|
|
||||||
row.invoiced += amount # Invoice amount
|
|
||||||
else:
|
|
||||||
# Negative amount - payment or credit note
|
|
||||||
if ple.voucher_type in ["Sales Invoice"] and ple.voucher_no == ple.against_voucher_no:
|
|
||||||
# Return/credit note against same invoice
|
|
||||||
row.credit_note -= amount
|
|
||||||
else:
|
|
||||||
# Payment made
|
|
||||||
row.paid -= amount
|
|
||||||
|
|
||||||
return voucher_balance
|
# Add period movements
|
||||||
|
for row in period_data:
|
||||||
|
customer = row['customer']
|
||||||
|
if customer not in result_dict:
|
||||||
|
result_dict[customer] = {
|
||||||
|
'customer': customer,
|
||||||
|
'opening_balance': 0.0,
|
||||||
|
'payments': 0.0,
|
||||||
|
'new_invoices': 0.0,
|
||||||
|
'closing_balance': 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
result_dict[customer]['payments'] = row['payments'] or 0.0
|
||||||
|
result_dict[customer]['new_invoices'] = row['new_invoices'] or 0.0
|
||||||
|
|
||||||
|
# Add closing balances
|
||||||
|
for row in closing_data:
|
||||||
|
customer = row['customer']
|
||||||
|
if customer not in result_dict:
|
||||||
|
result_dict[customer] = {
|
||||||
|
'customer': customer,
|
||||||
|
'opening_balance': 0.0,
|
||||||
|
'payments': 0.0,
|
||||||
|
'new_invoices': 0.0,
|
||||||
|
'closing_balance': 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
result_dict[customer]['closing_balance'] = row['closing_balance'] or 0.0
|
||||||
|
|
||||||
|
# Convert to list and filter out zero balances
|
||||||
|
result = []
|
||||||
|
for customer, data in result_dict.items():
|
||||||
|
# Only include if there's some activity or outstanding balance
|
||||||
|
if (abs(data['opening_balance']) >= 0.01 or
|
||||||
|
abs(data['closing_balance']) >= 0.01 or
|
||||||
|
abs(data['new_invoices']) >= 0.01 or
|
||||||
|
abs(data['payments']) >= 0.01):
|
||||||
|
result.append(data)
|
||||||
|
|
||||||
|
return sorted(result, key=lambda x: x['customer'])
|
||||||
|
|
||||||
def get_invoice_details(voucher_balance, filters):
|
def add_customer_details(data):
|
||||||
"""Get additional invoice details"""
|
"""Add customer names and other details"""
|
||||||
invoice_names = []
|
if not data:
|
||||||
|
return
|
||||||
|
|
||||||
for key, row in voucher_balance.items():
|
customer_names = {}
|
||||||
if row.voucher_type == "Sales Invoice":
|
customers = [row['customer'] for row in data]
|
||||||
invoice_names.append(row.voucher_no)
|
|
||||||
|
|
||||||
if invoice_names:
|
|
||||||
# Get invoice details
|
|
||||||
invoice_details = frappe.db.get_all(
|
|
||||||
"Sales Invoice",
|
|
||||||
filters={
|
|
||||||
"name": ["in", invoice_names],
|
|
||||||
"company": filters.get("company")
|
|
||||||
},
|
|
||||||
fields=["name", "due_date", "po_no"]
|
|
||||||
)
|
|
||||||
|
|
||||||
invoice_dict = {d.name: d for d in invoice_details}
|
|
||||||
|
|
||||||
# Update voucher balance with invoice details
|
|
||||||
for key, row in voucher_balance.items():
|
|
||||||
if row.voucher_type == "Sales Invoice" and row.voucher_no in invoice_dict:
|
|
||||||
details = invoice_dict[row.voucher_no]
|
|
||||||
if not row.due_date and details.due_date:
|
|
||||||
row.due_date = details.due_date
|
|
||||||
row.po_no = details.po_no
|
|
||||||
|
|
||||||
def get_customer_details(voucher_balance):
|
|
||||||
"""Get customer details"""
|
|
||||||
customers = set()
|
|
||||||
|
|
||||||
for key, row in voucher_balance.items():
|
|
||||||
if row.party_type == "Customer":
|
|
||||||
customers.add(row.party)
|
|
||||||
|
|
||||||
if customers:
|
if customers:
|
||||||
customer_details = frappe.db.get_all(
|
customer_details = frappe.db.sql("""
|
||||||
"Customer",
|
SELECT name, customer_name
|
||||||
filters={"name": ["in", list(customers)]},
|
FROM `tabCustomer`
|
||||||
fields=["name", "customer_name"]
|
WHERE name IN ({})
|
||||||
)
|
""".format(', '.join(['%s'] * len(customers))), customers, as_dict=1)
|
||||||
|
|
||||||
customer_dict = {d.name: d for d in customer_details}
|
for customer in customer_details:
|
||||||
|
customer_names[customer['name']] = customer['customer_name']
|
||||||
# Update voucher balance with customer details
|
|
||||||
for key, row in voucher_balance.items():
|
|
||||||
if row.party in customer_dict:
|
|
||||||
row.customer_name = customer_dict[row.party].customer_name
|
|
||||||
|
|
||||||
def set_ageing(row, filters):
|
|
||||||
"""Set aging buckets"""
|
|
||||||
# Initialize aging fields
|
|
||||||
row.age = 0
|
|
||||||
row.range1 = 0.0 # 0-30
|
|
||||||
row.range2 = 0.0 # 30-60
|
|
||||||
row.range3 = 0.0 # 60-90
|
|
||||||
row.range4 = 0.0 # 90+
|
|
||||||
|
|
||||||
# Use due date if available, otherwise posting date
|
# Update data with customer names
|
||||||
entry_date = row.due_date or row.posting_date
|
for row in data:
|
||||||
age_as_on = getdate(filters.get("to_date") or nowdate())
|
row['customer_name'] = customer_names.get(row['customer'], row['customer'])
|
||||||
|
|
||||||
if entry_date:
|
|
||||||
row.age = (age_as_on - getdate(entry_date)).days
|
|
||||||
|
|
||||||
# Assign to appropriate aging bucket
|
|
||||||
if row.age <= 30:
|
|
||||||
row.range1 = row.outstanding
|
|
||||||
elif row.age <= 60:
|
|
||||||
row.range2 = row.outstanding
|
|
||||||
elif row.age <= 90:
|
|
||||||
row.range3 = row.outstanding
|
|
||||||
else:
|
|
||||||
row.range4 = row.outstanding
|
|
||||||
Loading…
Reference in New Issue