diff --git a/src/backoffice/templates/chain_detail_backoffice.html b/src/backoffice/templates/chain_detail_backoffice.html
index 547a3032..814bb7c3 100644
--- a/src/backoffice/templates/chain_detail_backoffice.html
+++ b/src/backoffice/templates/chain_detail_backoffice.html
@@ -7,7 +7,7 @@ Details for Chain {{ chain.name }} | {{ block.super }}
{% block content %}
@@ -30,9 +30,9 @@ Select Chain | {{ block.super }}
{{ chain.name }} |
{{ chain.notes|default:"N/A" }} |
{{ chain.credebtors.count }} |
- {{ chain.expenses.count }} |
+ {{ chain.expenses_count }} |
{{ chain.expenses_total|default:"0" }} DKK |
- {{ chain.revenues.count }} |
+ {{ chain.revenues_count }} |
{{ chain.revenues_total|default:"0" }} DKK |
Details
diff --git a/src/backoffice/templates/credebtor_detail_backoffice.html b/src/backoffice/templates/credebtor_detail_backoffice.html
index 8a0e7baa..1d2151f2 100644
--- a/src/backoffice/templates/credebtor_detail_backoffice.html
+++ b/src/backoffice/templates/credebtor_detail_backoffice.html
@@ -7,7 +7,7 @@ Details for Credebtor {{ credebtor.name }} (Chain {{ credebtor.chain.name }}) |
{% block content %}
Details for Credebtor {{ credebtor.name }} (Chain {{ credebtor.chain.name }})
-Back to Credebtor list
+ Back to Credebtor list
{{ credebtor.expenses.count }} Expenses for Credebtor {{ credebtor.name }}
{% include 'includes/expense_list_panel.html' with expense_list=credebtor.expenses.all %}
diff --git a/src/economy/admin.py b/src/economy/admin.py
index bf08576c..7280cbc6 100644
--- a/src/economy/admin.py
+++ b/src/economy/admin.py
@@ -16,8 +16,8 @@ class ChainAdmin(admin.ModelAdmin):
@admin.register(Credebtor)
class CredebtorAdmin(admin.ModelAdmin):
list_filter = ["chain", "name"]
- list_display = ["chain", "name", "notes"]
- search_fields = ["chain", "name", "notes"]
+ list_display = ["name", "chain", "address", "notes"]
+ search_fields = ["chain", "name", "address", "notes"]
###############################
diff --git a/src/economy/models.py b/src/economy/models.py
index a31a7b26..fb604359 100644
--- a/src/economy/models.py
+++ b/src/economy/models.py
@@ -20,12 +20,17 @@ from .email import (
class ChainManager(models.Manager):
"""
ChainManager adds 'expenses_total' and 'revenues_total' to the Chain qs
+ Also adds 'expenses_count' and 'revenues_count' and prefetches all expenses
+ and revenues for the credebtors.
"""
def get_queryset(self):
qs = super().get_queryset()
+ qs = qs.prefetch_related("credebtors__expenses", "credebtors__revenues")
qs = qs.annotate(expenses_total=models.Sum("credebtors__expenses__amount"))
+ qs = qs.annotate(expenses_count=models.Count("credebtors__expenses", distinct=True))
qs = qs.annotate(revenues_total=models.Sum("credebtors__revenues__amount"))
+ qs = qs.annotate(revenues_count=models.Count("credebtors__revenues", distinct=True))
return qs
@@ -81,11 +86,13 @@ class Chain(CreatedUpdatedModel, UUIDModel):
class CredebtorManager(models.Manager):
"""
- CredebtorManager adds 'expenses_total' and 'revenues_total' to the Credebtor qs
+ CredebtorManager adds 'expenses_total' and 'revenues_total' to the Credebtor qs,
+ and prefetches expenses and revenues for the credebtor(s).
"""
def get_queryset(self):
qs = super().get_queryset()
+ qs = qs.prefetch_related("expenses", "revenues")
qs = qs.annotate(expenses_total=models.Sum("expenses__amount"))
qs = qs.annotate(revenues_total=models.Sum("revenues__amount"))
return qs
diff --git a/src/economy/templates/includes/expense_list_panel.html b/src/economy/templates/includes/expense_list_panel.html
index c5050d43..78128580 100644
--- a/src/economy/templates/includes/expense_list_panel.html
+++ b/src/economy/templates/includes/expense_list_panel.html
@@ -1,5 +1,5 @@
{% if expense_list %}
-
+
Invoice Date |
diff --git a/src/utils/management/commands/bootstrap-devsite.py b/src/utils/management/commands/bootstrap_devsite.py
similarity index 93%
rename from src/utils/management/commands/bootstrap-devsite.py
rename to src/utils/management/commands/bootstrap_devsite.py
index b1757d96..a14dcba3 100644
--- a/src/utils/management/commands/bootstrap-devsite.py
+++ b/src/utils/management/commands/bootstrap_devsite.py
@@ -12,6 +12,7 @@ from django.contrib.auth.models import User
from django.contrib.gis.geos import Point
from django.core.exceptions import ValidationError
from django.core.management.base import BaseCommand
+from django.core.management import call_command
from django.db.models.signals import post_save
from django.utils import timezone
from django.utils.crypto import get_random_string
@@ -52,13 +53,65 @@ from tickets.models import TicketType
from tokens.models import Token, TokenFind
from utils.slugs import unique_slugify
from villages.models import Village
+from economy.models import Chain, Credebtor, Expense, Revenue
fake = Faker()
tz = pytz.timezone("Europe/Copenhagen")
logger = logging.getLogger("bornhack.%s" % __name__)
-@factory.django.mute_signals(post_save)
+class ChainFactory(factory.django.DjangoModelFactory):
+ class Meta:
+ model = Chain
+
+ name = factory.Faker("company")
+ slug = factory.LazyAttribute(lambda f: unique_slugify(f.name, Chain.objects.all().values_list("slug", flat=True)))
+
+
+class CredebtorFactory(factory.django.DjangoModelFactory):
+ class Meta:
+ model = Credebtor
+
+ chain = factory.SubFactory(ChainFactory)
+ name = factory.Faker("company")
+ slug = factory.LazyAttribute(lambda f: unique_slugify(f.name, Credebtor.objects.all().values_list("slug", flat=True)))
+ address = factory.Faker("address", locale="dk_DK")
+ notes = factory.Faker("text")
+
+
+class ExpenseFactory(factory.django.DjangoModelFactory):
+ class Meta:
+ model = Expense
+
+ camp = factory.Faker("random_element", elements=Camp.objects.all())
+ creditor = factory.Faker("random_element", elements=Credebtor.objects.all())
+ user = factory.Faker("random_element", elements=User.objects.all())
+ amount = factory.Faker("random_int", min=20, max=20000)
+ description = factory.Faker("text")
+ paid_by_bornhack = factory.Faker("random_element", elements=[True, True, False])
+ invoice = factory.django.ImageField(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])
+ notes = factory.Faker("text")
+
+
+class RevenueFactory(factory.django.DjangoModelFactory):
+ class Meta:
+ model = Revenue
+
+ camp = factory.Faker("random_element", elements=Camp.objects.all())
+ debtor = factory.Faker("random_element", elements=Credebtor.objects.all())
+ user = factory.Faker("random_element", elements=User.objects.all())
+ amount = factory.Faker("random_int", min=20, max=20000)
+ description = factory.Faker("text")
+ invoice = factory.django.ImageField(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])
+ notes = factory.Faker("text")
+
+
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = Profile
@@ -159,8 +212,9 @@ class Command(BaseCommand):
dict(year=2017, tagline="Make Tradition", colour="#750787", read_only=True),
dict(year=2018, tagline="scale it", colour="#008026", read_only=True),
dict(year=2019, tagline="a new /home", colour="#ffed00", read_only=True),
- dict(year=2020, tagline="Going Viral", colour="#ff8c00", read_only=False),
+ dict(year=2020, tagline="Make Clean", colour="#ff8c00", read_only=False),
dict(year=2021, tagline="Undecided", colour="#e40303", read_only=False),
+ dict(year=2022, tagline="Undecided", colour="#004dff", read_only=False),
]
camp_instances = []
@@ -508,6 +562,21 @@ class Command(BaseCommand):
name="Recording", defaults={"icon": "fas fa-film"}
)
+ def create_credebtors(self):
+ self.output("Creating Chains and Credebtors...")
+ try:
+ CredebtorFactory.create_batch(50)
+ except ValidationError:
+ self.outout("Name conflict, retrying...")
+ CredebtorFactory.create_batch(50)
+ for _ in range(20):
+ # add 20 more credebtors to random existing chains
+ try:
+ CredebtorFactory.create(chain=Chain.objects.order_by("?").first())
+ except ValidationError:
+ self.outout("Name conflict, skipping...")
+ continue
+
def create_product_categories(self):
categories = {}
self.output("Creating productcategories...")
@@ -686,7 +755,7 @@ class Command(BaseCommand):
orders = {}
self.output("Creating orders...")
orders[0] = Order.objects.create(
- user=users[1], payment_method="cash", open=None, paid=True
+ user=users[1], payment_method="in_person", open=None, paid=True
)
orders[0].orderproductrelation_set.create(
product=camp_products["ticket1"], quantity=1
@@ -697,7 +766,7 @@ class Command(BaseCommand):
orders[0].mark_as_paid(request=None)
orders[1] = Order.objects.create(
- user=users[2], payment_method="cash", open=None
+ user=users[2], payment_method="in_person", open=None
)
orders[1].orderproductrelation_set.create(
product=camp_products["ticket1"], quantity=1
@@ -708,7 +777,7 @@ class Command(BaseCommand):
orders[1].mark_as_paid(request=None)
orders[2] = Order.objects.create(
- user=users[3], payment_method="cash", open=None
+ user=users[3], payment_method="in_person", open=None
)
orders[2].orderproductrelation_set.create(
product=camp_products["ticket2"], quantity=1
@@ -722,7 +791,7 @@ class Command(BaseCommand):
orders[2].mark_as_paid(request=None)
orders[3] = Order.objects.create(
- user=users[4], payment_method="cash", open=None
+ user=users[4], payment_method="in_person", open=None
)
orders[3].orderproductrelation_set.create(
product=global_products["product0"], quantity=1
@@ -1189,6 +1258,13 @@ class Command(BaseCommand):
mailing_list="content@example.com",
permission_set="contentteam_permission",
)
+ teams["economy"] = Team.objects.create(
+ name="Economy",
+ description="The Economy Team handles the money and accounts.",
+ camp=camp,
+ mailing_list="economy@example.com",
+ permission_set="economyteam_permission",
+ )
return teams
@@ -1600,6 +1676,14 @@ class Command(BaseCommand):
for i in range(0, 6):
TokenFind.objects.create(token=tokens[i], user=users[1])
+ def create_camp_expenses(self, camp):
+ self.output(f"Creating expenses for {camp}...")
+ ExpenseFactory.create_batch(200, camp=camp)
+
+ def create_camp_revenues(self, camp):
+ self.output(f"Creating revenues for {camp}...")
+ RevenueFactory.create_batch(20, camp=camp)
+
def output(self, message):
self.stdout.write(
"%s: %s" % (timezone.now().strftime("%Y-%m-%d %H:%M:%S"), message)
@@ -1610,6 +1694,13 @@ class Command(BaseCommand):
self.output(
self.style.SUCCESS("----------[ Running bootstrap-devsite ]----------")
)
+ self.output(
+ self.style.SUCCESS(
+ "----------[ Deleting all data from database ]----------"
+ )
+ )
+ call_command("flush", "--noinput")
+
self.output(self.style.SUCCESS("----------[ Global stuff ]----------"))
camps = self.create_camps()
@@ -1628,6 +1719,8 @@ class Command(BaseCommand):
quickfeedback_options = self.create_quickfeedback_options()
+ self.create_credebtors()
+
for (camp, read_only) in camps:
year = camp.camp.lower.year
@@ -1635,7 +1728,7 @@ class Command(BaseCommand):
self.style.SUCCESS("----------[ Bornhack {} ]----------".format(year))
)
- if year < 2021:
+ if year < 2022:
ticket_types = self.create_camp_ticket_types(camp)
camp_products = self.create_camp_products(
@@ -1716,6 +1809,10 @@ class Command(BaseCommand):
tokens = self.create_camp_tokens(camp)
self.create_camp_token_finds(camp, tokens, users)
+
+ self.create_camp_expenses(camp)
+
+ self.create_camp_revenues(camp)
else:
self.output("Not creating anything for this year yet")
|