more work on backoffice economy stuff, chains and credebtors section now only shows expenses and revenues for the current camp, thanks to some ORM foo; while here add a cost and a comment field to the Product table, and fix a bug in bootstrap_devsite and a few nags to make new flake8 happy
This commit is contained in:
parent
833fc85e46
commit
fbda2b4b53
|
@ -6,44 +6,56 @@ Details for Chain {{ chain.name }} | {{ block.super }}
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Details for Chain {{ chain.name }}</h2>
|
||||
<a class="btn btn-default" href="{% url "backoffice:chain_list" camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Back to Chain list</a>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><h3 class="panel-title">Details for Chain {{ chain.name }} - BackOffice</h3></div>
|
||||
<div class="panel-body">
|
||||
<p class="lead">This chain has <b>{{ chain.camp_expenses_count }} expenses</b> for a total of <b>{{ chain.camp_expenses_amount }} DKK</b> and <b>{{ chain.camp_revenues_count }} revenues</b> for a total of <b>{{ chain.camp_revenues_amount }} DKK</b> for {{ camp.title }}.</p>
|
||||
<p class="lead">This chain has <b>{{ chain.all_expenses_count }} expenses</b> for a total of <b>{{ chain.all_expenses_amount }} DKK</b> and <b>{{ chain.all_revenues_count }} revenues</b> for a total of <b>{{ chain.all_revenues_amount }} DKK</b> across all camps.</p>
|
||||
<a class="btn btn-default" href="{% url "backoffice:chain_list" camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Back to Chain list</a>
|
||||
|
||||
<h3>{{ chain.credebtors.count }} Credebtors for Chain {{ chain.name }}</h3>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Credebtor Name</th>
|
||||
<th>Address</th>
|
||||
<th>Notes</th>
|
||||
<th class="text-center">Expenses</th>
|
||||
<th class="text-center">Expenses Total</th>
|
||||
<th class="text-center">Revenues</th>
|
||||
<th class="text-center">Revenues Total</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for credebtor in chain.credebtors.all %}
|
||||
<tr>
|
||||
<td><a class="btn btn-primary" href="{% url 'backoffice:credebtor_detail' camp_slug=camp.slug chain_slug=chain.slug credebtor_slug=credebtor.slug %}">{{ credebtor.name }}</a></td>
|
||||
<td><address>{{ credebtor.address }}</address></td>
|
||||
<td>{{ credebtor.notes|default:"N/A" }}</td>
|
||||
<td class="text-center"><span class="badge">{{ credebtor.expenses.count }}</span></td>
|
||||
<td class="text-center">{{ credebtor.expenses_total }} DKK</td>
|
||||
<td class="text-center"><span class="badge">{{ credebtor.revenues.count }}</span></td>
|
||||
<td class="text-center">{{ credebtor.revenues_total }} DKK</td>
|
||||
<td>
|
||||
<a class="btn btn-primary" href="{% url 'backoffice:credebtor_detail' camp_slug=camp.slug chain_slug=chain.slug credebtor_slug=credebtor.slug %}"><i class="fas fa-search"></i> Details</a>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<hr>
|
||||
|
||||
<h3>{{ chain.expenses_count }} Expenses for Chain {{ chain.name }}</h3>
|
||||
{% include 'includes/expense_list_panel.html' with expense_list=chain.expenses.all %}
|
||||
<h3>{{ chain.credebtors.count }} Credebtors for Chain {{ chain.name }}</h3>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Credebtor Name</th>
|
||||
<th>Address</th>
|
||||
<th>Notes</th>
|
||||
<th class="text-center">Expenses</th>
|
||||
<th class="text-center">Expenses Total</th>
|
||||
<th class="text-center">Revenues</th>
|
||||
<th class="text-center">Revenues Total</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for credebtor in credebtors %}
|
||||
<tr>
|
||||
<td><a class="btn btn-primary" href="{% url 'backoffice:credebtor_detail' camp_slug=camp.slug chain_slug=chain.slug credebtor_slug=credebtor.slug %}">{{ credebtor.name }}</a></td>
|
||||
<td><address>{{ credebtor.address }}</address></td>
|
||||
<td>{{ credebtor.notes|default:"N/A" }}</td>
|
||||
<td class="text-center"><span class="badge">{{ credebtor.camp_expenses_count }}</span></td>
|
||||
<td class="text-center">{{ credebtor.camp_expenses_amount }} DKK</td>
|
||||
<td class="text-center"><span class="badge">{{ credebtor.camp_revenues_count }}</span></td>
|
||||
<td class="text-center">{{ credebtor.camp_revenues_amount }} DKK</td>
|
||||
<td>
|
||||
<a class="btn btn-primary" href="{% url 'backoffice:credebtor_detail' camp_slug=camp.slug chain_slug=chain.slug credebtor_slug=credebtor.slug %}"><i class="fas fa-search"></i> Details</a>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>{{ chain.revenues_count }} Revenues for Chain {{ chain.name }}</h3>
|
||||
{% include 'includes/revenue_list_panel.html' with revenue_list=chain.revenues.all %}
|
||||
<hr>
|
||||
|
||||
<h3>{{ expenses.count }} Expenses for Chain {{ chain.name }}</h3>
|
||||
{% include 'includes/expense_list_panel.html' with expense_list=expenses %}
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>{{ revenues.count }} Revenues for Chain {{ chain.name }}</h3>
|
||||
{% include 'includes/revenue_list_panel.html' with revenue_list=revenues %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
|
|
|
@ -7,42 +7,46 @@ Select Chain | {{ block.super }}
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Chains</h2>
|
||||
<p><a class="btn btn-default" href="{% url "backoffice:index" camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Back to Backoffice Index</a></p>
|
||||
|
||||
{% if chain_list %}
|
||||
<table class="table table-hover datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Chain Name</th>
|
||||
<th>Notes</th>
|
||||
<th class="text-center">Credebtors</th>
|
||||
<th class="text-center">Expenses</th>
|
||||
<th class="text-center">Expenses Total</th>
|
||||
<th class="text-center">Revenues</th>
|
||||
<th class="text-center">Revenues Total</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for chain in chain_list %}
|
||||
<tr>
|
||||
<td>{{ chain.name }}</td>
|
||||
<td>{{ chain.notes|default:"N/A" }}</td>
|
||||
<td class="text-center"><span class="badge">{{ chain.credebtors.count }}</span></td>
|
||||
<td class="text-center"><span class="badge">{{ chain.expenses_count }}</span></td>
|
||||
<td class="text-center">{{ chain.expenses_total|default:"0" }} DKK</td>
|
||||
<td class="text-center"><span class="badge">{{ chain.revenues_count }}</span></td>
|
||||
<td class="text-center">{{ chain.revenues_total|default:"0" }} DKK</td>
|
||||
<td>
|
||||
<a class="btn btn-primary" href="{% url 'backoffice:chain_detail' camp_slug=camp.slug chain_slug=chain.slug %}"><i class="fas fa-search"></i> Details</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="lead"><i>No Chains found.</i></p>
|
||||
{% endif %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><h3 class="panel-title">Chains - BackOffice</h3></div>
|
||||
<div class="panel-body">
|
||||
<p class="lead">Showing {{ chain_list.count }} chains. Not all chains have expenses or revenues for {{ camp.title }}.</p>
|
||||
<p><a class="btn btn-default" href="{% url "backoffice:index" camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Back to Backoffice Index</a></p>
|
||||
|
||||
{% if chain_list %}
|
||||
<table class="table table-hover datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Chain Name</th>
|
||||
<th>Notes</th>
|
||||
<th class="text-center">Credebtors</th>
|
||||
<th class="text-center">Expenses</th>
|
||||
<th class="text-center">Expenses Total</th>
|
||||
<th class="text-center">Revenues</th>
|
||||
<th class="text-center">Revenues Total</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for chain in chain_list %}
|
||||
<tr>
|
||||
<td>{{ chain.name }}</td>
|
||||
<td>{{ chain.notes|default:"N/A" }}</td>
|
||||
<td class="text-center"><span class="badge">{{ chain.credebtors.count }}</span></td>
|
||||
<td class="text-center"><span class="badge">{{ chain.camp_expenses_count }}</span></td>
|
||||
<td class="text-center">{{ chain.camp_expenses_amount|default:"0" }} DKK</td>
|
||||
<td class="text-center"><span class="badge">{{ chain.camp_revenues_count }}</span></td>
|
||||
<td class="text-center">{{ chain.camp_revenues_amount|default:"0" }} DKK</td>
|
||||
<td>
|
||||
<a class="btn btn-primary" href="{% url 'backoffice:chain_detail' camp_slug=camp.slug chain_slug=chain.slug %}"><i class="fas fa-search"></i> Details</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="lead"><i>No Chains found.</i></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -6,14 +6,17 @@ Details for Credebtor {{ credebtor.name }} (Chain {{ credebtor.chain.name }}) |
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Details for Credebtor {{ credebtor.name }} (Chain {{ credebtor.chain.name }})</h2>
|
||||
<a class="btn btn-default" href="{% url "backoffice:chain_detail" camp_slug=camp.slug chain_slug=credebtor.chain.slug %}"><i class="fas fa-undo"></i> Back to Credebtor list</a>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><h3 class="panel-title">Details for Credebtor {{ credebtor.name }} (Chain {{ credebtor.chain.name }}) - BackOffice</h3></div>
|
||||
<div class="panel-body">
|
||||
<a class="btn btn-default" href="{% url "backoffice:chain_detail" camp_slug=camp.slug chain_slug=credebtor.chain.slug %}"><i class="fas fa-undo"></i> Back to Credebtor list for {{ credebtor.chain.name }}</a>
|
||||
|
||||
<h3>{{ credebtor.expenses.count }} Expenses for Credebtor {{ credebtor.name }}</h3>
|
||||
{% include 'includes/expense_list_panel.html' with expense_list=credebtor.expenses.all %}
|
||||
|
||||
<h3>{{ credebtor.revenues.count }} Revenues for Credebtor {{ credebtor.name }}</h3>
|
||||
{% include 'includes/revenue_list_panel.html' with revenue_list=credebtor.revenues.all %}
|
||||
<h3>{{ expenses.count }} Expenses for Credebtor {{ credebtor.name }}</h3>
|
||||
{% include 'includes/expense_list_panel.html' with expense_list=expenses %}
|
||||
|
||||
<h3>{{ revenues.count }} Revenues for Credebtor {{ credebtor.name }}</h3>
|
||||
{% include 'includes/revenue_list_panel.html' with revenue_list=revenues %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
|
|
|
@ -298,7 +298,11 @@ class FacilityCreateView(CampViewMixin, OrgaTeamPermissionMixin, CreateView):
|
|||
|
||||
def get_form(self, *args, **kwargs):
|
||||
form = super().get_form(*args, **kwargs)
|
||||
form.fields["location"].widget = LeafletWidget(attrs={"display_raw": "true",})
|
||||
form.fields["location"].widget = LeafletWidget(
|
||||
attrs={
|
||||
"display_raw": "true",
|
||||
}
|
||||
)
|
||||
return form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -324,7 +328,11 @@ class FacilityUpdateView(CampViewMixin, OrgaTeamPermissionMixin, UpdateView):
|
|||
|
||||
def get_form(self, *args, **kwargs):
|
||||
form = super().get_form(*args, **kwargs)
|
||||
form.fields["location"].widget = LeafletWidget(attrs={"display_raw": "true",})
|
||||
form.fields["location"].widget = LeafletWidget(
|
||||
attrs={
|
||||
"display_raw": "true",
|
||||
}
|
||||
)
|
||||
return form
|
||||
|
||||
def get_success_url(self):
|
||||
|
@ -445,7 +453,7 @@ class FacilityOpeningHoursCreateView(
|
|||
hours = form.save(commit=False)
|
||||
hours.facility = self.facility
|
||||
hours.save()
|
||||
messages.success(self.request, f"New opening hours created successfully!")
|
||||
messages.success(self.request, "New opening hours created successfully!")
|
||||
return redirect(
|
||||
reverse(
|
||||
"backoffice:facility_detail",
|
||||
|
@ -547,7 +555,9 @@ class SpeakerProposalListView(CampViewMixin, ContentTeamPermissionMixin, ListVie
|
|||
|
||||
|
||||
class SpeakerProposalDetailView(
|
||||
AvailabilityMatrixViewMixin, ContentTeamPermissionMixin, DetailView,
|
||||
AvailabilityMatrixViewMixin,
|
||||
ContentTeamPermissionMixin,
|
||||
DetailView,
|
||||
):
|
||||
""" This view permits Content Team members to see SpeakerProposal details """
|
||||
|
||||
|
@ -915,15 +925,16 @@ class EventDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteView):
|
|||
|
||||
def get_success_url(self):
|
||||
messages.success(
|
||||
self.request, f"Event '{self.get_object().title}' has been deleted!",
|
||||
self.request,
|
||||
f"Event '{self.get_object().title}' has been deleted!",
|
||||
)
|
||||
return reverse("backoffice:event_list", kwargs={"camp_slug": self.camp.slug})
|
||||
|
||||
|
||||
class EventScheduleView(CampViewMixin, ContentTeamPermissionMixin, FormView):
|
||||
""" This view is used by the Content Team to manually schedule Events.
|
||||
"""This view is used by the Content Team to manually schedule Events.
|
||||
It shows a table with radioselect buttons for the available slots for the
|
||||
EventType of the Event """
|
||||
EventType of the Event"""
|
||||
|
||||
form_class = EventScheduleForm
|
||||
template_name = "event_schedule.html"
|
||||
|
@ -1259,9 +1270,9 @@ class AutoScheduleCrashCourseView(
|
|||
|
||||
|
||||
class AutoScheduleValidateView(CampViewMixin, ContentTeamPermissionMixin, FormView):
|
||||
""" This view is used to validate schedules. It uses the AutoScheduler and can
|
||||
"""This view is used to validate schedules. It uses the AutoScheduler and can
|
||||
either validate the currently applied schedule or a new similar schedule, or a
|
||||
brand new schedule """
|
||||
brand new schedule"""
|
||||
|
||||
template_name = "autoschedule_validate.html"
|
||||
form_class = AutoScheduleValidateForm
|
||||
|
@ -1314,7 +1325,7 @@ class AutoScheduleDiffView(CampViewMixin, ContentTeamPermissionMixin, TemplateVi
|
|||
|
||||
|
||||
class AutoScheduleApplyView(CampViewMixin, ContentTeamPermissionMixin, FormView):
|
||||
""" This view is used by the Content Team to apply a new schedules by unscheduling
|
||||
"""This view is used by the Content Team to apply a new schedules by unscheduling
|
||||
all autoscheduled Events, and scheduling all Event/Slot combinations in the schedule.
|
||||
|
||||
TODO: see comment in program.autoscheduler.AutoScheduler.apply() method.
|
||||
|
@ -1481,18 +1492,118 @@ class ChainListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
|
|||
model = Chain
|
||||
template_name = "chain_list_backoffice.html"
|
||||
|
||||
def get_queryset(self, *args, **kwargs):
|
||||
"""Annotate the total count and amount for expenses and revenues for all credebtors in each chain."""
|
||||
qs = Chain.objects.annotate(
|
||||
camp_expenses_amount=Sum(
|
||||
"credebtors__expenses__amount",
|
||||
filter=Q(credebtors__expenses__camp=self.camp),
|
||||
distinct=True,
|
||||
),
|
||||
camp_expenses_count=Count(
|
||||
"credebtors__expenses",
|
||||
filter=Q(credebtors__expenses__camp=self.camp),
|
||||
distinct=True,
|
||||
),
|
||||
camp_revenues_amount=Sum(
|
||||
"credebtors__revenues__amount",
|
||||
filter=Q(credebtors__revenues__camp=self.camp),
|
||||
distinct=True,
|
||||
),
|
||||
camp_revenues_count=Count(
|
||||
"credebtors__revenues",
|
||||
filter=Q(credebtors__revenues__camp=self.camp),
|
||||
distinct=True,
|
||||
),
|
||||
)
|
||||
return qs
|
||||
|
||||
|
||||
class ChainDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView):
|
||||
model = Chain
|
||||
template_name = "chain_detail_backoffice.html"
|
||||
slug_url_kwarg = "chain_slug"
|
||||
|
||||
def get_queryset(self, *args, **kwargs):
|
||||
"""Annotate the Chain object with the camp filtered expense and revenue info."""
|
||||
qs = super().get_queryset(*args, **kwargs)
|
||||
qs = qs.annotate(
|
||||
camp_expenses_amount=Sum(
|
||||
"credebtors__expenses__amount",
|
||||
filter=Q(credebtors__expenses__camp=self.camp),
|
||||
distinct=True,
|
||||
),
|
||||
camp_expenses_count=Count(
|
||||
"credebtors__expenses",
|
||||
filter=Q(credebtors__expenses__camp=self.camp),
|
||||
distinct=True,
|
||||
),
|
||||
camp_revenues_amount=Sum(
|
||||
"credebtors__revenues__amount",
|
||||
filter=Q(credebtors__revenues__camp=self.camp),
|
||||
distinct=True,
|
||||
),
|
||||
camp_revenues_count=Count(
|
||||
"credebtors__revenues",
|
||||
filter=Q(credebtors__revenues__camp=self.camp),
|
||||
distinct=True,
|
||||
),
|
||||
)
|
||||
return qs
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
"""Add credebtors, expenses and revenues to the context in camp-filtered versions."""
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
|
||||
# include credebtors as a seperate queryset with annotations for total number and
|
||||
# amount of expenses and revenues
|
||||
context["credebtors"] = Credebtor.objects.filter(
|
||||
chain=self.get_object()
|
||||
).annotate(
|
||||
camp_expenses_amount=Sum(
|
||||
"expenses__amount", filter=Q(expenses__camp=self.camp), distinct=True
|
||||
),
|
||||
camp_expenses_count=Count(
|
||||
"expenses", filter=Q(expenses__camp=self.camp), distinct=True
|
||||
),
|
||||
camp_revenues_amount=Sum(
|
||||
"revenues__amount", filter=Q(revenues__camp=self.camp), distinct=True
|
||||
),
|
||||
camp_revenues_count=Count(
|
||||
"revenues", filter=Q(revenues__camp=self.camp), distinct=True
|
||||
),
|
||||
)
|
||||
|
||||
# Include expenses and revenues for the Chain in context as seperate querysets,
|
||||
# since accessing them through the relatedmanager returns for all camps
|
||||
context["expenses"] = Expense.objects.filter(
|
||||
camp=self.camp, creditor__chain=self.get_object()
|
||||
).prefetch_related("responsible_team", "user", "creditor")
|
||||
context["revenues"] = Revenue.objects.filter(
|
||||
camp=self.camp, debtor__chain=self.get_object()
|
||||
).prefetch_related("responsible_team", "user", "debtor")
|
||||
return context
|
||||
|
||||
|
||||
class CredebtorDetailView(CampViewMixin, EconomyTeamPermissionMixin, DetailView):
|
||||
model = Credebtor
|
||||
template_name = "credebtor_detail_backoffice.html"
|
||||
slug_url_kwarg = "credebtor_slug"
|
||||
|
||||
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")
|
||||
)
|
||||
context["revenues"] = (
|
||||
self.get_object()
|
||||
.revenues.filter(camp=self.camp)
|
||||
.prefetch_related("responsible_team", "user", "debtor")
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
################################
|
||||
# EXPENSES
|
||||
|
@ -1507,7 +1618,11 @@ class ExpenseListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
|
|||
Exclude unapproved expenses, they are shown seperately
|
||||
"""
|
||||
queryset = super().get_queryset(**kwargs)
|
||||
return queryset.exclude(approved__isnull=True)
|
||||
return queryset.exclude(approved__isnull=True).prefetch_related(
|
||||
"creditor",
|
||||
"user",
|
||||
"responsible_team",
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
|
@ -1516,6 +1631,10 @@ class ExpenseListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
|
|||
context = super().get_context_data(**kwargs)
|
||||
context["unapproved_expenses"] = Expense.objects.filter(
|
||||
camp=self.camp, approved__isnull=True
|
||||
).prefetch_related(
|
||||
"creditor",
|
||||
"user",
|
||||
"responsible_team",
|
||||
)
|
||||
return context
|
||||
|
||||
|
@ -1747,7 +1866,11 @@ class RevenueListView(CampViewMixin, EconomyTeamPermissionMixin, ListView):
|
|||
Exclude unapproved revenues, they are shown seperately
|
||||
"""
|
||||
queryset = super().get_queryset(**kwargs)
|
||||
return queryset.exclude(approved__isnull=True)
|
||||
return queryset.exclude(approved__isnull=True).prefetch_related(
|
||||
"debtor",
|
||||
"user",
|
||||
"responsible_team",
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
|
@ -2040,7 +2163,8 @@ class PosReportCreateView(PosViewMixin, CreateView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["form"].fields["bank_responsible"].queryset = Team.objects.get(
|
||||
camp=self.camp, name="Orga",
|
||||
camp=self.camp,
|
||||
name="Orga",
|
||||
).approved_members.all()
|
||||
context["form"].fields[
|
||||
"pos_responsible"
|
||||
|
@ -2054,7 +2178,7 @@ class PosReportCreateView(PosViewMixin, CreateView):
|
|||
pr = form.save(commit=False)
|
||||
pr.pos = self.pos
|
||||
pr.save()
|
||||
messages.success(self.request, f"New PosReport created successfully!")
|
||||
messages.success(self.request, "New PosReport created successfully!")
|
||||
return redirect(
|
||||
reverse(
|
||||
"backoffice:posreport_detail",
|
||||
|
@ -2086,7 +2210,8 @@ class PosReportUpdateView(PosViewMixin, UpdateView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["form"].fields["bank_responsible"].queryset = Team.objects.get(
|
||||
camp=self.camp, name="Orga",
|
||||
camp=self.camp,
|
||||
name="Orga",
|
||||
).approved_members.all()
|
||||
context["form"].fields[
|
||||
"pos_responsible"
|
||||
|
|
|
@ -26,11 +26,26 @@ class ChainManager(models.Manager):
|
|||
|
||||
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))
|
||||
qs = qs.prefetch_related(
|
||||
models.Prefetch("credebtors__expenses", to_attr="all_expenses"),
|
||||
models.Prefetch("credebtors__revenues", to_attr="all_revenues"),
|
||||
)
|
||||
qs = qs.annotate(
|
||||
all_expenses_amount=models.Sum(
|
||||
"credebtors__expenses__amount", distinct=True
|
||||
)
|
||||
)
|
||||
qs = qs.annotate(
|
||||
all_expenses_count=models.Count("credebtors__expenses", distinct=True)
|
||||
)
|
||||
qs = qs.annotate(
|
||||
all_revenues_amount=models.Sum(
|
||||
"credebtors__revenues__amount", distinct=True
|
||||
)
|
||||
)
|
||||
qs = qs.annotate(
|
||||
all_revenues_count=models.Count("credebtors__revenues", distinct=True)
|
||||
)
|
||||
return qs
|
||||
|
||||
|
||||
|
@ -92,9 +107,12 @@ class CredebtorManager(models.Manager):
|
|||
|
||||
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"))
|
||||
qs = qs.prefetch_related(
|
||||
models.Prefetch("expenses", to_attr="all_expenses"),
|
||||
models.Prefetch("revenues", to_attr="all_revenues"),
|
||||
)
|
||||
qs = qs.annotate(all_expenses_amount=models.Sum("expenses__amount"))
|
||||
qs = qs.annotate(all_revenues_amount=models.Sum("revenues__amount"))
|
||||
return qs
|
||||
|
||||
|
||||
|
@ -494,7 +512,9 @@ class Pos(CampRelatedModel, UUIDModel):
|
|||
)
|
||||
|
||||
team = models.ForeignKey(
|
||||
"teams.Team", on_delete=models.PROTECT, help_text="The Team managning this POS",
|
||||
"teams.Team",
|
||||
on_delete=models.PROTECT,
|
||||
help_text="The Team managning this POS",
|
||||
)
|
||||
|
||||
def save(self, **kwargs):
|
||||
|
@ -559,7 +579,8 @@ class PosReport(CampRelatedModel, UUIDModel):
|
|||
)
|
||||
|
||||
comments = models.TextField(
|
||||
blank=True, help_text="Any comments about this PosReport",
|
||||
blank=True,
|
||||
help_text="Any comments about this PosReport",
|
||||
)
|
||||
|
||||
dkk_sales_izettle = models.PositiveIntegerField(
|
||||
|
@ -567,7 +588,8 @@ class PosReport(CampRelatedModel, UUIDModel):
|
|||
)
|
||||
|
||||
hax_sold_izettle = models.PositiveIntegerField(
|
||||
default=0, help_text="The number of HAX sold through the iZettle from the POS",
|
||||
default=0,
|
||||
help_text="The number of HAX sold through the iZettle from the POS",
|
||||
)
|
||||
|
||||
hax_sold_website = models.PositiveIntegerField(
|
||||
|
|
28
src/shop/migrations/0064_add_product_comment_and_cost.py
Normal file
28
src/shop/migrations/0064_add_product_comment_and_cost.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Generated by Django 3.1 on 2020-10-17 00:30
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("shop", "0063_auto_20200812_1732"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="product",
|
||||
name="comment",
|
||||
field=models.TextField(
|
||||
blank=True, help_text="Internal comments for this product."
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="product",
|
||||
name="cost",
|
||||
field=models.IntegerField(
|
||||
default=0,
|
||||
help_text="The cost for this product, including VAT. Used for profit calculations in the economy system.",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -432,6 +432,15 @@ class Product(CreatedUpdatedModel, UUIDModel):
|
|||
blank=True,
|
||||
)
|
||||
|
||||
cost = models.IntegerField(
|
||||
default=0,
|
||||
help_text="The cost for this product, including VAT. Used for profit calculations in the economy system.",
|
||||
)
|
||||
|
||||
comment = models.TextField(
|
||||
blank=True, help_text="Internal comments for this product."
|
||||
)
|
||||
|
||||
objects = ProductQuerySet.as_manager()
|
||||
|
||||
def __str__(self):
|
||||
|
@ -442,7 +451,7 @@ class Product(CreatedUpdatedModel, UUIDModel):
|
|||
raise ValidationError("Products with category Tickets need a ticket_type")
|
||||
|
||||
def is_available(self):
|
||||
""" Is the product available or not?
|
||||
"""Is the product available or not?
|
||||
|
||||
Checks for the following:
|
||||
|
||||
|
|
|
@ -11,11 +11,12 @@ from camps.models import Camp
|
|||
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.core.management.base import BaseCommand
|
||||
from django.db.models.signals import post_save
|
||||
from django.utils import timezone
|
||||
from django.utils.crypto import get_random_string
|
||||
from economy.models import Chain, Credebtor, Expense, Revenue
|
||||
from events.models import Routing, Type
|
||||
from facilities.models import (
|
||||
Facility,
|
||||
|
@ -53,7 +54,6 @@ 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")
|
||||
|
@ -65,7 +65,11 @@ class ChainFactory(factory.django.DjangoModelFactory):
|
|||
model = Chain
|
||||
|
||||
name = factory.Faker("company")
|
||||
slug = factory.LazyAttribute(lambda f: unique_slugify(f.name, Chain.objects.all().values_list("slug", flat=True)))
|
||||
slug = factory.LazyAttribute(
|
||||
lambda f: unique_slugify(
|
||||
f.name, Chain.objects.all().values_list("slug", flat=True)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class CredebtorFactory(factory.django.DjangoModelFactory):
|
||||
|
@ -74,7 +78,11 @@ class CredebtorFactory(factory.django.DjangoModelFactory):
|
|||
|
||||
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)))
|
||||
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")
|
||||
|
||||
|
@ -89,7 +97,9 @@ class ExpenseFactory(factory.django.DjangoModelFactory):
|
|||
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 = 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])
|
||||
|
@ -105,7 +115,9 @@ class RevenueFactory(factory.django.DjangoModelFactory):
|
|||
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 = 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])
|
||||
|
@ -567,7 +579,7 @@ class Command(BaseCommand):
|
|||
try:
|
||||
CredebtorFactory.create_batch(50)
|
||||
except ValidationError:
|
||||
self.outout("Name conflict, retrying...")
|
||||
self.output("Name conflict, retrying...")
|
||||
CredebtorFactory.create_batch(50)
|
||||
for _ in range(20):
|
||||
# add 20 more credebtors to random existing chains
|
||||
|
@ -856,10 +868,18 @@ class Command(BaseCommand):
|
|||
capacity=50,
|
||||
)
|
||||
locations["food_area"] = EventLocation.objects.create(
|
||||
name="Food Area", slug="food-area", icon="utensils", camp=camp, capacity=50,
|
||||
name="Food Area",
|
||||
slug="food-area",
|
||||
icon="utensils",
|
||||
camp=camp,
|
||||
capacity=50,
|
||||
)
|
||||
locations["infodesk"] = EventLocation.objects.create(
|
||||
name="Infodesk", slug="infodesk", icon="info", camp=camp, capacity=20,
|
||||
name="Infodesk",
|
||||
slug="infodesk",
|
||||
icon="info",
|
||||
camp=camp,
|
||||
capacity=20,
|
||||
)
|
||||
|
||||
# add workshop room conflicts (the big root can not be used while either
|
||||
|
|
Loading…
Reference in a new issue