Sort person by paid year

This commit is contained in:
Elia Bieri 2024-09-12 17:07:52 +02:00
parent c65c1be0a8
commit 0a4bbb0df7
6 changed files with 88 additions and 6 deletions

View File

@ -10,6 +10,7 @@ import { useUserStore } from "@/stores/user";
import type { DashboardPersonsPageMode, StatisticsFilterItem } from "@/types"; import type { DashboardPersonsPageMode, StatisticsFilterItem } from "@/types";
import { exportDataAsXls } from "@/utils/export"; import { exportDataAsXls } from "@/utils/export";
import { useRouteQuery } from "@vueuse/router"; import { useRouteQuery } from "@vueuse/router";
import dayjs from "dayjs";
import { useTranslation } from "i18next-vue"; import { useTranslation } from "i18next-vue";
import _ from "lodash"; import _ from "lodash";
import log from "loglevel"; import log from "loglevel";
@ -160,7 +161,7 @@ const roles = computed(() => {
return [ return [
{ {
id: "", id: UNFILTERED,
name: `${t("Rolle")}: ${t("a.Alle")}`, name: `${t("Rolle")}: ${t("a.Alle")}`,
}, },
...values, ...values,
@ -191,6 +192,30 @@ const chosenProfiles = computed(() => {
}); });
const selectedChosenProfile = ref<MenuItem>(chosenProfiles.value[0]); const selectedChosenProfile = ref<MenuItem>(chosenProfiles.value[0]);
const paidYears = computed(() => {
const values = _(dashboardPersons.value)
.filter((cs) => dayjs(cs.paid_date).isValid())
.map((cs) => {
const paidYear = dayjs(cs.paid_date).format("YYYY");
return Object.assign({}, cs, {
name: paidYear,
id: paidYear,
});
})
.uniqBy("id")
.orderBy("name")
.value();
return [
{
id: UNFILTERED,
name: `${t("Jahr")}: ${t("a.Alle")}`,
},
...values,
];
});
const selectedPaidYear = ref<MenuItem>(paidYears.value[0]);
const filteredPersons = computed(() => { const filteredPersons = computed(() => {
return _.orderBy( return _.orderBy(
dashboardPersons.value dashboardPersons.value
@ -225,7 +250,7 @@ const filteredPersons = computed(() => {
); );
}) })
.filter((person) => { .filter((person) => {
if (selectedRole.value.id === "") { if (selectedRole.value.id === UNFILTERED) {
return true; return true;
} }
return person.course_sessions.some( return person.course_sessions.some(
@ -233,10 +258,17 @@ const filteredPersons = computed(() => {
); );
}) })
.filter((person) => { .filter((person) => {
if (selectedChosenProfile.value.id === "") { if (selectedChosenProfile.value.id === UNFILTERED) {
return true; return true;
} }
return person.chosen_profile === selectedChosenProfile.value.id; return person.chosen_profile === selectedChosenProfile.value.id;
})
.filter((person) => {
if (selectedPaidYear.value.id === UNFILTERED) {
return true;
}
const paidYear = dayjs(person.paid_date).format("YYYY");
return paidYear == selectedPaidYear.value.id;
}), }),
["last_name", "first_name"] ["last_name", "first_name"]
); );
@ -249,7 +281,8 @@ const filtersVisible = computed(() => {
regions.value.length > 2 || regions.value.length > 2 ||
generations.value.length > 2 || generations.value.length > 2 ||
roles.value.length > 2 || roles.value.length > 2 ||
chosenProfiles.value.length > 2 chosenProfiles.value.length > 2 ||
paidYears.value.length > 2
); );
}); });
@ -380,6 +413,14 @@ watch(selectedRegion, () => {
:items="chosenProfiles" :items="chosenProfiles"
borderless borderless
></ItDropdownSelect> ></ItDropdownSelect>
<ItDropdownSelect
v-if="paidYears.length > 2"
v-model="selectedPaidYear"
data-cy="select-paid-year"
:items="paidYears"
borderless
></ItDropdownSelect>
</section> </section>
<div <div
v-for="person in filteredPersons" v-for="person in filteredPersons"

View File

@ -21,6 +21,7 @@ import type {
XlsExportRequestData, XlsExportRequestData,
XlsExportResponseData, XlsExportResponseData,
} from "@/types"; } from "@/types";
import type dayjs from "dayjs";
export type DashboardPersonRoleType = export type DashboardPersonRoleType =
| "SUPERVISOR" | "SUPERVISOR"
@ -78,11 +79,12 @@ export type DashboardPersonType = {
course_sessions: DashboardPersonCourseSessionType[]; course_sessions: DashboardPersonCourseSessionType[];
avatar_url: string; avatar_url: string;
avatar_url_small: string; avatar_url_small: string;
chosen_profile: string;
competence_metrics?: { competence_metrics?: {
passed_count: number; passed_count: number;
failed_count: number; failed_count: number;
}; };
chosen_profile: string; paid_date?: string;
}; };
export type DashboardCourseConfigType = { export type DashboardCourseConfigType = {

View File

@ -1,4 +1,5 @@
import uuid import uuid
from datetime import datetime
from enum import Enum from enum import Enum
from django.db import models from django.db import models
@ -13,6 +14,7 @@ from vbv_lernwelt.core.model_utils import find_available_slug
from vbv_lernwelt.core.models import User from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.serializer_helpers import get_course_serializer_class from vbv_lernwelt.course.serializer_helpers import get_course_serializer_class
from vbv_lernwelt.files.models import UploadFile from vbv_lernwelt.files.models import UploadFile
from vbv_lernwelt.shop.models import CheckoutState
class CircleContactType(Enum): class CircleContactType(Enum):
@ -301,6 +303,16 @@ class CourseSessionUser(models.Model):
] ]
ordering = ["user__last_name", "user__first_name", "user__email"] ordering = ["user__last_name", "user__first_name", "user__email"]
@property
def paid_date(self) -> datetime | None:
"""
Returns the date when the user paid for the course session
"""
checkout = self.user.checkout_information.filter(state=CheckoutState.PAID)
if checkout:
return checkout.first().created_at
return None
def __str__(self): def __str__(self):
return f"{self.user} ({self.course_session.title})" return f"{self.user} ({self.course_session.title})"

