Compare commits

..

1 Commits

Author SHA1 Message Date
flo 5eab52a0f8 chore(deps): update dependency ruff to v0.15.18 2026-06-19 07:00:53 +00:00
9 changed files with 7 additions and 215 deletions
+2 -2
View File
@@ -210,8 +210,8 @@ class DonationAdmin(admin.ModelAdmin):
@admin.register(Payment)
class PaymentAdmin(admin.ModelAdmin):
list_display = ("date", "purpose", "amount", "method")
list_filter = ("method", "date")
list_display = ("date", "purpose", "amount")
list_filter = ("date",)
ordering = ("-date",)
search_fields = ("purpose",)
@@ -1,86 +0,0 @@
from decimal import Decimal, ROUND_HALF_UP
from django.core.management.base import BaseCommand
from django.db.models import F, Sum
from gaehsnitz.models import Consumption, Payment, UserPayment
YEAR = 2026
CASH_PREFILL = Decimal("500.00")
def eur(amount):
return str(Decimal(amount).quantize(Decimal("1"), rounding=ROUND_HALF_UP))
class Command(BaseCommand):
help = "Print full 2026 festival finance summary"
def handle(self, *args, **options):
w = self.stdout.write
sep = "-" * 40
# --- Ausgaben ---
w("\nAUSGABEN")
w(sep)
payments = Payment.objects.filter(date__year=YEAR).order_by("date")
total_out = Decimal("0")
for p in payments:
method_label = dict(Payment.Method.choices).get(p.method, p.method)
w(f" {p.date} {eur(p.amount):>6}{method_label:<15} {p.purpose}")
total_out += p.amount
w(sep)
w(f" {'TOTAL':<11} {eur(total_out):>6}")
# by method
w("")
for method, label in Payment.Method.choices:
subtotal = payments.filter(method=method).aggregate(s=Sum("amount"))["s"] or Decimal("0")
if subtotal:
w(f" {label:<15} {eur(subtotal):>6}")
# --- Einnahmen ---
w("\nEINNAHMEN")
w(sep)
up = UserPayment.objects.filter(created_at__year=YEAR)
cash_prefill = CASH_PREFILL
cash_payments = up.filter(method="cash").aggregate(s=Sum("amount"))["s"] or Decimal("0")
cash_from_sales = cash_payments - cash_prefill
non_cash = up.exclude(method="cash").aggregate(s=Sum("amount"))["s"] or Decimal("0")
total_in = cash_from_sales + non_cash
drink_revenue = Consumption.objects.filter(for_free=False, drink__year=YEAR).annotate(
cost=F("amount") * F("drink__sale_price_per_bottle")
).aggregate(s=Sum("cost"))["s"] or Decimal("0")
entry_donations = total_in - drink_revenue
w(f" Cash: {eur(cash_from_sales):>6}")
w(f" Cashless: {eur(non_cash):>6}")
w(f" Einnahmen Gesamt: {eur(total_in):>6}")
w(f" - Getränke-Umsatz: {eur(drink_revenue):>6}")
w(f" - Eintrittsspenden: {eur(entry_donations):>6}")
# --- Kassensaldo ---
w("\nKASSE")
w(sep)
cash_out = payments.filter(method="cash").aggregate(s=Sum("amount"))["s"] or Decimal("0")
expected_cash = cash_prefill + cash_from_sales - cash_out
w(f" Kassen-Vorschuss: {eur(cash_prefill):>6}")
w(f" Cash-Einnahmen: {eur(cash_from_sales):>6}")
w(f" Cash-Ausgaben: {eur(cash_out):>6}")
w(f" -> in Kasse: {eur(expected_cash):>6}")
# --- Gesamtbilanz ---
w("\nGESAMTBILANZ")
w(sep)
net = total_in - total_out
out_of_pocket = total_out - total_in
w(f" Gesamtausgaben: {eur(total_out):>6}")
w(f" Gesamteinnahmen: {eur(total_in):>6}")
w(sep)
label = "Verlust" if out_of_pocket > 0 else "Gewinn"
w(f" {label}: {eur(abs(net)):>6}\n")
@@ -1,46 +0,0 @@
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from gaehsnitz.models import User
class Command(BaseCommand):
help = "Merge source user into target user, then delete source"
def add_arguments(self, parser):
parser.add_argument("source", help="Username of user to merge FROM (will be deleted)")
parser.add_argument("target", help="Username of user to merge INTO (will be kept)")
def handle(self, *args, **options):
source_name = options["source"]
target_name = options["target"]
if source_name == target_name:
raise CommandError("Source and target must be different users")
try:
source = User.objects.get(username=source_name)
except User.DoesNotExist:
raise CommandError(f"Source user '{source_name}' not found")
try:
target = User.objects.get(username=target_name)
except User.DoesNotExist:
raise CommandError(f"Target user '{target_name}' not found")
with transaction.atomic():
payments = source.user_payments.count()
consumptions = source.consumption_list.count()
source.user_payments.update(user=target)
source.consumption_list.update(user=target)
source.delete()
self.stdout.write(
self.style.SUCCESS(
f"Merged '{source_name}' into '{target_name}': "
f"{payments} payment(s), {consumptions} consumption(s) reassigned. "
f"Source user deleted."
)
)
@@ -1,39 +0,0 @@
from django.core.management.base import BaseCommand
from django.db import transaction
from gaehsnitz.models import Drink
# Actual crates bought from supplier (all others were returned full, no cost)
# (name, crates_ordered, crates_purchased)
# crates_returned = crates_purchased (all empties back, full deposit refunded)
ACTUAL_CRATES = [
("Sternburg Export", 12, 11),
("Ur-Krostitzer", 5, 2),
("Budweiser", 5, 2),
("Altenburger Helles", 5, 5),
("Feldschl. Radler", 2, 1),
("Lübzer Grapef. 0,0", 1, 1),
("Freiberger 0,0", 4, 3),
("Club Mate", 2, 2),
("Vita Cola", 2, 1),
("Paulaner Spezi", 2, 2),
("Wasser", 10, 5),
]
class Command(BaseCommand):
help = "Update 2026 drink crates to actual purchased amounts"
def handle(self, *args, **options):
with transaction.atomic():
for name, ordered, purchased in ACTUAL_CRATES:
updated = Drink.objects.filter(name=name, year=2026).update(
crates_ordered=ordered,
crates_purchased=purchased,
crates_returned=purchased,
)
if updated:
self.stdout.write(f" {name}: ordered={ordered}, purchased={purchased}, returned={purchased}")
else:
self.stdout.write(self.style.WARNING(f" NOT FOUND: {name}"))
self.stdout.write(self.style.SUCCESS("Done."))
@@ -1,18 +0,0 @@
# Generated by Django 6.0.6 on 2026-06-20 12:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gaehsnitz', '0010_add_sekt_category'),
]
operations = [
migrations.AddField(
model_name='payment',
name='method',
field=models.CharField(choices=[('cash', 'Bar'), ('card', 'EC-Karte'), ('paypal', 'PayPal'), ('bank', 'Überweisung')], default='paypal', max_length=16, verbose_name='Methode'),
),
]
-7
View File
@@ -73,16 +73,9 @@ class Payment(models.Model):
bands = 11, "Bands"
supply_purchase = 12, "Getränke-/Essenseinkauf"
class Method(models.TextChoices):
cash = "cash", "Bar"
card = "card", "EC-Karte"
paypal = "paypal", "PayPal"
bank = "bank", "Überweisung"
purpose = models.CharField("Zweck", max_length=64)
date = models.DateField("Datum")
amount = PriceField("Betrag")
method = models.CharField("Methode", max_length=16, choices=Method.choices, default=Method.paypal)
class Meta:
verbose_name = "Ausgabe"
+1 -9
View File
@@ -1,6 +1,5 @@
import math
from datetime import datetime, timedelta
from decimal import Decimal
from zoneinfo import ZoneInfo
from django.conf import settings
@@ -748,14 +747,9 @@ DAY_LABELS = {1: "Donnerstag", 2: "Freitag", 3: "Samstag", 4: "Sonntag"}
def dashboard_view(request):
year = current_year()
CASH_PREFILL = Decimal("500.00")
total_donations = Donation.objects.filter(date__year=year).aggregate(s=Sum("amount"))["s"] or 0
total_costs = Payment.objects.filter(date__year=year).aggregate(s=Sum("amount"))["s"] or 0
cash_raw = UserPayment.objects.filter(created_at__year=year, method="cash").aggregate(s=Sum("amount"))["s"] or Decimal("0")
cash_net = cash_raw - CASH_PREFILL
non_cash = UserPayment.objects.filter(created_at__year=year).exclude(method="cash").aggregate(s=Sum("amount"))["s"] or Decimal("0")
user_payments_total = cash_net + non_cash # real income, prefill excluded
user_payments_total = UserPayment.objects.filter(created_at__year=year).aggregate(s=Sum("amount"))["s"] or 0
drinks = list(Drink.objects.filter(year=year).order_by("name"))
drink_rows = [
@@ -964,8 +958,6 @@ def dashboard_view(request):
"total_entry": total_entry,
"method_split": method_split,
"cash_total": cash_total,
"cash_prefill": CASH_PREFILL,
"cash_net": cash_net,
"top_free_recipient": top_free_recipient,
"free_total_count": free_total_count,
}
+3 -7
View File
@@ -30,16 +30,12 @@
<span class="hist-price">{{ total_donations|euro }}</span>
</li>
<li class="fin-row">
<span class="hist-what">Cashless (User-Tab)</span>
<span class="hist-what">Zahlungen (User-Tab)</span>
<span class="hist-price">{{ user_payments_total|euro }}</span>
</li>
<li class="fin-row">
<span class="hist-what">Kasse (bar, nach Vorschuss)</span>
<span class="hist-price">{{ cash_net|euro }}</span>
</li>
<li class="fin-row">
<span class="hist-what">Kassen-Vorschuss</span>
<span class="hist-price">{{ cash_prefill|euro }}</span>
<span class="hist-what">Kasse (bar)</span>
<span class="hist-price">{{ cash_total|euro }}</span>
</li>
<li class="fin-row">
<span class="hist-what">Einkaufspreis Getränke</span>
+1 -1
View File
@@ -10,7 +10,7 @@ dependencies = [
[dependency-groups]
dev = [
"ruff==0.15.16",
"ruff==0.15.18",
]
[tool.ruff]