Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/backoffice/templates/reimbursement_list_backoffice.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ <h2>BackOffice: Reimbursements for {{ camp.title }}</h2>
<p class="lead">This view shows all existing reimbursements for {{ camp.title }}. Users have to create their own reimbursements when they are done adding expenses. The user will be asked for a bank account when creating the reimbursement.</p>

<p>
<a class="btn btn-secondary" href="{% url 'backoffice:index' camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Backoffice</a>
<a class="btn btn-secondary" href="{% url 'backoffice:index' camp_slug=camp.slug %}#economy"><i class="fas fa-undo"></i> Backoffice</a>
</p>

{% include 'includes/reimbursement_list_panel.html' %}

<p>
<a class="btn btn-secondary" href="{% url 'backoffice:index' camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Backoffice</a>
<a class="btn btn-secondary" href="{% url 'backoffice:index' camp_slug=camp.slug %}#economy"><i class="fas fa-undo"></i> Backoffice</a>
</p>
{% endblock content %}
42 changes: 28 additions & 14 deletions src/backoffice/views/economy.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,21 +160,21 @@ def get_context_data(self, *args, **kwargs):
context["expenses"] = Expense.objects.filter(
camp=self.camp,
creditor__chain=self.get_object(),
).prefetch_related("responsible_team", "user", "creditor")
).prefetch_related("user", "creditor")
context["revenues"] = Revenue.objects.filter(
camp=self.camp,
debtor__chain=self.get_object(),
).prefetch_related("responsible_team", "user", "debtor")
).prefetch_related("user", "debtor")

# Include past years expenses and revenues for the Chain in context as separate querysets
context["past_expenses"] = Expense.objects.filter(
camp__camp__lt=self.camp.camp,
creditor__chain=self.get_object(),
).prefetch_related("responsible_team", "user", "creditor")
).prefetch_related("user", "creditor")
context["past_revenues"] = Revenue.objects.filter(
camp__camp__lt=self.camp.camp,
debtor__chain=self.get_object(),
).prefetch_related("responsible_team", "user", "debtor")
).prefetch_related("user", "debtor")

return context

Expand All @@ -187,10 +187,10 @@ class CredebtorDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView)
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context["expenses"] = (
self.get_object().expenses.filter(camp=self.camp).prefetch_related("responsible_team", "user", "creditor")
self.get_object().expenses.filter(camp=self.camp).prefetch_related("user", "creditor")
)
context["revenues"] = (
self.get_object().revenues.filter(camp=self.camp).prefetch_related("responsible_team", "user", "debtor")
self.get_object().revenues.filter(camp=self.camp).prefetch_related("user", "debtor")
)
return context

Expand All @@ -209,7 +209,6 @@ def get_queryset(self, **kwargs):
return queryset.exclude(approved__isnull=True).prefetch_related(
"creditor",
"user",
"responsible_team",
)

def get_context_data(self, **kwargs):
Expand All @@ -221,7 +220,6 @@ def get_context_data(self, **kwargs):
).prefetch_related(
"creditor",
"user",
"responsible_team",
)
return context

Expand Down Expand Up @@ -275,19 +273,36 @@ class ReimbursementUpdateView(
):
model = Reimbursement
template_name = "reimbursement_form.html"
fields = ["notes", "paid"]
fields = ["notes"]

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["expenses"] = self.object.expenses.filter(paid_by_bornhack=False)
context["total_amount"] = context["expenses"].aggregate(Sum("amount"))
context["expenses"] = self.object.covered_expenses.all()
context["revenues"] = self.object.covered_revenues.all()
context["total_amount"] = self.object.amount
context["reimbursement_user"] = self.object.reimbursement_user
context["cancelurl"] = reverse(
"backoffice:reimbursement_list",
kwargs={"camp_slug": self.camp.slug},
)
return context

def form_valid(self, form):
"""Backoffice has two submit buttons in this form, 'Just Save', and 'Mark as Paid'."""
if "paid" in form.data:
# mark as paid button was pressed
reimbursement = form.save()
reimbursement.mark_as_paid()
messages.success(self.request, "Reimbursement marked as paid, related expenses and revenues payment_status set accordingly")
elif "save" in form.data:
reimbursement = form.save()
messages.success(self.request, "Reimbursement notes updated")
else:
messages.error(self.request, "Unknown submit action")
return redirect(
reverse("backoffice:expense_list", kwargs={"camp_slug": self.camp.slug}),
)

