229 lines
7.5 KiB
Python
229 lines
7.5 KiB
Python
from datetime import timedelta
|
|
|
|
from django.template.defaultfilters import floatformat
|
|
from django.utils.html import format_html
|
|
from django.utils.safestring import SafeString
|
|
|
|
import django_tables2 as tables
|
|
|
|
from .models import EventAttendee, EventExt, Member
|
|
|
|
|
|
class DurationColumn(tables.Column):
|
|
def render(self, value: timedelta):
|
|
if value is None:
|
|
return None
|
|
return floatformat(value.total_seconds() / 60 / 60, -2)
|
|
|
|
def value(self, value: timedelta):
|
|
if value is None:
|
|
return None
|
|
return value.total_seconds() / 60 / 60
|
|
|
|
|
|
class MoneyColumn(tables.columns.Column):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.attrs["cell"] = {"class": "text-end", **self.attrs.get("cell", {})}
|
|
|
|
def render(self, value) -> str:
|
|
return f"{super().render(value):.2f}"
|
|
|
|
def value(self, **kwargs):
|
|
return kwargs["value"]
|
|
|
|
|
|
class CurrencySymbolMoneyColumn(MoneyColumn):
|
|
def render(self, value) -> str:
|
|
return f"${super().render(value)}"
|
|
|
|
|
|
class EventTable(tables.Table):
|
|
title = tables.TemplateColumn(
|
|
template_code=(
|
|
'<a title="MembershipWorks" href="https://membershipworks.com/admin/#!event/admin/{{ record.url }}">{{ value }}</a> '
|
|
'<a title="Admin" href="{% url "admin:membershipworks_eventext_change" record.pk %}"><i class="bi bi-pencil-square"></i></a> '
|
|
'<a title="Details" href="{% url "membershipworks:event-detail" record.pk %}"><i class="bi bi-receipt"></i></a> '
|
|
'<a title="Registrations" href="{% url "membershipworks:event-registrations" record.pk %}"><i class="bi bi-people"></i></a> '
|
|
),
|
|
accessor="unescaped_title",
|
|
)
|
|
occurred = tables.BooleanColumn(visible=False)
|
|
start = tables.DateColumn("N d, Y")
|
|
duration = DurationColumn()
|
|
person_hours = DurationColumn()
|
|
meetings = tables.Column()
|
|
gross_revenue = MoneyColumn()
|
|
total_due_to_instructor = MoneyColumn()
|
|
net_revenue = MoneyColumn()
|
|
invoice__date_submitted = tables.DateColumn(verbose_name="Invoice Submitted")
|
|
invoice__date_paid = tables.DateColumn(verbose_name="Invoice Paid")
|
|
|
|
class Meta:
|
|
model = EventExt
|
|
fields = (
|
|
"title",
|
|
"occurred",
|
|
"start",
|
|
"instructor",
|
|
"category",
|
|
"count",
|
|
"cap",
|
|
"meetings",
|
|
"duration",
|
|
"person_hours",
|
|
"gross_revenue",
|
|
"total_due_to_instructor",
|
|
"net_revenue",
|
|
)
|
|
row_attrs = {
|
|
"class": lambda record: (
|
|
"" if record.occurred else "text-decoration-line-through table-danger"
|
|
)
|
|
}
|
|
|
|
|
|
class EventRegistrationsTable(tables.Table):
|
|
total_ticket_count = tables.Column(empty_values=())
|
|
non_member_ticket_count = tables.Column("Non-Member ticket count", empty_values=())
|
|
name = tables.Column(accessor="Full name")
|
|
email = tables.EmailColumn(accessor="Email")
|
|
phone = tables.Column(accessor="Phone")
|
|
emergency_contact_name = tables.Column(accessor="Emergency Contact Name:")
|
|
emergency_contact_phone_number = tables.Column(
|
|
accessor="Emergency Contact Phone Number:"
|
|
)
|
|
emergency_contact_relation = tables.Column(accessor="Emergency Contact Relation:")
|
|
|
|
def render_total_ticket_count(self, record):
|
|
return sum(int(v) for k, v in record.items() if k.startswith("Ticket: "))
|
|
|
|
def render_non_member_ticket_count(self, record):
|
|
# TODO: this is somewhat brittle
|
|
return sum(int(v) for k, v in record.items() if k == "Ticket: CMS Non-Members")
|
|
|
|
class Meta:
|
|
row_attrs = {
|
|
"class": lambda table, record: (
|
|
""
|
|
if table.render_total_ticket_count(record) > 0
|
|
else "text-decoration-line-through table-danger"
|
|
)
|
|
}
|
|
|
|
|
|
class EventSummaryTable(tables.Table):
|
|
event_count = tables.Column("Events")
|
|
canceled_event_count = tables.Column("Canceled Events")
|
|
count__sum = tables.Column("Tickets")
|
|
instructor__count = tables.Column("Unique Instructors")
|
|
meetings__sum = tables.Column("Meetings")
|
|
duration__sum = DurationColumn("Class Hours")
|
|
person_hours__sum = DurationColumn("Person Hours")
|
|
gross_revenue__sum = MoneyColumn("Gross Revenue")
|
|
total_due_to_instructor__sum = MoneyColumn("Total Due to Instructor")
|
|
net_revenue__sum = MoneyColumn("Net Revenue")
|
|
|
|
|
|
class UserEventTable(EventTable):
|
|
title = tables.Column(linkify=True, accessor="unescaped_title")
|
|
instructor = None
|
|
person_hours = None
|
|
gross_revenue = None
|
|
net_revenue = None
|
|
|
|
|
|
class CurrentAndUpcomingEventTable(EventTable):
|
|
next_meeting = tables.DateTimeColumn("N d, Y H:i", accessor="next_meeting_start")
|
|
person_hours = None
|
|
total_due_to_instructor = None
|
|
invoice__date_submitted = None
|
|
invoice__date_paid = None
|
|
gross_revenue = None
|
|
net_revenue = None
|
|
|
|
class Meta(EventTable.Meta):
|
|
row_attrs = {
|
|
"class": lambda table, record: (
|
|
""
|
|
if record.cap is None or record.cap > 0
|
|
else "text-decoration-line-through table-danger"
|
|
)
|
|
}
|
|
sequence = ("title", "start", "next_meeting")
|
|
|
|
|
|
class MoneyFooterColumn(CurrencySymbolMoneyColumn):
|
|
def render_footer(self, bound_column, table):
|
|
value = getattr(table.event, bound_column.accessor)
|
|
if value is not None:
|
|
return f"${value:.2f}"
|
|
else:
|
|
return bound_column.default
|
|
|
|
|
|
class InvoiceTable(tables.Table):
|
|
def __init__(self, *args, **kwargs):
|
|
self.event = kwargs.pop("event")
|
|
super().__init__(*args, **kwargs)
|
|
|
|
@staticmethod
|
|
def _math_header(name: str, formula: str) -> SafeString:
|
|
return format_html(
|
|
'{} <div class="text-nowrap font-monospace fw-light">[{}]</div>',
|
|
name,
|
|
formula,
|
|
)
|
|
|
|
label = tables.Column("Ticket Type", footer="Subtotals")
|
|
list_price = CurrencySymbolMoneyColumn("Ticket Price")
|
|
actual_price = CurrencySymbolMoneyColumn(_math_header("Actual Price", "P"))
|
|
quantity = tables.Column(
|
|
_math_header("Quantity", "Q"),
|
|
footer=lambda table: table.event.quantity,
|
|
)
|
|
amount = MoneyFooterColumn(_math_header("Amount", "A=P*Q"))
|
|
materials = MoneyFooterColumn(_math_header("CMS Collected Materials Fee", "M=m*Q"))
|
|
amount_without_materials = MoneyFooterColumn(
|
|
_math_header("Event Revenue Base", "B=A-M")
|
|
)
|
|
instructor_revenue = MoneyFooterColumn(
|
|
_math_header("Instructor Percentage Revenue", "R=B*I"),
|
|
)
|
|
instructor_amount = MoneyFooterColumn(
|
|
_math_header("Amount Due to Instructor", "R+M"),
|
|
)
|
|
|
|
class Meta:
|
|
attrs = {
|
|
"class": "table table-sm mx-auto w-auto",
|
|
"tbody": {"class": "table-group-divider"},
|
|
"tfoot": {"class": "table-group-divider"},
|
|
}
|
|
orderable = False
|
|
|
|
|
|
class EventAttendeeTable(tables.Table):
|
|
class Meta:
|
|
model = EventAttendee
|
|
fields = ("name", "email")
|
|
|
|
|
|
class MissingPaperworkTable(tables.Table):
|
|
policy_agreement = tables.BooleanColumn()
|
|
authorize_charge = tables.BooleanColumn()
|
|
|
|
class Meta:
|
|
model = Member
|
|
fields = [
|
|
"first_name",
|
|
"last_name",
|
|
"membership",
|
|
"billing_method",
|
|
"join_date",
|
|
"membership_agreement_signed_and_on_file_date",
|
|
"waiver_form_signed_and_on_file_date",
|
|
"policy_agreement",
|
|
"authorize_charge",
|
|
]
|