From cc7012a22b31a5c53adc88f9537081f75010e495 Mon Sep 17 00:00:00 2001 From: Christian Henriksen Date: Thu, 19 Feb 2026 00:11:04 +0100 Subject: [PATCH 1/4] Add missing settings for creating local expenses --- src/bornhack/environment_settings.py.dist.dev | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bornhack/environment_settings.py.dist.dev b/src/bornhack/environment_settings.py.dist.dev index 55097a7ce..269dc0fc6 100644 --- a/src/bornhack/environment_settings.py.dist.dev +++ b/src/bornhack/environment_settings.py.dist.dev @@ -63,6 +63,10 @@ IRCBOT_CHANNELS = { IRCBOT_PUBLIC_CHANNEL = "#my-bornhack-channel" IRCBOT_VOLUNTEER_CHANNEL = "#my-bornhack-channel" +ACCOUNTINGSYSTEM_EMAIL = "accounting_system@example.com" +ECONOMYTEAM_EMAIL = "economy@example.com" +ECONOMYTEAM_NAME = "Economy" + BANKACCOUNT_IBAN = "LOL" BANKACCOUNT_SWIFTBIC = "lol" BANKACCOUNT_REG = "lol" From 362b91b3a2d44f97240a24c0fef6428a1de79d5f Mon Sep 17 00:00:00 2001 From: Christian Henriksen Date: Thu, 19 Feb 2026 00:37:48 +0100 Subject: [PATCH 2/4] Add profile properties and add/update signal handlers - Add profile properties for getting expenses and revenues with 'needs reimbursement' status. - Update expense signal handler to use new property and add new signal handler for revenues. --- src/profiles/apps.py | 6 ++++++ src/profiles/models.py | 23 +++++++++++++++++++++++ src/profiles/signal_handlers.py | 28 ++++++++++++++++++++-------- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/profiles/apps.py b/src/profiles/apps.py index f155ea304..f63ea5d00 100644 --- a/src/profiles/apps.py +++ b/src/profiles/apps.py @@ -11,6 +11,7 @@ from .signal_handlers import profile_pre_save from .signal_handlers import set_session_on_login from .signal_handlers import reimbursement_msg_on_login +from .signal_handlers import redisbursement_msg_on_login logger = logging.getLogger(f"bornhack.{__name__}") @@ -42,3 +43,8 @@ def ready(self) -> None: sender=User, dispatch_uid="reimbursement_msg_on_login_signal", ) + user_logged_in.connect( + redisbursement_msg_on_login, + sender=User, + dispatch_uid="redisbursement_msg_on_login_signal", + ) diff --git a/src/profiles/models.py b/src/profiles/models.py index 9ab0c5bb3..a8ad3081c 100644 --- a/src/profiles/models.py +++ b/src/profiles/models.py @@ -9,6 +9,8 @@ from utils.models import CreatedUpdatedModel from utils.models import UUIDModel +from economy.models import Expense +from economy.models import Revenue class Profile(ExportModelOperationsMixin("profile"), CreatedUpdatedModel, UUIDModel): @@ -104,3 +106,24 @@ def get_name(self): if self.name: return self.name return self.user.username + + @property + def paid_expenses_needs_reimbursement(self): + """The paid_expense_needs_reimbursement property.""" + return Expense.objects.filter( + user=self.user, + approved=True, + reimbursement__isnull=True, + payment_status="PAID_NEEDS_REIMBURSEMENT", + ) + + @property + def paid_revenues_needs_redisbursement(self): + """The paid_revenues_needs_redisbursement property.""" + return Revenue.objects.filter( + user=self.user, + approved=True, + reimbursement__isnull=True, + payment_status="PAID_NEEDS_REDISBURSEMENT", + ) + diff --git a/src/profiles/signal_handlers.py b/src/profiles/signal_handlers.py index cc6ccddbc..a295a8e5e 100644 --- a/src/profiles/signal_handlers.py +++ b/src/profiles/signal_handlers.py @@ -79,15 +79,27 @@ def set_session_on_login(sender, request, user, **kwargs) -> None: """Signal handler called on_login to set session["theme"] from the user profile.""" request.session["theme"] = request.user.profile.theme + def reimbursement_msg_on_login(sender, request, user, **kwargs) -> None: - """ - Add message when user has approved expenses without matching reimbursement. - """ - approved_expenses = user.expenses.all().filter(approved=True, reimbursement=None) + """Add message for reminding user about missing reimbursement.""" + expenses = user.profile.paid_expenses_needs_reimbursement + if expenses.exists(): + msg = ( + f"NOTE: You have {expenses.count()} expense(s) with missing " + "reimbursement. Please create a reimbursement once all your " + "expenses have been approved." + ) + messages.info(request, msg) + - if approved_expenses.exists(): - messages.info( - request, - f"NOTE: You have {approved_expenses.count()} expenses with a missing reimbursement. Please create a reimbursement once all your expenses have been approved." +def redisbursement_msg_on_login(sender, request, user, **kwargs) -> None: + """Add message for reminding user about missing redisbursement.""" + revenues = user.profile.paid_revenues_needs_redisbursement + if revenues.exists(): + msg = ( + f"NOTE: You have {revenues.count()} revenue(s) with missing " + "redisbursement. Please create a redisbursement once all your " + "revenue(s) have been approved." ) + messages.info(request, msg) From bdde9254a7b9989feae2ebb90f6932c18469ddb2 Mon Sep 17 00:00:00 2001 From: Christian Henriksen Date: Thu, 19 Feb 2026 00:47:54 +0100 Subject: [PATCH 3/4] Change existing queries to use new properties --- src/economy/views.py | 40 ++++++------------------------------- src/utils/bootstrap/base.py | 14 ++----------- 2 files changed, 8 insertions(+), 46 deletions(-) diff --git a/src/economy/views.py b/src/economy/views.py index 06ad877f0..a20ad75cd 100644 --- a/src/economy/views.py +++ b/src/economy/views.py @@ -415,16 +415,8 @@ class ReimbursementCreateView(CampViewMixin, ExpensePermissionMixin, CreateView) def dispatch(self, request, *args, **kwargs): """Get any approved and un-reimbursed expenses and revenues, or return error.""" - self.expenses = request.user.expenses.filter( - reimbursement__isnull=True, - approved=True, - payment_status="PAID_NEEDS_REIMBURSEMENT", - ) - self.revenues = request.user.revenues.filter( - reimbursement__isnull=True, - approved=True, - payment_status="PAID_NEEDS_REDISBURSEMENT", - ) + self.expenses = request.user.profile.paid_expenses_needs_reimbursement + self.revenues = request.user.profile.paid_revenues_needs_redisbursement if not self.expenses and not self.revenues: messages.error( request, @@ -458,28 +450,8 @@ def form_valid(self, form): reverse("economy:dashboard", kwargs={"camp_slug": self.camp.slug}), ) - # get the expenses for this user - expenses = Expense.objects.filter( - user=self.request.user, - approved=True, - reimbursement__isnull=True, - payment_status="PAID_NEEDS_REIMBURSEMENT", - ) - expenses_total = expenses.aggregate(Sum("amount"))["amount__sum"] or 0 - - # get the revenues for this user - revenues = Revenue.objects.filter( - user=self.request.user, - approved=True, - reimbursement__isnull=True, - payment_status="PAID_NEEDS_REDISBURSEMENT", - ) - revenues_total = revenues.aggregate(Sum("amount"))["amount__sum"] or 0 - if not expenses and not revenues: - messages.error(self.request, "No approved unhandled expenses or revenues found") - return redirect( - reverse("economy:dashboard", kwargs={"camp_slug": self.camp.slug}), - ) + expenses_total = self.expenses.aggregate(Sum("amount"))["amount__sum"] or 0 + revenues_total = self.revenues.aggregate(Sum("amount"))["amount__sum"] or 0 # calculate the reimbursement total reimbursement_total = expenses_total - revenues_total @@ -502,12 +474,12 @@ def form_valid(self, form): reimbursement.save() # add all expenses to reimbursement - for expense in expenses: + for expense in self.expenses: expense.reimbursement = reimbursement expense.save() # add all revenues to reimbursement - for revenue in revenues: + for revenue in self.revenues: revenue.reimbursement = reimbursement revenue.save() diff --git a/src/utils/bootstrap/base.py b/src/utils/bootstrap/base.py index e52db4551..4a79eba37 100644 --- a/src/utils/bootstrap/base.py +++ b/src/utils/bootstrap/base.py @@ -2037,18 +2037,8 @@ def create_camp_reimbursements(self, camp: Camp) -> None: .distinct(), ) for user in users: - expenses = Expense.objects.filter( - user=user, - approved=True, - reimbursement__isnull=True, - payment_status="PAID_NEEDS_REIMBURSEMENT", - ) - revenues = Revenue.objects.filter( - user=user, - approved=True, - reimbursement__isnull=True, - payment_status="PAID_NEEDS_REDISBURSEMENT", - ) + expenses = user.profile.paid_expenses_needs_reimbursement + revenues = user.profile.paid_revenues_needs_redisbursement if not expenses and not revenues: continue reimbursement = Reimbursement.objects.create( From 30fcc07d9de48b0820da5f224104609dd3e8721b Mon Sep 17 00:00:00 2001 From: Christian Henriksen Date: Thu, 19 Feb 2026 00:57:51 +0100 Subject: [PATCH 4/4] Manually run pre-commit on changed files --- src/economy/views.py | 2 +- src/profiles/apps.py | 4 ++-- src/profiles/models.py | 21 ++++++++++----------- src/profiles/signal_handlers.py | 1 - 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/economy/views.py b/src/economy/views.py index a20ad75cd..35bb0267e 100644 --- a/src/economy/views.py +++ b/src/economy/views.py @@ -6,8 +6,8 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin -from django.db.models import Prefetch from django.core.exceptions import ValidationError +from django.db.models import Prefetch from django.db.models import Q from django.db.models import Sum from django.http import HttpResponse diff --git a/src/profiles/apps.py b/src/profiles/apps.py index f63ea5d00..435c6fe9a 100644 --- a/src/profiles/apps.py +++ b/src/profiles/apps.py @@ -9,9 +9,9 @@ from .signal_handlers import create_profile from .signal_handlers import profile_pre_save -from .signal_handlers import set_session_on_login -from .signal_handlers import reimbursement_msg_on_login from .signal_handlers import redisbursement_msg_on_login +from .signal_handlers import reimbursement_msg_on_login +from .signal_handlers import set_session_on_login logger = logging.getLogger(f"bornhack.{__name__}") diff --git a/src/profiles/models.py b/src/profiles/models.py index a8ad3081c..e5213145b 100644 --- a/src/profiles/models.py +++ b/src/profiles/models.py @@ -7,10 +7,10 @@ from django.utils.translation import gettext_lazy as _ from django_prometheus.models import ExportModelOperationsMixin -from utils.models import CreatedUpdatedModel -from utils.models import UUIDModel from economy.models import Expense from economy.models import Revenue +from utils.models import CreatedUpdatedModel +from utils.models import UUIDModel class Profile(ExportModelOperationsMixin("profile"), CreatedUpdatedModel, UUIDModel): @@ -78,22 +78,22 @@ class Meta: ) @property - def email(self): + def email(self) -> str: return self.user.email def __str__(self) -> str: return self.user.username def approve_public_credit_name(self) -> None: - """This method just sets profile.public_credit_name_approved=True and calls save() - It is used in an admin action. + """This method just sets profile.public_credit_name_approved=True + and calls save(). It is used in an admin action. """ self.public_credit_name_approved = True self.save() @property - def get_public_credit_name(self): - """Convenience method to return profile.public_credit_name if it is approved, + def get_public_credit_name(self) -> str: + """Convenience method to return profile.public_credit_name if approved, and the string "Unnamed" otherwise. """ if self.public_credit_name_approved: @@ -101,14 +101,14 @@ def get_public_credit_name(self): return "Unnamed" @property - def get_name(self): + def get_name(self) -> str: """Convenience method to return profile.name if set, otherwise username.""" if self.name: return self.name return self.user.username @property - def paid_expenses_needs_reimbursement(self): + def paid_expenses_needs_reimbursement(self) -> models.QuerySet: """The paid_expense_needs_reimbursement property.""" return Expense.objects.filter( user=self.user, @@ -118,7 +118,7 @@ def paid_expenses_needs_reimbursement(self): ) @property - def paid_revenues_needs_redisbursement(self): + def paid_revenues_needs_redisbursement(self) -> models.QuerySet: """The paid_revenues_needs_redisbursement property.""" return Revenue.objects.filter( user=self.user, @@ -126,4 +126,3 @@ def paid_revenues_needs_redisbursement(self): reimbursement__isnull=True, payment_status="PAID_NEEDS_REDISBURSEMENT", ) - diff --git a/src/profiles/signal_handlers.py b/src/profiles/signal_handlers.py index a295a8e5e..a833b81cf 100644 --- a/src/profiles/signal_handlers.py +++ b/src/profiles/signal_handlers.py @@ -102,4 +102,3 @@ def redisbursement_msg_on_login(sender, request, user, **kwargs) -> None: "revenue(s) have been approved." ) messages.info(request, msg) -