add initial POS stuff

This commit is contained in:
Thomas Steen Rasmussen 2020-08-11 02:22:36 +02:00
parent 6d97d4b603
commit 001ba2d955
17 changed files with 1201 additions and 3 deletions

View file

@ -1,3 +1,7 @@
from camps.mixins import CampViewMixin
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404
from economy.models import Pos
from utils.mixins import RaisePermissionRequiredMixin from utils.mixins import RaisePermissionRequiredMixin
@ -37,3 +41,27 @@ class ContentTeamPermissionMixin(RaisePermissionRequiredMixin):
"camps.backoffice_permission", "camps.backoffice_permission",
"camps.contentteam_permission", "camps.contentteam_permission",
) )
class PosViewMixin(CampViewMixin):
"""A mixin to set self.pos based on pos_slug in url kwargs."""
def setup(self, *args, **kwargs):
super().setup(*args, **kwargs)
self.pos = get_object_or_404(
Pos, team__camp=self.camp, slug=self.kwargs["pos_slug"]
)
def get_permission_required(self):
"""
This view requires two permissions, camps.backoffice_permission and the permission_set for the team in question.
"""
if not self.pos.team.permission_set:
raise PermissionDenied("No permissions set defined for this team")
perms = ["camps.backoffice_permission"]
return perms
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context["pos"] = self.pos
return context

View file

@ -0,0 +1,28 @@
<table class="table table-striped{% if not nodatatable %} datatable{% endif %}">
<thead>
<tr>
<th>Name</th>
<th>Team</th>
<th>Slug</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for pos in pos_list %}
<tr>
<td><a href="{% url 'backoffice:pos_detail' camp_slug=camp.slug pos_slug=pos.slug %}">{{ pos.name }}</a></td>
<td>{{ pos.team }}</td>
<td>{{ pos.slug }}</td>
<td>
<div class="btn-group-vertical">
<a href="{% url 'backoffice:pos_detail' camp_slug=camp.slug pos_slug=pos.slug %}" class="btn btn-primary"><i class="fas fa-search"></i> Details</a>
{% if perms.camps.orgateam_permission %}
<a href="{% url 'backoffice:pos_update' camp_slug=camp.slug pos_slug=pos.slug %}" class="btn btn-primary"><i class="fas fa-edit"></i> Update</a>
<a href="{% url 'backoffice:pos_delete' camp_slug=camp.slug pos_slug=pos.slug %}" class="btn btn-danger"><i class="fas fa-times"></i> Delete</a>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View file

@ -0,0 +1,34 @@
<table class="table table-striped{% if not nodatatable %} datatable{% endif %}">
<thead>
<tr>
<th>Date</th>
<th>Pos</th>
<th>Bank Responsible</th>
<th>Pos Responsible</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for pr in posreport_list %}
<tr>
<td><a href="{% url 'backoffice:posreport_detail' camp_slug=camp.slug pos_slug=pos.slug posreport_uuid=pr.uuid %}">{{ pr.date }}</a></td>
<td>{{ pr.pos.name }}</td>
<td>{{ pr.bank_responsible }}</td>
<td>{{ pr.pos_responsible }}</td>
<td>
<div class="btn-group-vertical">
<a href="{% url 'backoffice:posreport_detail' camp_slug=camp.slug pos_slug=pos.slug posreport_uuid=pr.uuid %}" class="btn btn-primary"><i class="fas fa-search"></i> Details</a>
{% if request.user == pr.pos.bank_responsible %}
<a href="{% url 'backoffice:posreport_bank_count_start' camp_slug=camp.slug pos_slug=pos.slug posreport_uuid=pr.uuid %}" class="btn btn-primary"><i class="fas fa-edit"></i> Bank Count Start</a>
<a href="{% url 'backoffice:posreport_bank_count_end' camp_slug=camp.slug pos_slug=pos.slug posreport_uuid=pr.uuid %}" class="btn btn-primary"><i class="fas fa-edit"></i> Bank Count End</a>
{% endif %}
{% if request.user == pr.pos.pos_responsible %}
<a href="{% url 'backoffice:posreport_pos_count_start' camp_slug=camp.slug pos_slug=pos.slug posreport_uuid=pr.uuid %}" class="btn btn-primary"><i class="fas fa-edit"></i> Pos Count Start</a>
<a href="{% url 'backoffice:posreport_pos_count_end' camp_slug=camp.slug pos_slug=pos.slug posreport_uuid=pr.uuid %}" class="btn btn-primary"><i class="fas fa-edit"></i> Pos Count End</a>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View file

