import csv
import datetime
import pytz
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Sum
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render, redirect
from django.utils import timezone, dateformat
from cobalt.settings import GLOBAL_CURRENCY_SYMBOL, BRIDGE_CREDITS, TIME_ZONE
from notifications.models import BatchID
from notifications.views.core import contact_member, create_rbac_batch_id
from organisations.models import Organisation
from organisations.views.general import org_balance
from payments.forms import MemberTransferOrg
from payments.models import OrganisationTransaction
from payments.views.core import update_organisation, update_account
from rbac.core import rbac_user_has_role
from rbac.views import rbac_forbidden
from utils.utils import cobalt_paginator
TZ = pytz.timezone(TIME_ZONE)
[docs]
@login_required()
def statement_org(request, org_id):
"""Organisation statement view.
Basic view of statement showing transactions in a web page.
Args:
request: standard request object
org_id: organisation to view
Returns:
HTTPResponse
"""
organisation = get_object_or_404(Organisation, pk=org_id)
admin_view = rbac_user_has_role(request.user, "payments.global.view")
if (
not rbac_user_has_role(request.user, "payments.manage.%s.view" % org_id)
and not rbac_user_has_role(request.user, "payments.manage.%s.edit" % org_id)
and not admin_view
):
return rbac_forbidden(request, "payments.manage.%s.view" % org_id)
# get balance
balance = org_balance(organisation, True)
# get summary
today = timezone.now()
ref_date = today - datetime.timedelta(days=30)
summary = (
OrganisationTransaction.objects.filter(
organisation=organisation, created_date__gte=ref_date
)
.values("type")
.annotate(total=Sum("amount"))
.order_by("-total")
)
total = 0.0
for item in summary:
total += float(item["total"])
# get details
events_list = OrganisationTransaction.objects.filter(
organisation=organisation
).order_by("-created_date")
things = cobalt_paginator(request, events_list)
page_balance = {}
if things:
page_balance["closing_balance"] = things[0].balance
page_balance["closing_date"] = things[0].created_date
earliest = things[len(things) - 1]
page_balance["opening_balance"] = earliest.balance - earliest.amount
page_balance["opening_date"] = earliest.created_date
return render(
request,
"payments/orgs/statement_org.html",
{
"things": things,
"balance": balance,
"org": organisation,
"summary": summary,
"total": total,
"page_balance": page_balance,
"admin_view": admin_view,
},
)
[docs]
@login_required()
def statement_csv_org(request, org_id):
"""Organisation statement CSV.
Args:
request: standard request object
org_id: organisation to view
Returns:
HTTPResponse: CSV
"""
# TODO: Retire this and replace with the club admin version
organisation = get_object_or_404(Organisation, pk=org_id)
if not rbac_user_has_role(request.user, "payments.manage.%s.view" % org_id):
if not rbac_user_has_role(request.user, "payments.global.view"):
return rbac_forbidden(request, "payments.manage.%s.view" % org_id)
# get details
events_list = OrganisationTransaction.objects.filter(
organisation=organisation
).order_by("-created_date")
local_dt = timezone.localtime(timezone.now(), TZ)
today = dateformat.format(local_dt, "Y-m-d H:i:s")
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = 'attachment; filename="statement.csv"'
writer = csv.writer(response)
writer.writerow(
[organisation.name, "Downloaded by %s" % request.user.full_name, today]
)
writer.writerow(
[
"Date",
"Counterparty",
"Reference",
"Type",
"Description",
"Amount",
"Balance",
]
)
for row in events_list:
counterparty = ""
if row.member:
counterparty = row.member
if row.other_organisation:
counterparty = row.other_organisation
local_dt = timezone.localtime(row.created_date, TZ)
writer.writerow(
[
dateformat.format(local_dt, "Y-m-d H:i:s"),
counterparty,
row.reference_no,
row.type,
row.description,
row.amount,
row.balance,
]
)
return response
[docs]
def statement_org_summary_ajax(request, org_id, range):
"""Called by the org statement when the summary date range changes
Args:
request (HTTPRequest): standard request object
org_id(int): pk of the org to query
range(str): range to include in summary
Returns:
HTTPResponse: data for table
"""
if request.method == "GET":
organisation = get_object_or_404(Organisation, pk=org_id)
if (
not rbac_user_has_role(request.user, "payments.manage.%s.view" % org_id)
and not rbac_user_has_role(request.user, "payments.manage.%s.edit" % org_id)
and not rbac_user_has_role(request.user, "payments.global.view")
):
return rbac_forbidden(request, "payments.manage.%s.view" % org_id)
if range == "All":
summary = (
OrganisationTransaction.objects.filter(organisation=organisation)
.values("type")
.annotate(total=Sum("amount"))
.order_by("-total")
)
else:
days = int(range)
today = timezone.now()
ref_date = today - datetime.timedelta(days=days)
summary = (
OrganisationTransaction.objects.filter(
organisation=organisation, created_date__gte=ref_date
)
.values("type")
.annotate(total=Sum("amount"))
.order_by("-total")
)
total = 0.0
for item in summary:
total += float(item["total"])
return render(
request,
"payments/orgs/statement_org_summary_ajax.html",
{"summary": summary, "total": total},
)
[docs]
@login_required()
def member_transfer_org(request, org_id):
"""Allows an organisation to transfer money to a member
Args:
request (HTTPRequest): standard request object
org_id (int): organisation doing the transfer
Returns:
HTTPResponse
"""
organisation = get_object_or_404(Organisation, pk=org_id)
if not rbac_user_has_role(
request.user, "payments.manage.%s.edit" % org_id
) and not rbac_user_has_role(request.user, "payments.global.edit"):
return rbac_forbidden(request, "payments.manage.%s.edit" % org_id)
balance = org_balance(organisation)
if request.method == "POST":
form = MemberTransferOrg(request.POST, balance=balance)
if form.is_valid():
member = form.cleaned_data["transfer_to"]
amount = form.cleaned_data["amount"]
description = form.cleaned_data["description"]
# Org transaction
update_organisation(
organisation=organisation,
description=description,
amount=-amount,
payment_type="Member Transfer",
member=member,
)
update_account(
member=member,
amount=amount,
description=description,
payment_type="Org Transfer",
organisation=organisation,
)
# Notify member
email_body = f"""<b>{organisation}</b> has transferred {GLOBAL_CURRENCY_SYMBOL}{amount:.2f}
into your {BRIDGE_CREDITS} account.
<br><br>
The description was: {description}.
<br><br>
Please contact {organisation} directly if you have any queries.
This transfer was made by {request.user}.
<br><br>"""
# create batch ID
batch_id = create_rbac_batch_id(
rbac_role=f"notifications.orgcomms.{organisation}.edit",
organisation=organisation,
batch_type=BatchID.BATCH_TYPE_ADMIN,
description=f"Transfer from {organisation}",
batch_size=1,
complete=True,
)
# send
contact_member(
member=member,
msg="Transfer from %s - %s%s"
% (organisation, GLOBAL_CURRENCY_SYMBOL, amount),
contact_type="Email",
html_msg=email_body,
link="/payments",
subject="Transfer from %s" % organisation,
batch_id=batch_id,
)
msg = "Transferred %s%s to %s" % (
GLOBAL_CURRENCY_SYMBOL,
amount,
member,
)
messages.success(request, msg, extra_tags="cobalt-message-success")
return redirect("payments:statement_org", org_id=organisation.id)
else:
print(form.errors)
else:
form = MemberTransferOrg(balance=balance)
return render(
request,
"payments/orgs/member_transfer_org.html",
{"form": form, "balance": balance, "org": organisation},
)
[docs]
@login_required()
def get_org_fees(request, org_id):
"""Get the ABF fees associated with this organisation"""
org = get_object_or_404(Organisation, pk=org_id)
return HttpResponse(org.settlement_fee_percent)