View File

@ -109,6 +109,7 @@ def create_person_list_with_roles(
"avatar_url": user_object.avatar_url, "avatar_url": user_object.avatar_url,
"course_sessions": [], "course_sessions": [],
"chosen_profile": csu.chosen_profile.code if csu.chosen_profile else "all", "chosen_profile": csu.chosen_profile.code if csu.chosen_profile else "all",
"paid_date": csu.paid_date.isoformat() if csu.paid_date else None,
} }
if include_private_data: if include_private_data:
user_data["phone_number"] = user_object.phone_number user_data["phone_number"] = user_object.phone_number

View File

@ -0,0 +1,24 @@
# Generated by Django 4.2.13 on 2024-09-12 13:40
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("shop", "0019_alter_checkoutinformation_refno2"),
]
operations = [
migrations.AlterField(
model_name="checkoutinformation",
name="user",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="checkout_information",
to=settings.AUTH_USER_MODEL,
),
),
]

View File

@ -43,7 +43,9 @@ class CheckoutInformation(models.Model):
(INVOICE_ADDRESS_ORGANISATION, "Organisation"), (INVOICE_ADDRESS_ORGANISATION, "Organisation"),
) )
user = models.ForeignKey("core.User", on_delete=models.PROTECT) user = models.ForeignKey(
"core.User", on_delete=models.PROTECT, related_name="checkout_information"
)
product_sku = models.CharField(max_length=255) product_sku = models.CharField(max_length=255)
product_name = models.CharField(max_length=255) product_name = models.CharField(max_length=255)