def get_success_url(self):
return reverse(
"backoffice:reimbursement_detail",
Expand All @@ -299,7 +314,7 @@ class ReimbursementDeleteView(CampViewMixin, EconomyTeamPermissionMixin, DeleteV
model = Reimbursement
template_name = "reimbursement_delete.html"

def get(self, request, *args, **kwargs):
def dispatch(self, request, *args, **kwargs):
if self.get_object().paid:
messages.error(
request,
Expand All @@ -312,7 +327,7 @@ def get(self, request, *args, **kwargs):
),
)
# continue with the request
return super().get(request, *args, **kwargs)
return super().dispatch(request, *args, **kwargs)

def get_success_url(self):
messages.success(
Expand All @@ -339,7 +354,6 @@ def get_queryset(self, **kwargs):
return queryset.exclude(approved__isnull=True).prefetch_related(
"debtor",
"user",
"responsible_team",
)

def get_context_data(self, **kwargs):
Expand Down
7 changes: 2 additions & 5 deletions src/bornhack/environment_settings.py.dist
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,14 @@ DATABASES = {
'HOST': '{{ django_postgres_host }}',
# comment this out for non-tls connection to postgres
'OPTIONS': {'sslmode': 'verify-full', 'sslrootcert': 'system'},
# always use transactions
'ATOMIC_REQUESTS': True,
},
}

DEBUG={{ django_debug }}
DEBUG_TOOLBAR_ENABLED={{ django_debug_toolbar_enabled }}


# start redirecting to the next camp instead of the previous camp after
# this much of the time between the camps has passed
CAMP_REDIRECT_PERCENT=15

### changes below here are only needed for production

# email settings
Expand Down
5 changes: 1 addition & 4 deletions src/economy/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ class ExpenseAdmin(admin.ModelAdmin):
"camp",
"creditor__chain",
"creditor",
"responsible_team",
"approved",
"user",
]
Expand All @@ -79,7 +78,6 @@ class ExpenseAdmin(admin.ModelAdmin):
"amount",
"camp",
"creditor",
"responsible_team",
"approved",
"reimbursement",
]
Expand Down Expand Up @@ -109,14 +107,13 @@ def reject_revenues(modeladmin, request, queryset) -> None:

@admin.register(Revenue)
class RevenueAdmin(admin.ModelAdmin):
list_filter = ["camp", "responsible_team", "approved", "user"]
list_filter = ["camp", "approved", "user"]
list_display = [
"user",
"description",
"invoice_date",
"amount",
"camp",
"responsible_team",
"approved",
]
search_fields = ["description", "amount", "user"]
Expand Down
4 changes: 2 additions & 2 deletions src/economy/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,9 +549,9 @@ class Meta:
color=random.choice(["#ff0000", "#00ff00", "#0000ff"]),
)
invoice_date = factory.Faker("date")
responsible_team = factory.Faker("random_element", elements=Team.objects.all())
approved = factory.Faker("random_element", elements=[True, True, False, None])
notes = factory.Faker("text")
payment_status = factory.Faker("random_element", elements=["PAID_IN_NETBANK", "PAID_WITH_TYKLINGS_MASTERCARD", "PAID_WITH_AHFS_MASTERCARD", "PAID_NEEDS_REIMBURSEMENT"])


class RevenueFactory(factory.django.DjangoModelFactory):
Expand All @@ -571,6 +571,6 @@ class Meta:
color=random.choice(["#ff0000", "#00ff00", "#0000ff"]),
)
invoice_date = factory.Faker("date")
responsible_team = factory.Faker("random_element", elements=Team.objects.all())
approved = factory.Faker("random_element", elements=[True, True, False, None])
notes = factory.Faker("text")
payment_status = factory.Faker("random_element", elements=["PAID_IN_NETBANK", "PAID_TO_TYKLINGS_MASTERCARD", "PAID_TO_AHFS_MASTERCARD", "PAID_NEEDS_REDISBURSEMENT"])
91 changes: 69 additions & 22 deletions src/economy/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,8 @@
from .models import Revenue