@ -166,6 +166,12 @@
<h4 class="list-group-item-heading">Proxied Content</h4> <h4 class="list-group-item-heading">Proxied Content</h4>
<p class="list-group-item-text">Use this view to see proxied content</p> <p class="list-group-item-text">Use this view to see proxied content</p>
</a> </a>
<h3>Point of Sale</h3>
<a href="{% url 'backoffice:pos_list' camp_slug=camp.slug %}" class="list-group-item">
<h4 class="list-group-item-heading">Point of Sale</h4>
<p class="list-group-item-text">Use this view to see a list of Pos objects, and to see and submit PosReports</p>
</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,18 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Delete Pos {{ pos.name }}?</h2>
</div>
<div class="panel-body">
<p class="lead">This Pos has <b>{{ pos.pos_reports.count }}</b> PosReports which will also be deleted.</p>
<form method="POST">
{% csrf_token %}
<button type="submit" name="Delete" class="btn btn-danger"><i class="fas fa-times"></i> Yes, Delete it</button>
<a href="{% url 'backoffice:pos_detail' camp_slug=camp.slug pos_slug=pos.slug %}" class="btn btn-default"><i class="fas fa-undo"></i> Cancel</a>
</form>
</div>
</div>
{% endblock content %}

View file

@ -0,0 +1,42 @@
{% extends 'base.html' %}
{% block title %}
{{ pos.name }} | Pos | BackOffice | {{ block.super }}
{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ pos.name }} | Pos | BackOffice</h3>
</div>
<div class="panel-body">
<p>
<a href="{% url 'backoffice:pos_update' camp_slug=camp.slug pos_slug=pos.slug %}" class="btn btn-primary"><i class="fas fa-edit"></i> Update Pos</a>
<a href="{% url 'backoffice:pos_delete' camp_slug=camp.slug pos_slug=pos.slug %}" class="btn btn-danger"><i class="fas fa-times"></i> Delete Pos</a>
<a href="{% url 'backoffice:pos_list' camp_slug=camp.slug %}" class="btn btn-default"><i class="fas fa-undo"></i> Pos List</a>
</p>
<table class="table">
<tbody>
<tr>
<th>Pos Name</th>
<td>{{ pos.name }}</td>
</tr>
<tr>
<th>Team</th>
<td>{{ pos.team }}</p>
</tr>
</tbody>
</table>
<h3>Pos Reports</h3>
{% if pos.pos_reports.exists %}
{% include "includes/posreport_list_table.html" with posreport_list=pos.pos_reports.all %}
{% else %}
None found
{% endif %}
<br>
{% if perms.camps.orgateam_permission %}
<a href="{% url 'backoffice:posreport_create' camp_slug=camp.slug pos_slug=pos.slug %}" class="btn btn-success"><i class="fas fa-plus"></i> Create PosReport</a>
{% endif %}
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,21 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% load static %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% if request.resolver_match.url_name == "pos_update" %}Update{% else %}Create new{% endif %} Pos</h3>
</div>
<div class="panel-body">
<form method="POST">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-success"><i class="fas fa-check"></i> Save</button>
<a href="{% url 'backoffice:pos_list' camp_slug=camp.slug %}" class="btn btn-default"><i class="fas fa-undo"></i> Cancel</a>
{% endbuttons %}
</form>
</div>
</div>
{% endblock content %}

View file

