feat(events): Add billing info and organizer name

This commit is contained in:
2025-12-12 12:55:40 +01:00
parent 78066b77ae
commit 4b0057859e
6 changed files with 102 additions and 20 deletions

View File

@@ -0,0 +1,49 @@
"""add billing info and organizer name to event
Revision ID: 914ebe23f071
Revises: 13084c5c1f68
Create Date: 2025-12-12 12:26:13.314293
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "914ebe23f071"
down_revision: Union[str, Sequence[str], None] = "13084c5c1f68"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"event",
sa.Column(
"billed", sa.Boolean(), nullable=False, server_default=sa.text("false")
),
)
op.add_column(
"event",
sa.Column(
"exclude_from_billing",
sa.Boolean(),
nullable=False,
server_default=sa.text("false"),
),
)
op.add_column("event", sa.Column("organizer_name", sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("event", "organizer_name")
op.drop_column("event", "exclude_from_billing")
op.drop_column("event", "billed")
# ### end Alembic commands ###

View File

@@ -241,6 +241,8 @@ async def edit_event(
event.description = form_data.get("eventDescription") event.description = form_data.get("eventDescription")
event.recipe_link = form_data.get("recipeLink") event.recipe_link = form_data.get("recipeLink")
event.ignore_subscriptions = form_data.get("ignoreSubscriptions") == "on" event.ignore_subscriptions = form_data.get("ignoreSubscriptions") == "on"
event.organizer_name = form_data.get("organizerName")
event.exclude_from_billing = form_data.get("excludeFromBilling") == "on"
session.commit() session.commit()
@@ -260,6 +262,8 @@ async def add_event(request: Request, session: SessionDep, user: StrictUserDep):
description=form_data.get("eventDescription"), description=form_data.get("eventDescription"),
recipe_link=form_data.get("recipeLink"), recipe_link=form_data.get("recipeLink"),
ignore_subscriptions=form_data.get("ignoreSubscriptions") == "on", ignore_subscriptions=form_data.get("ignoreSubscriptions") == "on",
organizer_name=form_data.get("organizerName"),
exclude_from_billing=form_data.get("excludeFromBilling") == "on",
) )
session.add(event) session.add(event)
session.commit() session.commit()
@@ -448,8 +452,11 @@ def sync_with_grist_route(event_id: int, session: SessionDep, user: StrictUserDe
event = session.scalars(statement).one() event = session.scalars(statement).one()
entries_written = sync_with_grist(event) entries_written = sync_with_grist(event)
message = "Es wurden keine Einträge geschrieben." message = "Es wurden keine Einträge geschrieben."
if entries_written > 0: if entries_written > 0:
event.billed = True
session.commit()
message = f"Erfolgreich {entries_written} Einträge geschrieben." message = f"Erfolgreich {entries_written} Einträge geschrieben."
return RedirectResponse( return RedirectResponse(
url=f"/event/{event_id}?message={quote_plus(message)}", url=f"/event/{event_id}?message={quote_plus(message)}",

View File

@@ -36,6 +36,11 @@ class Event(Base):
subscriptions_applied: Mapped[bool] = mapped_column(default=False, nullable=False) subscriptions_applied: Mapped[bool] = mapped_column(default=False, nullable=False)
ignore_subscriptions: Mapped[bool] = mapped_column(default=False, nullable=False) ignore_subscriptions: Mapped[bool] = mapped_column(default=False, nullable=False)
billed: Mapped[bool] = mapped_column(default=False, nullable=False)
exclude_from_billing: Mapped[bool] = mapped_column(default=False, nullable=False)
organizer_name: Mapped[str] = mapped_column(nullable=True)
registrations: Mapped[list["Registration"]] = relationship( registrations: Mapped[list["Registration"]] = relationship(
"Registration", cascade="all, delete" "Registration", cascade="all, delete"
) )

View File

@@ -21,6 +21,12 @@
<small class="form-text text-muted">Leer lassen für Sonntag Abend vor dem Event</small> <small class="form-text text-muted">Leer lassen für Sonntag Abend vor dem Event</small>
</div> </div>
<div class="mb-3">
<label for="organizerName" class="form-label">Organisator*in</label>
<input type="text" class="form-control" id="organizerName" name="organizerName" {% if edit_mode %}value="{{ event.organizer_name }}"{% endif %}>
<small class="form-text text-muted">Name der Person, die das Event organisiert.</small>
</div>
<div class="mb-3"> <div class="mb-3">
<label for="recipeLink" class="form-label">Rezept-Link</label> <label for="recipeLink" class="form-label">Rezept-Link</label>
<input type="text" class="form-control" id="recipeLink" name="recipeLink" {% if edit_mode %}value="{{ event.recipe_link }}"{% endif %}> <input type="text" class="form-control" id="recipeLink" name="recipeLink" {% if edit_mode %}value="{{ event.recipe_link }}"{% endif %}>
@@ -34,11 +40,18 @@
<div class="mb-3 form-check"> <div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="ignoreSubscriptions" name="ignoreSubscriptions" {% if (edit_mode and event.ignore_subscriptions) or not edit_mode %}checked{% endif %}> <input type="checkbox" class="form-check-input" id="ignoreSubscriptions" name="ignoreSubscriptions" {% if (edit_mode and event.ignore_subscriptions) or not edit_mode %}checked{% endif %}>
<label class="form-check-label" for="ignoreSubscriptions">Dauerhafte Anmeldung ignorieren</label> <label class="form-check-label" for="ignoreSubscriptions">Dauerhafte Anmeldung ignorieren</label>
<small class="form-text text-muted"> <small class="form-text text-muted">
Aktivieren, um dauerhafte Anmeldungen für dieses Event zu ignorieren. Das sollte für alle Events getan werden, die keine offiziellen AG Kochen Kochabende sind. Aktivieren, um dauerhafte Anmeldungen für dieses Event zu ignorieren. Das sollte für alle Events getan werden, die keine offiziellen AG Kochen Kochabende sind.
</small> </small>
</div> </div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="excludeFromBilling" name="excludeFromBilling" {% if edit_mode and event.exclude_from_billing %}checked{% endif %}>
<label class="form-check-label" for="excludeFromBilling">Keine Abrechnung</label>
<small class="form-text text-muted">
Aktivieren, um dieses Event von der Abrechnung auszuschließen.
</small>
</div>
<button type="submit" class="btn btn-primary">Event {% if edit_mode %}bearbeiten{% else %}erstellen{% endif %}</button> <button type="submit" class="btn btn-primary">Event {% if edit_mode %}bearbeiten{% else %}erstellen{% endif %}</button>
</form> </form>

View File

@@ -18,6 +18,7 @@
{% block content %} {% block content %}
<p class="h1">{{ event.title }}</p> <p class="h1">{{ event.title }}</p>
<p class="text-muted">{{ event.event_time.strftime('%A, %d.%m.%Y') }}</p> <p class="text-muted">{{ event.event_time.strftime('%A, %d.%m.%Y') }}</p>
{% if event.organizer_name %}<p>Organisiert von {{ event.organizer_name }}</p>{% endif %}
<p>{{ event.description }}</p> <p>{{ event.description }}</p>
<hr class="hr"/> <hr class="hr"/>
{% if message %} {% if message %}
@@ -59,7 +60,7 @@
</div> </div>
<div class="col-6 p-1"> <div class="col-6 p-1">
{% if user -%} {% if user -%}
<a href="/event/{{event.id}}/edit" class="btn btn-secondary w-100" target="_blank"> <a href="/event/{{event.id}}/edit" class="btn btn-secondary w-100">
<i class="bi bi-pen m-2"></i> Event bearbeiten <i class="bi bi-pen m-2"></i> Event bearbeiten
</a> </a>
{% else -%} {% else -%}
@@ -77,7 +78,7 @@
</button> </button>
</div> </div>
<div class="col-6 p-1"> <div class="col-6 p-1">
<a href="/event/{{event.id}}/sync_with_grist" class="btn btn-secondary w-100"> <a href="/event/{{event.id}}/sync_with_grist" class="btn btn-secondary w-100 {% if event.exclude_from_billing %}disabled{% endif %}">
<i class="bi bi-cash-coin m-2"></i> Abrechnen <i class="bi bi-cash-coin m-2"></i> Abrechnen
</a> </a>
</div> </div>

View File

@@ -33,18 +33,25 @@
{% for event in events %} {% for event in events %}
<div class="col"> <div class="col">
<div class="card h-100 shadow-sm"> <div class="card h-100 shadow-sm">
<div class="card-body"> <div class="card-body">
<h5 class="card-title mb-1">{{ event.title }}</h5> <div class="d-flex justify-content-between align-items-start mb-2">
<p class="text-muted mb-3"><i class="bi bi-calendar"></i> {{ event.event_time.strftime('%A, %d.%m.%Y') <div>
}} <h5 class="card-title mb-0">
</p> {{ event.title }}
<p class="card-text">{{ event.description }}</p> {% if event.billed %}<i class="bi bi-cash-coin" title="Abgerechnet"></i>{% endif %}
<a href="event/{{ event.id }}" class="btn btn-sm {% if event.registration_deadline > now %}btn-primary{% else %}btn-secondary{% endif %}">{% if event.registration_deadline > now %}Zur Anmeldung{% else %}Details ansehen{% endif %}</a> {% if event.exclude_from_billing %}<i class="bi bi-ban" title="Keine Abrechung"></i>{% endif %}
</h5>
</div> </div>
</div> {% if event.organizer_name %}<p class="text-muted small mb-0"><i class="bi bi-person"></i> {{ event.organizer_name }}</p>{% endif %}
</div>
<p class="text-muted mb-3"><i class="bi bi-calendar"></i> {{ event.event_time.strftime('%A, %d.%m.%Y') }}</p>
<p class="card-text">{{ event.description }}</p>
<a href="event/{{ event.id }}" class="btn btn-sm {% if event.registration_deadline > now %}btn-primary{% else %}btn-secondary{% endif %}">{% if event.registration_deadline > now %}Zur Anmeldung{% else %}Details ansehen{% endif %}</a>
</div>
</div> </div>
</div>
{% endfor %} {% endfor %}
</div> </div>