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 %}

Details for Chain {{ chain.name }}

-Back to Chain list + Back to Chain list

{{ chain.credebtors.count }} Credebtors for Chain {{ chain.name }}

@@ -26,7 +26,7 @@ Details for Chain {{ chain.name }} | {{ block.super }} {% for credebtor in chain.credebtors.all %} - + @@ -39,10 +39,10 @@ Details for Chain {{ chain.name }} | {{ block.super }}
{{ credebtor.name }}{{ credebtor.name }}
{{ credebtor.address }}
{{ credebtor.notes|default:"N/A" }} {{ credebtor.expenses.count }}
-

{{ chain.expenses.count }} Expenses for Chain {{ chain.name }}

+

{{ chain.expenses_count }} Expenses for Chain {{ chain.name }}

{% include 'includes/expense_list_panel.html' with expense_list=chain.expenses.all %} -

{{ chain.revenues.count }} Revenues for Chain {{ chain.name }}

+

{{ chain.revenues_count }} Revenues for Chain {{ chain.name }}

{% include 'includes/revenue_list_panel.html' with revenue_list=chain.revenues.all %} {% endblock content %} diff --git a/src/backoffice/templates/chain_list_backoffice.html b/src/backoffice/templates/chain_list_backoffice.html index 95eecc88..b075cc08 100644 --- a/src/backoffice/templates/chain_list_backoffice.html +++ b/src/backoffice/templates/chain_list_backoffice.html @@ -8,7 +8,7 @@ Select Chain | {{ block.super }} {% block content %}

Chains

-

Back to Backoffice Index

+

Back to Backoffice Index

{% if chain_list %} @@ -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 %} - +
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")
Invoice Date