@ -0,0 +1,29 @@
{% extends 'base.html' %}
{% load bornhack %}
{% block title %}
Pos List | Backoffice | {{ block.super }}
{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">Pos List - BackOffice</h3></div>
<div class="panel-body">
<p>A <i>Pos</i> is a place where we sell stuff for DKK and/or HAX.</p>
{% if not pos_list %}
<p class="lead">No Pos found.</p>
{% else %}
<p>
<a class="btn btn-default" href="{% url 'backoffice:index' camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Backoffice</a>
{% include "includes/pos_list_table.html" %}
</p>
{% endif %}
<p>
{% if perms.camps.orgateam_permission %}
<a class="btn btn-success" href="{% url 'backoffice:pos_create' camp_slug=camp.slug %}"><i class="fas fa-plus"></i> Create Pos</a>
{% endif %}
<a class="btn btn-default" href="{% url 'backoffice:index' camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Backoffice</a>
</p>
</div>
</div>
{% endblock content %}

View file

@ -0,0 +1,115 @@
{% extends 'base.html' %}
{% block title %}
PosReport {{ posreport.date }} {{ posreport.pos.name }} | Pos | BackOffice | {{ block.super }}
{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">PosReport {{ posreport.date }} {{ posreport.pos.name }} | Pos | BackOffice</h3>
</div>
<div class="panel-body">
<p>
{% if "camps.orgateam_permission" in perms %}
<a href="{% url 'backoffice:posreport_update' camp_slug=camp.slug pos_slug=pos.slug posreport_uuid=posreport.uuid %}" class="btn btn-primary"><i class="fas fa-edit"></i> Update</a>
{% endif %}
{% if request.user == posreport.bank_responsible %}
<a href="{% url 'backoffice:posreport_bank_count_start' camp_slug=camp.slug pos_slug=pos.slug posreport_uuid=posreport.uuid %}" class="btn btn-primary"><i class="fas fa-edit"></i> Bank Count Start</a>
<a href="{% url 'backoffice:posreport_bank_count_end' camp_slug=camp.slug pos_slug=pos.slug posreport_uuid=posreport.uuid %}" class="btn btn-primary"><i class="fas fa-edit"></i> Bank Count End</a>
{% endif %}
{% if request.user == posreport.pos_responsible %}
<a href="{% url 'backoffice:posreport_pos_count_start' camp_slug=camp.slug pos_slug=pos.slug posreport_uuid=posreport.uuid %}" class="btn btn-primary"><i class="fas fa-edit"></i> Pos Count Start</a>
<a href="{% url 'backoffice:posreport_pos_count_end' camp_slug=camp.slug pos_slug=pos.slug posreport_uuid=posreport.uuid %}" class="btn btn-primary"><i class="fas fa-edit"></i> Pos Count End</a>
{% endif %}
</p>
<table class="table">
<tbody>
<tr>
<th>PosReport UUID</th>
<td>{{ posreport.uuid }}</td>
</tr>
<tr>
<th>PosReport Date</th>
<td>{{ posreport.date }}</td>
</tr>
<tr>
<th>Pos Name</th>
<td>{{ posreport.pos.name }}</td>
</tr>
<tr>
<th>Team</th>
<td>{{ posreport.pos.team }}</p>
</tr>
<tr>
<th>Bank Responsible</th>
<td>{{ posreport.bank_responsible }}</p>
</tr>
<tr>
<th>Pos Responsible</th>
<td>{{ posreport.pos_responsible }}</p>
</tr>
<tr>
<th>Counts</th>
<td>
<table class="table table-condensed">
<thead>
<tr>
<th>What</th>
<th>Bank Start</th>
<th>Bank End</th>
<th>Pos Start</th>
<th>Pos End</th>
</tr>
</thead>
<tbody>
<tr>
<td>DKK</td>
<td>{{ posreport.bank_count_dkk_start }}</td>
<td>{{ posreport.bank_count_dkk_end }}</td>
<td>{{ posreport.pos_count_dkk_start }}</td>
<td>{{ posreport.pos_count_dkk_end }}</td>
</tr>
<tr>
<td>5 HAX</td>
<td>{{ posreport.bank_count_hax5_start }}</td>
<td>{{ posreport.bank_count_hax5_end }}</td>
<td>{{ posreport.pos_count_hax5_start }}</td>
<td>{{ posreport.pos_count_hax5_end }}</td>
</tr>
<tr>
<td>10 HAX</td>
<td>{{ posreport.bank_count_hax10_start }}</td>
<td>{{ posreport.bank_count_hax10_end }}</td>
<td>{{ posreport.pos_count_hax10_start }}</td>
<td>{{ posreport.pos_count_hax10_end }}</td>
</tr>
<td>20 HAX</td>
<td>{{ posreport.bank_count_hax20_start }}</td>
<td>{{ posreport.bank_count_hax20_end }}</td>
<td>{{ posreport.pos_count_hax20_start }}</td>
<td>{{ posreport.pos_count_hax20_end }}</td>
</tr>
<tr>
<td>50 HAX</td>
<td>{{ posreport.bank_count_hax50_start }}</td>
<td>{{ posreport.bank_count_hax50_end }}</td>
<td>{{ posreport.pos_count_hax50_start }}</td>
<td>{{ posreport.pos_count_hax50_end }}</td>
</tr>
<tr>
<td>100 HAX</td>
<td>{{ posreport.bank_count_hax100_start }}</td>
<td>{{ posreport.bank_count_hax100_end }}</td>
<td>{{ posreport.pos_count_hax100_start }}</td>
<td>{{ posreport.pos_count_hax100_end }}</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,21 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% load static %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% if request.resolver_match.url_name == "posreport_create" %}Create{% else %}Update{% endif %} PosReport</h3>
</div>
<div class="panel-body">
<form method="POST">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-success"><i class="fas fa-check"></i> Save</button>
<a href="{% url 'backoffice:pos_list' camp_slug=camp.slug %}" class="btn btn-default"><i class="fas fa-undo"></i> Cancel</a>
{% endbuttons %}
</form>
</div>
</div>
{% endblock content %}

View file

@ -0,0 +1,25 @@
{% extends 'base.html' %}
{% load bornhack %}
{% block title %}
PosReport List for {{ pos.name }} | Backoffice | {{ block.super }}
{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">PosReport List for {{ pos.name }} - BackOffice</h3></div>
<div class="panel-body">
<p>A <i>PosReport</i> contains the start and end counts of HAX+DKK from a point-of-sale and the exported JSON file from the Pos.</p>
{% if not pos_list %}
<p class="lead">No Pos found.</p>
{% else %}
<p>
{% include "includes/posreport_list_table.html" %}
</p>
{% endif %}
<p>
<a class="btn btn-default" href="{% url 'backoffice:index' camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Backoffice</a>
</p>
</div>
</div>
{% endblock content %}

View file

@ -60,6 +60,18 @@ from .views import (
MerchandiseToOrderView, MerchandiseToOrderView,
OutgoingEmailMassUpdateView, OutgoingEmailMassUpdateView,
PendingProposalsView, PendingProposalsView,
PosCreateView,
PosDeleteView,
PosDetailView,
PosListView,
PosReportBankCountEndView,
PosReportBankCountStartView,
PosReportCreateView,
PosReportDetailView,
PosReportPosCountEndView,
PosReportPosCountStartView,
PosReportUpdateView,
PosUpdateView,
ProductHandoutView, ProductHandoutView,
ReimbursementCreateUserSelectView, ReimbursementCreateUserSelectView,
ReimbursementCreateView, ReimbursementCreateView,
@ -632,4 +644,77 @@ urlpatterns = [
OutgoingEmailMassUpdateView.as_view(), OutgoingEmailMassUpdateView.as_view(),
name="outgoing_email_release", name="outgoing_email_release",
), ),
# point-of-sale
path(
"pos/",
include(
[
path("", PosListView.as_view(), name="pos_list",),
path("create/", PosCreateView.as_view(), name="pos_create",),
path(
"<slug:pos_slug>/",
include(
[
path("", PosDetailView.as_view(), name="pos_detail",),
path(
"update/", PosUpdateView.as_view(), name="pos_update",
),
path(
"delete/", PosDeleteView.as_view(), name="pos_delete",
),
path(
"reports/",
include(
[
path(
"create/",
PosReportCreateView.as_view(),
name="posreport_create",
),
path(
"<uuid:posreport_uuid>/",
include(
[
path(
"",
PosReportDetailView.as_view(),
name="posreport_detail",
),
path(
"update/",
PosReportUpdateView.as_view(),
name="posreport_update",
),
path(
"bankcount/start/",
PosReportBankCountStartView.as_view(),
name="posreport_bank_count_start",
),
path(
"bankcount/end/",
PosReportBankCountEndView.as_view(),
name="posreport_bank_count_end",
),
path(
"poscount/start/",
PosReportPosCountStartView.as_view(),
name="posreport_pos_count_start",
),
path(
"poscount/end/",
PosReportPosCountEndView.as_view(),
name="posreport_pos_count_end",
),
]
),
),
]
),
),
]
),
),
],
),
),
] ]