class CleanInvoiceForm(forms.ModelForm):
"""We have to define this form explicitly because we want our ImageField to accept PDF files as well as images,
and we cannot change the clean_* methods with an autogenerated form from inside views.py.
"""

invoice = forms.FileField()
class CleanInvoiceMixin:
"""We want our ImageFields to accept PDF files as well as images."""

def clean_invoice(self):
# get the uploaded file from cleaned_data
Expand All @@ -38,44 +34,95 @@ def clean_invoice(self):
return uploaded_file


class ExpenseCreateForm(CleanInvoiceForm):
class ExpenseUpdateForm(forms.ModelForm):
class Meta:
model = Expense
fields = [
"description",
"amount",
"payment_status",
"invoice_date",
"invoice",
"paid_by_bornhack",
"responsible_team",
]

def __init__(self, *args, **kwargs):
"""Remove some choices."""
super().__init__(*args, **kwargs)
# TODO: this is a subset of the choices in the model,
# find a way to keep this more DRY
self.fields["payment_status"].choices = [
(
"Paid by BornHack",
(
("PAID_WITH_TYKLINGS_MASTERCARD", "Expense was paid with Tyklings BornHack Mastercard"),
("PAID_WITH_AHFS_MASTERCARD", "Expense was paid with ahfs BornHack Mastercard"),
("PAID_WITH_VIDIRS_MASTERCARD", "Expense was paid with Vidirs BornHack Mastercard"),
("PAID_IN_NETBANK", "Expense was paid with bank transfer from BornHacks netbank"),
("PAID_WITH_BORNHACKS_CASH", "Expense was paid with BornHacks cash"),
),
),
(
"Paid by Participant",
(("PAID_NEEDS_REIMBURSEMENT", "Expense was paid by me, I need a reimbursement"),),
),
(
"Unpaid",
(("UNPAID_NEEDS_PAYMENT", "Expense is unpaid"),),
),
]


class ExpenseCreateForm(ExpenseUpdateForm, CleanInvoiceMixin):
invoice = forms.FileField()

class ExpenseUpdateForm(forms.ModelForm):
class Meta:
model = Expense
fields = [
"description",
"amount",
"payment_status",
"invoice_date",
"paid_by_bornhack",
"responsible_team",
"invoice",
]


class RevenueCreateForm(CleanInvoiceForm):
######### REVENUE ###############################


class RevenueUpdateForm(forms.ModelForm):
class Meta:
model = Revenue
fields = [
"description",
"amount",
"invoice_date",
"invoice",
"responsible_team",
fields = ["description", "amount", "payment_status", "invoice_date"]

def __init__(self, *args, **kwargs):
"""Remove some choices."""
super().__init__(*args, **kwargs)
# TODO: this is a subset of the choices in the model,
# find a way to keep this more DRY
self.fields["payment_status"].choices = [
(
"Paid to BornHack",
(
("PAID_TO_TYKLINGS_MASTERCARD", "Revenue was credited to Tyklings BornHack Mastercard"),
("PAID_TO_AHFS_MASTERCARD", "Revenue was credited to ahfs BornHack Mastercard"),
("PAID_TO_VIDIRS_MASTERCARD", "Revenue was credited to Vidirs BornHack Mastercard"),
("PAID_IN_NETBANK", "Revenue was transferred to a BornHack bank account"),
("PAID_IN_CASH", "Revenue was paid to BornHack with cash"),
),
),
(
"Paid to Participant",
(("PAID_NEEDS_REDISBURSEMENT", "Revenue has been paid out to me, a redisbursement is needed"),),
),
(
"Unpaid",
(("UNPAID_NEEDS_PAYMENT", "Revenue is unpaid"),),
),
]


class RevenueUpdateForm(forms.ModelForm):
class RevenueCreateForm(RevenueUpdateForm, CleanInvoiceMixin):
invoice = forms.FileField()

class Meta:
model = Revenue
fields = ["description", "amount", "invoice_date", "responsible_team"]
fields = ["description", "amount", "payment_status", "invoice_date", "invoice"]
Loading
Loading