449 lines
12 KiB
Python
449 lines
12 KiB
Python
import frappe
|
|
from frappe import _
|
|
from frappe.utils import flt
|
|
|
|
|
|
INACTIVE_REGISTRATION_STATUSES = (
|
|
"Cancelled",
|
|
"Transferred",
|
|
"Refunded",
|
|
)
|
|
|
|
CONFIRMED_PAYMENT_STATUSES = (
|
|
"Paid",
|
|
"Reconciled",
|
|
)
|
|
|
|
REFUND_STATUS = "Refunded"
|
|
|
|
|
|
def _edition_filter(alias, event_edition):
|
|
if event_edition:
|
|
return f" and {alias}.event_edition = %(event_edition)s"
|
|
return ""
|
|
|
|
|
|
def _date_filter(alias, from_date=None, to_date=None):
|
|
filters = ""
|
|
|
|
if from_date:
|
|
filters += f" and date({alias}.payment_date) >= %(from_date)s"
|
|
|
|
if to_date:
|
|
filters += f" and date({alias}.payment_date) <= %(to_date)s"
|
|
|
|
return filters
|
|
|
|
|
|
def _payment_params(event_edition=None, from_date=None, to_date=None):
|
|
return {
|
|
"event_edition": event_edition,
|
|
"from_date": from_date,
|
|
"to_date": to_date,
|
|
"inactive": INACTIVE_REGISTRATION_STATUSES,
|
|
"confirmed": CONFIRMED_PAYMENT_STATUSES,
|
|
}
|
|
|
|
|
|
def _single_value(sql, params):
|
|
rows = frappe.db.sql(sql, params, as_dict=True)
|
|
if not rows:
|
|
return 0
|
|
|
|
return rows[0].value or 0
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_payment_editions():
|
|
return frappe.get_all(
|
|
"Event Edition",
|
|
fields=[
|
|
"name",
|
|
"edition_name",
|
|
"edition_code",
|
|
"event_date",
|
|
"status",
|
|
],
|
|
order_by="event_date desc, modified desc",
|
|
limit_page_length=100,
|
|
)
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_payment_metrics(
|
|
event_edition=None,
|
|
from_date=None,
|
|
to_date=None,
|
|
gateway=None,
|
|
status=None,
|
|
):
|
|
params = _payment_params(event_edition, from_date, to_date)
|
|
|
|
registration_scope = """
|
|
r.registration_status not in %(inactive)s
|
|
"""
|
|
|
|
if event_edition:
|
|
registration_scope += " and r.event_edition = %(event_edition)s"
|
|
|
|
billed = _single_value(
|
|
f"""
|
|
select coalesce(sum(r.amount_due), 0) as value
|
|
from `tabRegistration` r
|
|
where {registration_scope}
|
|
""",
|
|
params,
|
|
)
|
|
|
|
transaction_scope = """
|
|
r.registration_status not in %(inactive)s
|
|
"""
|
|
|
|
if event_edition:
|
|
transaction_scope += " and r.event_edition = %(event_edition)s"
|
|
|
|
transaction_scope += _date_filter("p", from_date, to_date)
|
|
|
|
if gateway:
|
|
transaction_scope += " and p.gateway = %(gateway)s"
|
|
params["gateway"] = gateway
|
|
|
|
if status:
|
|
transaction_scope += " and p.status = %(status)s"
|
|
params["status"] = status
|
|
|
|
received = _single_value(
|
|
f"""
|
|
select coalesce(sum(p.amount), 0) as value
|
|
from `tabPayment Transaction` p
|
|
join `tabRegistration` r on r.name = p.registration
|
|
where {transaction_scope}
|
|
and p.status in %(confirmed)s
|
|
""",
|
|
params,
|
|
)
|
|
|
|
refunded = _single_value(
|
|
f"""
|
|
select coalesce(sum(p.amount), 0) as value
|
|
from `tabPayment Transaction` p
|
|
join `tabRegistration` r on r.name = p.registration
|
|
where {transaction_scope}
|
|
and p.status = 'Refunded'
|
|
""",
|
|
params,
|
|
)
|
|
|
|
paid_registrations = _single_value(
|
|
f"""
|
|
select count(*) as value
|
|
from `tabRegistration` r
|
|
where {registration_scope}
|
|
and r.payment_status = 'Paid'
|
|
""",
|
|
params,
|
|
)
|
|
|
|
unpaid_registrations = _single_value(
|
|
f"""
|
|
select count(*) as value
|
|
from `tabRegistration` r
|
|
where {registration_scope}
|
|
and r.payment_status in (
|
|
'Unpaid',
|
|
'Pending',
|
|
'Partially Paid',
|
|
'Failed'
|
|
)
|
|
""",
|
|
params,
|
|
)
|
|
|
|
pending_transactions = _single_value(
|
|
f"""
|
|
select count(*) as value
|
|
from `tabPayment Transaction` p
|
|
join `tabRegistration` r on r.name = p.registration
|
|
where {transaction_scope}
|
|
and p.status = 'Pending'
|
|
""",
|
|
params,
|
|
)
|
|
|
|
failed_transactions = _single_value(
|
|
f"""
|
|
select count(*) as value
|
|
from `tabPayment Transaction` p
|
|
join `tabRegistration` r on r.name = p.registration
|
|
where {transaction_scope}
|
|
and p.status = 'Failed'
|
|
""",
|
|
params,
|
|
)
|
|
|
|
refunded_transactions = _single_value(
|
|
f"""
|
|
select count(*) as value
|
|
from `tabPayment Transaction` p
|
|
join `tabRegistration` r on r.name = p.registration
|
|
where {transaction_scope}
|
|
and p.status = 'Refunded'
|
|
""",
|
|
params,
|
|
)
|
|
|
|
reconciliation_exceptions = _single_value(
|
|
f"""
|
|
select count(*) as value
|
|
from `tabRegistration` r
|
|
left join (
|
|
select registration, sum(amount) as paid
|
|
from `tabPayment Transaction`
|
|
where status in %(confirmed)s
|
|
group by registration
|
|
) p on p.registration = r.name
|
|
where {registration_scope}
|
|
and abs(coalesce(r.amount_due, 0) - coalesce(p.paid, 0)) > 0.01
|
|
""",
|
|
params,
|
|
)
|
|
|
|
total_received = flt(received)
|
|
total_refunded = flt(refunded)
|
|
|
|
return {
|
|
"total_billed": flt(billed),
|
|
"total_received": total_received,
|
|
"refunded_amount": total_refunded,
|
|
"net_received": total_received - total_refunded,
|
|
"outstanding": max(flt(billed) - total_received, 0),
|
|
"paid_registrations": int(paid_registrations),
|
|
"unpaid_registrations": int(unpaid_registrations),
|
|
"pending_transactions": int(pending_transactions),
|
|
"failed_transactions": int(failed_transactions),
|
|
"refunded_transactions": int(refunded_transactions),
|
|
"reconciliation_exceptions": int(reconciliation_exceptions),
|
|
}
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_payment_breakdown(
|
|
event_edition=None,
|
|
from_date=None,
|
|
to_date=None,
|
|
):
|
|
params = _payment_params(event_edition, from_date, to_date)
|
|
|
|
edition_sql = ""
|
|
if event_edition:
|
|
edition_sql = " and r.event_edition = %(event_edition)s"
|
|
|
|
date_sql = _date_filter("p", from_date, to_date)
|
|
|
|
statuses = frappe.db.sql(
|
|
f"""
|
|
select
|
|
p.status,
|
|
count(*) as transaction_count,
|
|
coalesce(sum(p.amount), 0) as amount
|
|
from `tabPayment Transaction` p
|
|
join `tabRegistration` r on r.name = p.registration
|
|
where r.registration_status not in %(inactive)s
|
|
{edition_sql}
|
|
{date_sql}
|
|
group by p.status
|
|
order by p.status
|
|
""",
|
|
params,
|
|
as_dict=True,
|
|
)
|
|
|
|
gateways = frappe.db.sql(
|
|
f"""
|
|
select
|
|
p.gateway,
|
|
count(*) as transaction_count,
|
|
coalesce(sum(p.amount), 0) as amount,
|
|
sum(case when p.status in %(confirmed)s then 1 else 0 end) as paid_count,
|
|
sum(case when p.status = 'Pending' then 1 else 0 end) as pending_count,
|
|
sum(case when p.status = 'Failed' then 1 else 0 end) as failed_count
|
|
from `tabPayment Transaction` p
|
|
join `tabRegistration` r on r.name = p.registration
|
|
where r.registration_status not in %(inactive)s
|
|
{edition_sql}
|
|
{date_sql}
|
|
group by p.gateway
|
|
order by amount desc
|
|
""",
|
|
params,
|
|
as_dict=True,
|
|
)
|
|
|
|
return {
|
|
"statuses": statuses,
|
|
"gateways": gateways,
|
|
}
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_payment_transactions(
|
|
event_edition=None,
|
|
from_date=None,
|
|
to_date=None,
|
|
gateway=None,
|
|
status=None,
|
|
search=None,
|
|
page=1,
|
|
page_length=25,
|
|
):
|
|
page = max(int(page or 1), 1)
|
|
page_length = min(max(int(page_length or 25), 1), 100)
|
|
offset = (page - 1) * page_length
|
|
|
|
params = _payment_params(event_edition, from_date, to_date)
|
|
|
|
conditions = [
|
|
"r.registration_status not in %(inactive)s",
|
|
]
|
|
|
|
if event_edition:
|
|
conditions.append("r.event_edition = %(event_edition)s")
|
|
|
|
if from_date:
|
|
conditions.append("date(p.payment_date) >= %(from_date)s")
|
|
|
|
if to_date:
|
|
conditions.append("date(p.payment_date) <= %(to_date)s")
|
|
|
|
if gateway:
|
|
conditions.append("p.gateway = %(gateway)s")
|
|
params["gateway"] = gateway
|
|
|
|
if status:
|
|
conditions.append("p.status = %(status)s")
|
|
params["status"] = status
|
|
|
|
if search:
|
|
conditions.append(
|
|
"""
|
|
(
|
|
p.name like %(search)s
|
|
or p.gateway_transaction_id like %(search)s
|
|
or p.registration like %(search)s
|
|
or part.full_name like %(search)s
|
|
)
|
|
"""
|
|
)
|
|
params["search"] = f"%{search}%"
|
|
|
|
where_sql = " and ".join(conditions)
|
|
|
|
rows = frappe.db.sql(
|
|
f"""
|
|
select
|
|
p.name,
|
|
p.registration,
|
|
p.gateway,
|
|
p.gateway_transaction_id,
|
|
p.amount,
|
|
p.currency,
|
|
p.status,
|
|
p.payment_date,
|
|
p.failure_reason,
|
|
r.event_edition,
|
|
r.race_category,
|
|
r.amount_due,
|
|
r.payment_status,
|
|
part.full_name as participant_name,
|
|
e.edition_name,
|
|
c.category_name
|
|
from `tabPayment Transaction` p
|
|
join `tabRegistration` r on r.name = p.registration
|
|
left join `tabParticipant` part on part.name = r.participant
|
|
left join `tabEvent Edition` e on e.name = r.event_edition
|
|
left join `tabRace Category` c on c.name = r.race_category
|
|
where {where_sql}
|
|
order by p.payment_date desc, p.modified desc
|
|
limit %(limit)s offset %(offset)s
|
|
""",
|
|
{
|
|
**params,
|
|
"limit": page_length,
|
|
"offset": offset,
|
|
},
|
|
as_dict=True,
|
|
)
|
|
|
|
total = _single_value(
|
|
f"""
|
|
select count(*) as value
|
|
from `tabPayment Transaction` p
|
|
join `tabRegistration` r on r.name = p.registration
|
|
left join `tabParticipant` part on part.name = r.participant
|
|
where {where_sql}
|
|
""",
|
|
params,
|
|
)
|
|
|
|
return {
|
|
"rows": rows,
|
|
"total": int(total),
|
|
"page": page,
|
|
"page_length": page_length,
|
|
}
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_payment_exceptions(
|
|
event_edition=None,
|
|
page=1,
|
|
page_length=25,
|
|
):
|
|
page = max(int(page or 1), 1)
|
|
page_length = min(max(int(page_length or 25), 1), 100)
|
|
offset = (page - 1) * page_length
|
|
|
|
params = {
|
|
"event_edition": event_edition,
|
|
"inactive": INACTIVE_REGISTRATION_STATUSES,
|
|
"confirmed": CONFIRMED_PAYMENT_STATUSES,
|
|
"limit": page_length,
|
|
"offset": offset,
|
|
}
|
|
|
|
edition_sql = ""
|
|
if event_edition:
|
|
edition_sql = " and r.event_edition = %(event_edition)s"
|
|
|
|
rows = frappe.db.sql(
|
|
f"""
|
|
select
|
|
r.name as registration,
|
|
r.event_edition,
|
|
r.amount_due,
|
|
coalesce(pay.paid, 0) as paid,
|
|
(coalesce(r.amount_due, 0) - coalesce(pay.paid, 0)) as difference,
|
|
r.payment_status,
|
|
part.full_name as participant_name,
|
|
e.edition_name
|
|
from `tabRegistration` r
|
|
left join (
|
|
select registration, sum(amount) as paid
|
|
from `tabPayment Transaction`
|
|
where status in %(confirmed)s
|
|
group by registration
|
|
) pay on pay.registration = r.name
|
|
left join `tabParticipant` part on part.name = r.participant
|
|
left join `tabEvent Edition` e on e.name = r.event_edition
|
|
where r.registration_status not in %(inactive)s
|
|
{edition_sql}
|
|
and abs(coalesce(r.amount_due, 0) - coalesce(pay.paid, 0)) > 0.01
|
|
order by abs(coalesce(r.amount_due, 0) - coalesce(pay.paid, 0)) desc
|
|
limit %(limit)s offset %(offset)s
|
|
""",
|
|
params,
|
|
as_dict=True,
|
|
)
|
|
|
|
return rows
|