View file

@ -20,7 +20,15 @@ from django.utils import timezone
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.views.generic import DetailView, ListView, TemplateView from django.views.generic import DetailView, ListView, TemplateView
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from economy.models import Chain, Credebtor, Expense, Reimbursement, Revenue from economy.models import (
Chain,
Credebtor,
Expense,
Pos,
PosReport,
Reimbursement,
Revenue,
)
from facilities.models import ( from facilities.models import (
Facility, Facility,
FacilityFeedback, FacilityFeedback,
@ -59,6 +67,7 @@ from .mixins import (
EconomyTeamPermissionMixin, EconomyTeamPermissionMixin,
InfoTeamPermissionMixin, InfoTeamPermissionMixin,
OrgaTeamPermissionMixin, OrgaTeamPermissionMixin,
PosViewMixin,
RaisePermissionRequiredMixin, RaisePermissionRequiredMixin,
) )
@ -1955,3 +1964,219 @@ class OutgoingEmailMassUpdateView(CampViewMixin, OrgaTeamPermissionMixin, FormVi
def get_success_url(self, *args, **kwargs): def get_success_url(self, *args, **kwargs):
"""Return to the backoffice index.""" """Return to the backoffice index."""
return reverse("backoffice:index", kwargs={"camp_slug": self.camp.slug}) return reverse("backoffice:index", kwargs={"camp_slug": self.camp.slug})
################################
# Pos and PosReport views
class PosListView(CampViewMixin, RaisePermissionRequiredMixin, ListView):
"""Show a list of Pos this user has access to (through team memberships)."""
permission_required = "camps.backoffice_permission"
model = Pos
template_name = "pos_list.html"
class PosDetailView(PosViewMixin, RaisePermissionRequiredMixin, DetailView):
"""Show details for a Pos."""
model = Pos
template_name = "pos_detail.html"
slug_url_kwarg = "pos_slug"
class PosCreateView(CampViewMixin, OrgaTeamPermissionMixin, CreateView):
"""Create a new Pos (orga only)."""
model = Pos
template_name = "pos_form.html"
fields = ["name", "team"]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["form"].fields["team"].queryset = Team.objects.filter(camp=self.camp)
return context
class PosUpdateView(CampViewMixin, OrgaTeamPermissionMixin, UpdateView):
"""Update a Pos."""
model = Pos
template_name = "pos_form.html"
slug_url_kwarg = "pos_slug"
fields = ["name", "team"]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["form"].fields["team"].queryset = Team.objects.filter(camp=self.camp)
return context
class PosDeleteView(CampViewMixin, OrgaTeamPermissionMixin, DeleteView):
model = Pos
template_name = "pos_delete.html"
slug_url_kwarg = "pos_slug"
def delete(self, *args, **kwargs):
self.get_object().pos_reports.all().delete()
return super().delete(*args, **kwargs)
def get_success_url(self):
messages.success(
self.request, "The Pos and all related PosReports has been deleted"
)
return reverse("backoffice:pos_list", kwargs={"camp_slug": self.camp.slug})
class PosReportCreateView(PosViewMixin, RaisePermissionRequiredMixin, CreateView):
"""Use this view to create new PosReports."""
model = PosReport
fields = ["date", "bank_responsible", "pos_responsible"]
template_name = "posreport_form.html"
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",
).approved_members.all()
context["form"].fields[
"pos_responsible"
].queryset = self.pos.team.responsible_members.all()
return context
def form_valid(self, form):
"""
Set Pos before saving
"""
pr = form.save(commit=False)
pr.pos = self.pos
pr.save()
messages.success(self.request, f"New PosReport created successfully!")
return redirect(
reverse(
"backoffice:posreport_detail",
kwargs={
"camp_slug": self.camp.slug,
"pos_slug": self.pos.slug,
"posreport_uuid": pr.uuid,
},
)
)
class PosReportUpdateView(PosViewMixin, RaisePermissionRequiredMixin, UpdateView):
"""Use this view to update PosReports."""
model = PosReport
fields = ["date", "bank_responsible", "pos_responsible"]
template_name = "posreport_form.html"
pk_url_kwarg = "posreport_uuid"
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",
).approved_members.all()
context["form"].fields[
"pos_responsible"
].queryset = self.pos.team.responsible_members.all()
return context
class PosReportDetailView(PosViewMixin, RaisePermissionRequiredMixin, DetailView):
"""Show details for a PosReport."""
model = PosReport
template_name = "posreport_detail.html"
pk_url_kwarg = "posreport_uuid"
class PosReportBankCountStartView(
PosViewMixin, RaisePermissionRequiredMixin, UpdateView
):
"""The bank responsible for a PosReport uses this view to add day-start HAX and DKK counts to a PosReport."""
model = PosReport
template_name = "posreport_form.html"
fields = [
"bank_count_dkk_start",
"bank_count_hax5_start",
"bank_count_hax10_start",
"bank_count_hax20_start",
"bank_count_hax50_start",
"bank_count_hax100_start",
]
pk_url_kwarg = "posreport_uuid"
def setup(self, *args, **kwargs):
super().setup(*args, **kwargs)
if self.request.user != self.get_object().bank_responsible:
raise PermissionDenied("Only the bank responsible can do this")
class PosReportBankCountEndView(PosViewMixin, RaisePermissionRequiredMixin, UpdateView):
"""The bank responsible for a PosReport uses this view to add day-end HAX and DKK counts to a PosReport."""
model = PosReport
template_name = "posreport_form.html"
fields = [
"bank_count_dkk_end",
"bank_count_hax5_end",
"bank_count_hax10_end",
"bank_count_hax20_end",
"bank_count_hax50_end",
"bank_count_hax100_end",
]
pk_url_kwarg = "posreport_uuid"
def setup(self, *args, **kwargs):
super().setup(*args, **kwargs)
if self.request.user != self.get_object().bank_responsible:
raise PermissionDenied("Only the bank responsible can do this")
class PosReportPosCountStartView(
PosViewMixin, RaisePermissionRequiredMixin, UpdateView
):
"""The Pos responsible for a PosReport uses this view to add day-start HAX and DKK counts to a PosReport."""
model = PosReport
template_name = "posreport_form.html"
fields = [
"pos_count_dkk_start",
"pos_count_hax5_start",
"pos_count_hax10_start",
"pos_count_hax20_start",
"pos_count_hax50_start",
"pos_count_hax100_start",
]
pk_url_kwarg = "posreport_uuid"
def setup(self, *args, **kwargs):
super().setup(*args, **kwargs)
if self.request.user != self.get_object().pos_responsible:
raise PermissionDenied("Only the Pos responsible can do this")
class PosReportPosCountEndView(PosViewMixin, RaisePermissionRequiredMixin, UpdateView):
"""The Pos responsible for a PosReport uses this view to add day-end HAX and DKK counts to a PosReport."""
model = PosReport
template_name = "posreport_form.html"
fields = [
"pos_count_dkk_end",
"pos_count_hax5_end",
"pos_count_hax10_end",
"pos_count_hax20_end",
"pos_count_hax50_end",
"pos_count_hax100_end",
"pos_json",
]
pk_url_kwarg = "posreport_uuid"
def setup(self, *args, **kwargs):
super().setup(*args, **kwargs)
if self.request.user != self.get_object().pos_responsible:
raise PermissionDenied("Only the pos responsible can do this")

