Sort person by paid year
This commit is contained in:
parent
c65c1be0a8
commit
0a4bbb0df7
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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})"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue