Açıklama Yok

views.py 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. from django.shortcuts import render, redirect, get_object_or_404
  2. from django.contrib.admin.views.decorators import staff_member_required
  3. from django.contrib import messages
  4. from django.urls import reverse
  5. from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
  6. import django_filters as filters
  7. from admin_frontend.templatetags.public_urls import public_route
  8. from admin_frontend.nav import _nav_items
  9. from cms.views import breadcrumbs
  10. from orgs.models import Organization
  11. from recycle_core.models import Customer
  12. from recycle_core.forms import PaymentForm
  13. from .models import Invoice, Payment, Payout
  14. from django.db.models import Sum, F, ExpressionWrapper, DecimalField, Value
  15. from django.db.models.functions import Coalesce
  16. from decimal import Decimal
  17. from django.http import HttpResponse
  18. import csv
  19. # Invoices UI ---------------------------------------------------------------
  20. @staff_member_required
  21. @public_route(label="Invoices", order=50, icon="receipt-percent")
  22. @breadcrumbs(label="Invoices", name="re_invoices")
  23. def invoices_list(request):
  24. class InvoiceFilter(filters.FilterSet):
  25. organization = filters.ModelChoiceFilter(queryset=Organization.objects.all())
  26. customer = filters.ModelChoiceFilter(queryset=Customer.objects.all())
  27. status = filters.ChoiceFilter(field_name="status", choices=Invoice.STATUS_CHOICES)
  28. issued_at = filters.DateFromToRangeFilter(field_name="issued_at", label="Issued between")
  29. class Meta:
  30. model = Invoice
  31. fields = ["organization", "customer", "status", "issued_at"]
  32. base_qs = (
  33. Invoice.objects.select_related("organization", "customer")
  34. .annotate(
  35. payments_total=Coalesce(
  36. Sum("payments__amount"),
  37. Value(Decimal("0.00")),
  38. output_field=DecimalField(max_digits=14, decimal_places=2),
  39. ),
  40. )
  41. .annotate(
  42. balance=ExpressionWrapper(
  43. F("total_amount") - F("payments_total"),
  44. output_field=DecimalField(max_digits=14, decimal_places=2),
  45. )
  46. )
  47. .order_by("-issued_at", "-id")
  48. )
  49. i_filter = InvoiceFilter(request.GET, queryset=base_qs)
  50. qs = i_filter.qs
  51. org = getattr(request, "org", None)
  52. if org is not None:
  53. qs = qs.filter(organization=org)
  54. paginator = Paginator(qs, 10)
  55. page = request.GET.get("page")
  56. try:
  57. page_obj = paginator.page(page)
  58. except PageNotAnInteger:
  59. page_obj = paginator.page(1)
  60. except EmptyPage:
  61. page_obj = paginator.page(paginator.num_pages)
  62. pay_form = PaymentForm()
  63. context = {
  64. "nav": _nav_items(),
  65. "invoices": page_obj.object_list,
  66. "page_obj": page_obj,
  67. "filter": i_filter,
  68. "pay_form": pay_form,
  69. }
  70. return render(request, "recycle_core/invoices_list.html", context)
  71. @staff_member_required
  72. def invoice_add_payment(request, pk: int):
  73. invoice = get_object_or_404(Invoice, pk=pk)
  74. form = PaymentForm(request.POST)
  75. if form.is_valid():
  76. Payment.objects.create(
  77. invoice=invoice,
  78. amount=form.cleaned_data["amount"],
  79. currency_code=invoice.currency_code,
  80. received_at=form.cleaned_data.get("received_at") or None,
  81. reference=form.cleaned_data.get("reference", ""),
  82. )
  83. messages.success(request, f"Payment recorded for invoice #{invoice.id}.")
  84. else:
  85. messages.error(request, "Invalid payment data.")
  86. return redirect("billing:invoices_list")
  87. # Payouts UI + CSV ----------------------------------------------------------
  88. @staff_member_required
  89. @public_route(label="Payouts", order=52, icon="banknotes")
  90. @breadcrumbs(label="Payouts", name="re_payouts")
  91. def payouts_list(request):
  92. class PayoutFilter(filters.FilterSet):
  93. organization = filters.ModelChoiceFilter(queryset=Organization.objects.all())
  94. customer = filters.ModelChoiceFilter(queryset=Customer.objects.all())
  95. paid_at = filters.DateFromToRangeFilter(field_name="paid_at", label="Paid between")
  96. class Meta:
  97. model = Payout
  98. fields = ["organization", "customer", "paid_at"]
  99. base_qs = Payout.objects.select_related("organization", "customer").order_by("-paid_at", "-id")
  100. p_filter = PayoutFilter(request.GET, queryset=base_qs)
  101. qs = p_filter.qs
  102. org = getattr(request, "org", None)
  103. if org is not None:
  104. qs = qs.filter(organization=org)
  105. paginator = Paginator(qs, 10)
  106. page = request.GET.get("page")
  107. try:
  108. page_obj = paginator.page(page)
  109. except PageNotAnInteger:
  110. page_obj = paginator.page(1)
  111. except EmptyPage:
  112. page_obj = paginator.page(paginator.num_pages)
  113. context = {
  114. "nav": _nav_items(),
  115. "payouts": page_obj.object_list,
  116. "page_obj": page_obj,
  117. "filter": p_filter,
  118. }
  119. return render(request, "recycle_core/payouts_list.html", context)
  120. @staff_member_required
  121. def payouts_export_csv(request):
  122. qs = Payout.objects.select_related("organization", "customer").all()
  123. org_obj = getattr(request, "org", None)
  124. if org_obj is not None:
  125. qs = qs.filter(organization=org_obj)
  126. org = request.GET.get("organization")
  127. cust = request.GET.get("customer")
  128. start = request.GET.get("paid_at_after") or request.GET.get("paid_at_min")
  129. end = request.GET.get("paid_at_before") or request.GET.get("paid_at_max")
  130. from django.utils.dateparse import parse_datetime
  131. if org:
  132. qs = qs.filter(organization_id=org)
  133. if cust:
  134. qs = qs.filter(customer_id=cust)
  135. if start:
  136. dt = parse_datetime(start)
  137. if dt:
  138. qs = qs.filter(paid_at__gte=dt)
  139. if end:
  140. dt = parse_datetime(end)
  141. if dt:
  142. qs = qs.filter(paid_at__lte=dt)
  143. response = HttpResponse(content_type="text/csv")
  144. response["Content-Disposition"] = "attachment; filename=payouts.csv"
  145. writer = csv.writer(response)
  146. writer.writerow(["organization", "customer", "amount", "currency", "paid_at", "reference", "pickup_id"])
  147. for p in qs.iterator():
  148. writer.writerow([
  149. p.organization.code,
  150. p.customer.name,
  151. p.amount,
  152. p.currency_code,
  153. p.paid_at.isoformat(),
  154. p.reference,
  155. p.pickup_id or "",
  156. ])
  157. return response