View file

@ -1,6 +1,6 @@
from django.contrib import admin from django.contrib import admin
from .models import Chain, Credebtor, Expense, Reimbursement, Revenue from .models import Chain, Credebtor, Expense, Pos, PosReport, Reimbursement, Revenue
############################### ###############################
# chains and credebtors # chains and credebtors
@ -113,3 +113,18 @@ class ReimbursementAdmin(admin.ModelAdmin):
list_filter = ["camp", "user", "reimbursement_user", "paid"] list_filter = ["camp", "user", "reimbursement_user", "paid"]
list_display = ["camp", "user", "reimbursement_user", "paid", "notes", "get_amount"] list_display = ["camp", "user", "reimbursement_user", "paid", "notes", "get_amount"]
search_fields = ["user__username", "reimbursement_user__username", "notes"] search_fields = ["user__username", "reimbursement_user__username", "notes"]
################################
# pos
@admin.register(Pos)
class PosAdmin(admin.ModelAdmin):
list_display = ["name", "team"]
list_filter = ["team"]
@admin.register(PosReport)
class PosReportAdmin(admin.ModelAdmin):
list_display = ["uuid", "pos"]

View file

@ -0,0 +1,284 @@
# Generated by Django 3.1 on 2020-08-10 23:41
import uuid
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("teams", "0052_team_permission_set"),
("economy", "0010_auto_20190330_1045"),
]
operations = [
migrations.CreateModel(
name="Pos",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
(
"name",
models.CharField(
help_text="The point-of-sale name", max_length=255
),
),
(
"slug",
models.SlugField(
blank=True,
help_text="Url slug for this POS. Leave blank to generate based on POS name.",
max_length=255,
),
),
(
"team",
models.ForeignKey(
help_text="The Team managning this POS",
on_delete=django.db.models.deletion.PROTECT,
to="teams.team",
),
),
],
options={"ordering": ["name"],},
),
migrations.CreateModel(
name="PosReport",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
(
"date",
models.DateField(
help_text="The date this report covers (pick the starting date if opening hours cross midnight)."
),
),
(
"pos_json",
models.JSONField(
blank=True,
help_text="The JSON exported from the external POS system",
null=True,
),
),
(
"bank_count_dkk_start",
models.PositiveIntegerField(
default=0,
help_text="The number of DKK handed out from the bank to the POS at the start of the business day (counted by the bank responsible)",
),
),
(
"bank_count_hax5_start",
models.PositiveIntegerField(
default=0,
help_text="The number of 5 HAX coins handed out from the bank to the POS at the start of the business day (counted by the bank responsible)",
),
),
(
"bank_count_hax10_start",
models.PositiveIntegerField(
default=0,
help_text="The number of 10 HAX coins handed out from the bank to the POS at the start of the business day (counted by the bank responsible)",
),
),
(
"bank_count_hax20_start",
models.PositiveIntegerField(
default=0,
help_text="The number of 20 HAX coins handed out from the bank to the POS at the start of the business day (counted by the bank responsible)",
),
),
(
"bank_count_hax50_start",
models.PositiveIntegerField(
default=0,
help_text="The number of 50 HAX coins handed out from the bank to the POS at the start of the business day (counted by the bank responsible)",
),
),
(
"bank_count_hax100_start",
models.PositiveIntegerField(
default=0,
help_text="The number of 100 HAX coins handed out from the bank to the POS at the start of the business day (counted by the bank responsible)",
),
),
(
"pos_count_dkk_start",
models.PositiveIntegerField(
default=0,
help_text="The number of DKK handed out from the bank to the POS at the start of the business day (counted by the POS responsible)",
),
),
(
"pos_count_hax5_start",
models.PositiveIntegerField(
default=0,
help_text="The number of 5 HAX coins received by the POS from the bank at the start of the business day (counted by the POS responsible)",
),
),
(
"pos_count_hax10_start",
models.PositiveIntegerField(
default=0,
help_text="The number of 10 HAX coins received by the POS from the bank at the start of the business day (counted by the POS responsible)",
),
),
(
"pos_count_hax20_start",
models.PositiveIntegerField(
default=0,
help_text="The number of 20 HAX coins received by the POS from the bank at the start of the business day (counted by the POS responsible)",
),
),
(
"pos_count_hax50_start",
models.PositiveIntegerField(
default=0,
help_text="The number of 50 HAX coins received by the POS from the bank at the start of the business day (counted by the POS responsible)",
),
),
(
"pos_count_hax100_start",
models.PositiveIntegerField(
default=0,
help_text="The number of 100 HAX coins received by the POS from the bank at the start of the business day (counted by the POS responsible)",
),
),
(
"bank_count_dkk_end",
models.PositiveIntegerField(
default=0,
help_text="The number of DKK handed back from the POS to the bank at the end of the business day (counted by the bank responsible)",
),
),
(
"bank_count_hax5_end",
models.PositiveIntegerField(
default=0,
help_text="The number of 5 HAX coins handed back from the POS to the bank at the end of the business day (counted by the bank responsible)",
),
),
(
"bank_count_hax10_end",
models.PositiveIntegerField(
default=0,
help_text="The number of 10 HAX coins handed back from the POS to the bank at the end of the business day (counted by the bank responsible)",
),
),
(
"bank_count_hax20_end",
models.PositiveIntegerField(
default=0,
help_text="The number of 20 HAX coins handed back from the POS to the bank at the end of the business day (counted by the bank responsible)",
),
),
(
"bank_count_hax50_end",
models.PositiveIntegerField(
default=0,
help_text="The number of 50 HAX coins handed back from the POS to the bank at the end of the business day (counted by the bank responsible)",
),
),
(
"bank_count_hax100_end",
models.PositiveIntegerField(
default=0,
help_text="The number of 100 HAX coins handed back from the POS to the bank at the end of the business day (counted by the bank responsible)",
),
),
(
"pos_count_dkk_end",
models.PositiveIntegerField(
default=0,
help_text="The number of DKK handed back from the POS to the bank at the end of the business day (counted by the POS responsible)",
),
),
(
"pos_count_hax5_end",
models.PositiveIntegerField(
default=0,
help_text="The number of 5 HAX coins received by the bank from the POS at the end of the business day (counted by the POS responsible)",
),
),
(
"pos_count_hax10_end",
models.PositiveIntegerField(
default=0,
help_text="The number of 10 HAX coins received by the bank from the POS at the end of the business day (counted by the POS responsible)",
),
),
(
"pos_count_hax20_end",
models.PositiveIntegerField(
default=0,
help_text="The number of 20 HAX coins received by the bank from the POS at the end of the business day (counted by the POS responsible)",
),
),
(
"pos_count_hax50_end",
models.PositiveIntegerField(
default=0,
help_text="The number of 50 HAX coins received by the bank from the POS at the end of the business day (counted by the POS responsible)",
),
),
(
"pos_count_hax100_end",
models.PositiveIntegerField(
default=0,
help_text="The number of 100 HAX coins received by the bank from the POS at the end of the business day (counted by the POS responsible)",
),
),
(
"bank_responsible",
models.ForeignKey(
help_text="The banker responsible for this PosReport",
on_delete=django.db.models.deletion.PROTECT,
related_name="pos_reports_banker",
to=settings.AUTH_USER_MODEL,
),
),
(
"pos",
models.ForeignKey(
help_text="The Pos this PosReport belongs to.",
on_delete=django.db.models.deletion.PROTECT,
related_name="pos_reports",
to="economy.pos",
),
),
(
"pos_responsible",
models.ForeignKey(
help_text="The POS person responsible for this PosReport",
on_delete=django.db.models.deletion.PROTECT,
related_name="pos_reports_poser",
to=settings.AUTH_USER_MODEL,
),
),
],
options={"abstract": False,},
),
]

View file

@ -3,6 +3,7 @@ import os
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.urls import reverse
from utils.models import CampRelatedModel, CreatedUpdatedModel, UUIDModel from utils.models import CampRelatedModel, CreatedUpdatedModel, UUIDModel
from utils.slugs import unique_slugify from utils.slugs import unique_slugify
@ -465,3 +466,224 @@ class Reimbursement(CampRelatedModel, UUIDModel):
for expense in self.expenses.filter(paid_by_bornhack=False): for expense in self.expenses.filter(paid_by_bornhack=False):
amount += expense.amount amount += expense.amount
return amount return amount
class Pos(CampRelatedModel, UUIDModel):
"""A Pos is a point-of-sale like the bar or infodesk."""
class Meta:
ordering = ["name"]
name = models.CharField(max_length=255, help_text="The point-of-sale name")
slug = models.SlugField(
max_length=255,
blank=True,
help_text="Url slug for this POS. Leave blank to generate based on POS name.",
)
team = models.ForeignKey(
"teams.Team", on_delete=models.PROTECT, help_text="The Team managning this POS",
)
def save(self, **kwargs):
"""Generate slug if needed."""
if not self.slug:
self.slug = unique_slugify(
self.name,
slugs_in_use=self.__class__.objects.filter(
team__camp=self.team.camp
).values_list("slug", flat=True),
)
super().save(**kwargs)
@property
def camp(self):
return self.team.camp
camp_filter = "team__camp"
def get_absolute_url(self):
return reverse(
"backoffice:pos_detail",
kwargs={"camp_slug": self.team.camp.slug, "pos_slug": self.slug},
)
class PosReport(CampRelatedModel, UUIDModel):
"""A PosReport contains the HAX/DKK counts and the csv report from the POS system."""
pos = models.ForeignKey(
"economy.Pos",
on_delete=models.PROTECT,
related_name="pos_reports",
help_text="The Pos this PosReport belongs to.",
)
bank_responsible = models.ForeignKey(
"auth.User",
on_delete=models.PROTECT,
related_name="pos_reports_banker",
help_text="The banker responsible for this PosReport",
)
pos_responsible = models.ForeignKey(
"auth.User",
on_delete=models.PROTECT,
related_name="pos_reports_poser",
help_text="The POS person responsible for this PosReport",
)
date = models.DateField(
help_text="The date this report covers (pick the starting date if opening hours cross midnight).",
)
pos_json = models.JSONField(
null=True,
blank=True,
help_text="The JSON exported from the external POS system",
)
# bank count start of day
bank_count_dkk_start = models.PositiveIntegerField(
default=0,
help_text="The number of DKK handed out from the bank to the POS at the start of the business day (counted by the bank responsible)",
)
bank_count_hax5_start = models.PositiveIntegerField(
default=0,
help_text="The number of 5 HAX coins handed out from the bank to the POS at the start of the business day (counted by the bank responsible)",
)
bank_count_hax10_start = models.PositiveIntegerField(
default=0,
help_text="The number of 10 HAX coins handed out from the bank to the POS at the start of the business day (counted by the bank responsible)",
)
bank_count_hax20_start = models.PositiveIntegerField(
default=0,
help_text="The number of 20 HAX coins handed out from the bank to the POS at the start of the business day (counted by the bank responsible)",
)
bank_count_hax50_start = models.PositiveIntegerField(
default=0,
help_text="The number of 50 HAX coins handed out from the bank to the POS at the start of the business day (counted by the bank responsible)",
)
bank_count_hax100_start = models.PositiveIntegerField(
default=0,
help_text="The number of 100 HAX coins handed out from the bank to the POS at the start of the business day (counted by the bank responsible)",
)
# POS count start of day
pos_count_dkk_start = models.PositiveIntegerField(
default=0,
help_text="The number of DKK handed out from the bank to the POS at the start of the business day (counted by the POS responsible)",
)
pos_count_hax5_start = models.PositiveIntegerField(
default=0,
help_text="The number of 5 HAX coins received by the POS from the bank at the start of the business day (counted by the POS responsible)",
)
pos_count_hax10_start = models.PositiveIntegerField(
default=0,
help_text="The number of 10 HAX coins received by the POS from the bank at the start of the business day (counted by the POS responsible)",
)
pos_count_hax20_start = models.PositiveIntegerField(
default=0,
help_text="The number of 20 HAX coins received by the POS from the bank at the start of the business day (counted by the POS responsible)",
)
pos_count_hax50_start = models.PositiveIntegerField(
default=0,
help_text="The number of 50 HAX coins received by the POS from the bank at the start of the business day (counted by the POS responsible)",
)
pos_count_hax100_start = models.PositiveIntegerField(
default=0,
help_text="The number of 100 HAX coins received by the POS from the bank at the start of the business day (counted by the POS responsible)",
)
# bank count end of day
bank_count_dkk_end = models.PositiveIntegerField(
default=0,
help_text="The number of DKK handed back from the POS to the bank at the end of the business day (counted by the bank responsible)",
)
bank_count_hax5_end = models.PositiveIntegerField(
default=0,
help_text="The number of 5 HAX coins handed back from the POS to the bank at the end of the business day (counted by the bank responsible)",
)
bank_count_hax10_end = models.PositiveIntegerField(
default=0,
help_text="The number of 10 HAX coins handed back from the POS to the bank at the end of the business day (counted by the bank responsible)",
)
bank_count_hax20_end = models.PositiveIntegerField(
default=0,
help_text="The number of 20 HAX coins handed back from the POS to the bank at the end of the business day (counted by the bank responsible)",
)
bank_count_hax50_end = models.PositiveIntegerField(
default=0,
help_text="The number of 50 HAX coins handed back from the POS to the bank at the end of the business day (counted by the bank responsible)",
)
bank_count_hax100_end = models.PositiveIntegerField(
default=0,
help_text="The number of 100 HAX coins handed back from the POS to the bank at the end of the business day (counted by the bank responsible)",
)
# pos count end of day
pos_count_dkk_end = models.PositiveIntegerField(
default=0,
help_text="The number of DKK handed back from the POS to the bank at the end of the business day (counted by the POS responsible)",
)
pos_count_hax5_end = models.PositiveIntegerField(
default=0,
help_text="The number of 5 HAX coins received by the bank from the POS at the end of the business day (counted by the POS responsible)",
)
pos_count_hax10_end = models.PositiveIntegerField(
default=0,
help_text="The number of 10 HAX coins received by the bank from the POS at the end of the business day (counted by the POS responsible)",
)
pos_count_hax20_end = models.PositiveIntegerField(
default=0,
help_text="The number of 20 HAX coins received by the bank from the POS at the end of the business day (counted by the POS responsible)",
)
pos_count_hax50_end = models.PositiveIntegerField(
default=0,
help_text="The number of 50 HAX coins received by the bank from the POS at the end of the business day (counted by the POS responsible)",
)
pos_count_hax100_end = models.PositiveIntegerField(
default=0,
help_text="The number of 100 HAX coins received by the bank from the POS at the end of the business day (counted by the POS responsible)",
)
@property
def camp(self):
return self.pos.team.camp
camp_filter = "pos__team__camp"
def get_absolute_url(self):
return reverse(
"backoffice:posreport_detail",
kwargs={
"camp_slug": self.camp.slug,
"pos_slug": self.pos.slug,
"posreport_uuid": self.uuid,
},
)

View file

@ -1,4 +1,4 @@
Django==3.0.8 Django==3.1
channels==2.4.0 channels==2.4.0
channels-redis==3.0.1 channels-redis==3.0.1