Merged develop into feature/VBV-704-eine-note-im-kn-für-ük
This commit is contained in:
commit
6f2b437a5c
|
|
@ -97,6 +97,7 @@ js-linting: &js-linting
|
||||||
|
|
||||||
default-steps: &default-steps
|
default-steps: &default-steps
|
||||||
- parallel:
|
- parallel:
|
||||||
|
- step: *e2e
|
||||||
- step: *e2e
|
- step: *e2e
|
||||||
- step: *e2e
|
- step: *e2e
|
||||||
- step: *python-tests
|
- step: *python-tests
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,12 @@ def main(app_name, image_name, environment_file):
|
||||||
"AWS_S3_SECRET_ACCESS_KEY": env.str("AWS_S3_SECRET_ACCESS_KEY", ""),
|
"AWS_S3_SECRET_ACCESS_KEY": env.str("AWS_S3_SECRET_ACCESS_KEY", ""),
|
||||||
"AWS_S3_REGION_NAME": "eu-central-1",
|
"AWS_S3_REGION_NAME": "eu-central-1",
|
||||||
"AWS_STORAGE_BUCKET_NAME": "myvbv-dev.iterativ.ch",
|
"AWS_STORAGE_BUCKET_NAME": "myvbv-dev.iterativ.ch",
|
||||||
"DATATRANS_HMAC_KEY": env.str("DATATRANS_HMAC_KEY", ""),
|
"DATATRANS_HMAC_KEY": env.str("PIPELINES_DATATRANS_HMAC_KEY", ""),
|
||||||
"DATATRANS_BASIC_AUTH_KEY": env.str("DATATRANS_BASIC_AUTH_KEY", ""),
|
"DATATRANS_BASIC_AUTH_KEY": env.str(
|
||||||
|
"PIPELINES_DATATRANS_BASIC_AUTH_KEY", ""
|
||||||
|
),
|
||||||
|
"DATATRANS_API_ENDPOINT": "https://api.sandbox.datatrans.com",
|
||||||
|
"DATATRANS_PAY_URL": "https://pay.sandbox.datatrans.com",
|
||||||
"FILE_UPLOAD_STORAGE": "s3",
|
"FILE_UPLOAD_STORAGE": "s3",
|
||||||
"IT_DJANGO_DEBUG": "false",
|
"IT_DJANGO_DEBUG": "false",
|
||||||
"IT_SERVE_VUE": "false",
|
"IT_SERVE_VUE": "false",
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import type { CourseStatisticsType } from "@/gql/graphql";
|
||||||
import AssignmentSummaryBox from "@/components/dashboard/AssignmentSummaryBox.vue";
|
import AssignmentSummaryBox from "@/components/dashboard/AssignmentSummaryBox.vue";
|
||||||
import FeedbackSummaryBox from "@/components/dashboard/FeedbackSummaryBox.vue";
|
import FeedbackSummaryBox from "@/components/dashboard/FeedbackSummaryBox.vue";
|
||||||
import CompetenceSummaryBox from "@/components/dashboard/CompetenceSummaryBox.vue";
|
import CompetenceSummaryBox from "@/components/dashboard/CompetenceSummaryBox.vue";
|
||||||
|
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseId: string;
|
courseId: string;
|
||||||
|
|
@ -95,4 +96,7 @@ onMounted(async () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="flex w-full flex-row justify-center">
|
||||||
|
<LoadingSpinner />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPa
|
||||||
import { calculateCircleSectorData } from "@/pages/learningPath/learningPathPage/utils";
|
import { calculateCircleSectorData } from "@/pages/learningPath/learningPathPage/utils";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useCourseCircleProgress, useCourseDataWithCompletion } from "@/composables";
|
import { useCourseCircleProgress, useCourseDataWithCompletion } from "@/composables";
|
||||||
|
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||||
|
|
||||||
export type DiagramType = "horizontal" | "horizontalSmall" | "singleSmall";
|
export type DiagramType = "horizontal" | "horizontalSmall" | "singleSmall";
|
||||||
|
|
||||||
|
|
@ -54,23 +55,25 @@ const { inProgressCirclesCount, circlesCount } = useCourseCircleProgress(
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h4
|
<div v-if="circlesCount > 0">
|
||||||
v-if="diagramType === 'horizontal' && circles.length > 0"
|
<h4 v-if="diagramType === 'horizontal'" class="mb-4 font-bold">
|
||||||
class="mb-4 font-bold"
|
{{
|
||||||
>
|
$t("learningPathPage.progressText", {
|
||||||
{{
|
inProgressCount: inProgressCirclesCount,
|
||||||
$t("learningPathPage.progressText", {
|
allCount: circlesCount,
|
||||||
inProgressCount: inProgressCirclesCount,
|
})
|
||||||
allCount: circlesCount,
|
}}
|
||||||
})
|
</h4>
|
||||||
}}
|
<div :class="wrapperClasses">
|
||||||
</h4>
|
<LearningPathCircle
|
||||||
<div :class="wrapperClasses">
|
v-for="circle in circles"
|
||||||
<LearningPathCircle
|
:key="circle.id"
|
||||||
v-for="circle in circles"
|
:sectors="calculateCircleSectorData(circle)"
|
||||||
:key="circle.id"
|
></LearningPathCircle>
|
||||||
:sectors="calculateCircleSectorData(circle)"
|
</div>
|
||||||
></LearningPathCircle>
|
</div>
|
||||||
|
<div v-else class="flex justify-center">
|
||||||
|
<LoadingSpinner />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ const orgAddress = computed({
|
||||||
for="company-name"
|
for="company-name"
|
||||||
class="block text-sm font-medium leading-6 text-gray-900"
|
class="block text-sm font-medium leading-6 text-gray-900"
|
||||||
>
|
>
|
||||||
{{ $t("a.Name") }}
|
{{ $t("a.Firmenname") }}
|
||||||
</label>
|
</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<input
|
<input
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useEntities } from "@/services/entities";
|
import { useEntities } from "@/services/entities";
|
||||||
import VueDatePicker from "@vuepic/vue-datepicker";
|
|
||||||
import "@vuepic/vue-datepicker/dist/main.css";
|
import "@vuepic/vue-datepicker/dist/main.css";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { useUserStore } from "@/stores/user";
|
import ItDatePicker from "@/components/ui/ItDatePicker.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
|
@ -25,20 +24,12 @@ const props = defineProps<{
|
||||||
const emit = defineEmits(["update:modelValue"]);
|
const emit = defineEmits(["update:modelValue"]);
|
||||||
|
|
||||||
const { countries } = useEntities();
|
const { countries } = useEntities();
|
||||||
const userStore = useUserStore();
|
|
||||||
|
|
||||||
const paymentMethods = [
|
const paymentMethods = [
|
||||||
{ value: "credit_card", label: t("a.Debit-/Kreditkarte/Twint") },
|
{ value: "credit_card", label: t("a.Debit-/Kreditkarte/Twint") },
|
||||||
{ value: "cembra_byjuno", label: t("a.Rechnung") },
|
{ value: "cembra_byjuno", label: t("a.Rechnung") },
|
||||||
];
|
];
|
||||||
|
|
||||||
// TODO: remove after cembra is ready for production
|
|
||||||
const appEnv = import.meta.env.VITE_APP_ENVIRONMENT || "local";
|
|
||||||
if (appEnv.startsWith("prod")) {
|
|
||||||
paymentMethods.splice(1, 1);
|
|
||||||
}
|
|
||||||
// END TODO
|
|
||||||
|
|
||||||
const address = computed({
|
const address = computed({
|
||||||
get() {
|
get() {
|
||||||
return props.modelValue;
|
return props.modelValue;
|
||||||
|
|
@ -234,40 +225,8 @@ const address = computed({
|
||||||
{{ $t("a.Geburtsdatum") }}
|
{{ $t("a.Geburtsdatum") }}
|
||||||
</label>
|
</label>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<VueDatePicker
|
<ItDatePicker v-model="address.birth_date"></ItDatePicker>
|
||||||
v-model="address.birth_date"
|
|
||||||
format="dd.MM.yyyy"
|
|
||||||
no-today
|
|
||||||
model-type="yyyy-MM-dd"
|
|
||||||
name="birth-date"
|
|
||||||
max-date="2007-01-01"
|
|
||||||
prevent-min-max-navigation
|
|
||||||
required
|
|
||||||
:enable-time-picker="false"
|
|
||||||
text-input
|
|
||||||
placeholder="15.06.1982"
|
|
||||||
start-date="1982-01-01"
|
|
||||||
:locale="userStore.language"
|
|
||||||
:cancel-text="$t('a.Abbrechen')"
|
|
||||||
:select-text="$t('a.Auswählen')"
|
|
||||||
></VueDatePicker>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Theming for date picker */
|
|
||||||
.dp__theme_light {
|
|
||||||
--dp-text-color: #585f63;
|
|
||||||
--dp-primary-color: #41b5fa;
|
|
||||||
--dp-border-color-focus: #3d6dcc;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--dp-font-family: "Buenos Aires" sans-serif;
|
|
||||||
--dp-border-radius: none;
|
|
||||||
--dp-font-size: 0.875rem;
|
|
||||||
--dp-cell-border-radius: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import { useEntities } from "@/services/entities";
|
||||||
import AvatarImage from "@/components/ui/AvatarImage.vue";
|
import AvatarImage from "@/components/ui/AvatarImage.vue";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { type User, useUserStore } from "@/stores/user";
|
import { type User, useUserStore } from "@/stores/user";
|
||||||
|
import ItDatePicker from "@/components/ui/ItDatePicker.vue";
|
||||||
|
import { normalizeSwissPhoneNumber } from "@/utils/phone";
|
||||||
|
|
||||||
const emit = defineEmits(["cancel", "save"]);
|
const emit = defineEmits(["cancel", "save"]);
|
||||||
|
|
||||||
|
|
@ -21,7 +23,12 @@ const formData = ref({
|
||||||
postal_code: user.postal_code,
|
postal_code: user.postal_code,
|
||||||
city: user.city,
|
city: user.city,
|
||||||
country_code: user.country?.country_code,
|
country_code: user.country?.country_code,
|
||||||
|
|
||||||
|
phone_number: user.phone_number,
|
||||||
|
birth_date: user.birth_date,
|
||||||
|
|
||||||
organisation: user.organisation,
|
organisation: user.organisation,
|
||||||
|
organisation_detail_name: user.organisation_detail_name,
|
||||||
organisation_street: user.organisation_street,
|
organisation_street: user.organisation_street,
|
||||||
organisation_street_number: user.organisation_street_number,
|
organisation_street_number: user.organisation_street_number,
|
||||||
organisation_postal_code: user.organisation_postal_code,
|
organisation_postal_code: user.organisation_postal_code,
|
||||||
|
|
@ -31,7 +38,8 @@ const formData = ref({
|
||||||
});
|
});
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
const { country_code, organisation_country_code, ...profileData } = formData.value;
|
const { country_code, organisation_country_code, phone_number, ...profileData } =
|
||||||
|
formData.value;
|
||||||
const typedProfileData: Partial<User> = { ...profileData };
|
const typedProfileData: Partial<User> = { ...profileData };
|
||||||
|
|
||||||
typedProfileData.country = countries.value.find(
|
typedProfileData.country = countries.value.find(
|
||||||
|
|
@ -41,6 +49,10 @@ async function save() {
|
||||||
(c) => c.country_code === organisation_country_code
|
(c) => c.country_code === organisation_country_code
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (phone_number) {
|
||||||
|
typedProfileData.phone_number = normalizeSwissPhoneNumber(phone_number);
|
||||||
|
}
|
||||||
|
|
||||||
await user.updateUserProfile(typedProfileData);
|
await user.updateUserProfile(typedProfileData);
|
||||||
emit("save");
|
emit("save");
|
||||||
}
|
}
|
||||||
|
|
@ -126,6 +138,28 @@ async function avatarUpload(e: Event) {
|
||||||
disabled
|
disabled
|
||||||
class="disabled:bg-gray-50 mb-4 block w-full border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 disabled:cursor-not-allowed disabled:text-gray-500 disabled:ring-gray-200 sm:max-w-sm sm:text-sm sm:leading-6"
|
class="disabled:bg-gray-50 mb-4 block w-full border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 disabled:cursor-not-allowed disabled:text-gray-500 disabled:ring-gray-200 sm:max-w-sm sm:text-sm sm:leading-6"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<label for="phone" class="block pb-1.5 leading-6">
|
||||||
|
{{ $t("a.Telefonnummer") }}
|
||||||
|
</label>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
id="phone"
|
||||||
|
v-model="formData.phone_number"
|
||||||
|
type="text"
|
||||||
|
name="phone"
|
||||||
|
autocomplete="phone-number"
|
||||||
|
class="disabled:bg-gray-50 mb-4 block w-full border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 disabled:cursor-not-allowed disabled:text-gray-500 disabled:ring-gray-200 sm:max-w-sm sm:text-sm sm:leading-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label for="birth-date" class="block pb-1.5 leading-6">
|
||||||
|
{{ $t("a.Geburtsdatum") }}
|
||||||
|
</label>
|
||||||
|
<div class="mb-4 block w-full py-1.5 sm:max-w-sm sm:text-sm sm:leading-6">
|
||||||
|
<ItDatePicker v-model="formData.birth_date"></ItDatePicker>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label class="block pb-1.5 leading-6">
|
<label class="block pb-1.5 leading-6">
|
||||||
{{ $t("a.Profilbild") }}
|
{{ $t("a.Profilbild") }}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -264,6 +298,22 @@ async function avatarUpload(e: Event) {
|
||||||
{{ $t("a.Firmenanschrift") }}
|
{{ $t("a.Firmenanschrift") }}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
|
<div class="flex flex-col justify-start md:flex-row md:space-x-4">
|
||||||
|
<div class="w-full md:max-w-lg">
|
||||||
|
<label for="org-street-address" class="block pb-1.5 leading-6">
|
||||||
|
{{ $t("a.Firmenname") }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
id="org-detail-name"
|
||||||
|
v-model="formData.organisation_detail_name"
|
||||||
|
type="text"
|
||||||
|
name="org-detail-name"
|
||||||
|
class="disabled:bg-gray-50 mb-4 block w-full border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 disabled:cursor-not-allowed disabled:text-gray-500 disabled:ring-gray-200 sm:text-sm sm:leading-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col justify-start md:flex-row md:space-x-4">
|
<div class="flex flex-col justify-start md:flex-row md:space-x-4">
|
||||||
<div class="w-full md:max-w-sm">
|
<div class="w-full md:max-w-sm">
|
||||||
<label for="org-street-address" class="block pb-1.5 leading-6">
|
<label for="org-street-address" class="block pb-1.5 leading-6">
|
||||||
|
|
@ -389,7 +439,7 @@ async function avatarUpload(e: Event) {
|
||||||
<button class="btn btn-secondary" @click="emit('cancel')">
|
<button class="btn btn-secondary" @click="emit('cancel')">
|
||||||
{{ $t("general.cancel") }}
|
{{ $t("general.cancel") }}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-primary" @click="save">
|
<button class="btn btn-primary" data-cy="saveButton" @click="save">
|
||||||
{{ $t("general.save") }}
|
{{ $t("general.save") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import { useUserStore } from "@/stores/user";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useEntities } from "@/services/entities";
|
import { useEntities } from "@/services/entities";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { displaySwissPhoneNumber } from "@/utils/phone";
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
@ -10,21 +12,20 @@ const user = useUserStore();
|
||||||
const { organisations } = useEntities();
|
const { organisations } = useEntities();
|
||||||
|
|
||||||
const privateAddress = computed(() => {
|
const privateAddress = computed(() => {
|
||||||
let addressText = `${user.street} ${user.street_number}`.trim();
|
const textParts = [];
|
||||||
if (user.postal_code || user.city) {
|
|
||||||
if (addressText.length) {
|
if (user.street || user.street_number) {
|
||||||
addressText += ", ";
|
textParts.push(`${user.street} ${user.street_number}`.trim());
|
||||||
}
|
|
||||||
addressText += `${user.postal_code} ${user.city}`;
|
|
||||||
}
|
|
||||||
if (user.country) {
|
|
||||||
if (addressText.length) {
|
|
||||||
addressText += ", ";
|
|
||||||
}
|
|
||||||
addressText += user.country.name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return addressText.trim();
|
if (user.postal_code || user.city) {
|
||||||
|
textParts.push(`${user.postal_code} ${user.city}`);
|
||||||
|
}
|
||||||
|
if (textParts.length && user.country) {
|
||||||
|
textParts.push(user.country.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return textParts;
|
||||||
});
|
});
|
||||||
|
|
||||||
const organisationName = computed(() => {
|
const organisationName = computed(() => {
|
||||||
|
|
@ -36,22 +37,25 @@ const organisationName = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const orgAddress = computed(() => {
|
const orgAddress = computed(() => {
|
||||||
let addressText =
|
const textParts = [];
|
||||||
`${user.organisation_street} ${user.organisation_street_number}`.trim();
|
|
||||||
if (user.organisation_postal_code || user.organisation_city) {
|
if (user.organisation_detail_name) {
|
||||||
if (addressText.length) {
|
textParts.push(user.organisation_detail_name);
|
||||||
addressText += ", ";
|
|
||||||
}
|
|
||||||
addressText += `${user.organisation_postal_code} ${user.organisation_city}`;
|
|
||||||
}
|
|
||||||
if (user.organisation_country) {
|
|
||||||
if (addressText.length) {
|
|
||||||
addressText += ", ";
|
|
||||||
}
|
|
||||||
addressText += user.organisation_country.name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return addressText.trim();
|
if (user.organisation_street || user.organisation_street_number) {
|
||||||
|
textParts.push(
|
||||||
|
`${user.organisation_street} ${user.organisation_street_number}`.trim()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (user.organisation_postal_code || user.organisation_city) {
|
||||||
|
textParts.push(`${user.organisation_postal_code} ${user.organisation_city}`);
|
||||||
|
}
|
||||||
|
if (textParts.length && user.organisation_country) {
|
||||||
|
textParts.push(user.organisation_country.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return textParts;
|
||||||
});
|
});
|
||||||
|
|
||||||
const invoiceAddress = computed(() => {
|
const invoiceAddress = computed(() => {
|
||||||
|
|
@ -67,20 +71,45 @@ const invoiceAddress = computed(() => {
|
||||||
<h3 class="mb-2">{{ $t("a.Persönliche Informationen") }}</h3>
|
<h3 class="mb-2">{{ $t("a.Persönliche Informationen") }}</h3>
|
||||||
<div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-8 sm:py-6">
|
<div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-8 sm:py-6">
|
||||||
<label class="block font-semibold leading-6">{{ $t("a.Vorname") }}</label>
|
<label class="block font-semibold leading-6">{{ $t("a.Vorname") }}</label>
|
||||||
<div class="mb-3 sm:col-span-2 sm:mb-0">{{ user.first_name }}</div>
|
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="firstName">
|
||||||
|
{{ user.first_name }}
|
||||||
|
</div>
|
||||||
<label class="block font-semibold leading-6">{{ $t("a.Name") }}</label>
|
<label class="block font-semibold leading-6">{{ $t("a.Name") }}</label>
|
||||||
<div class="mb-3 sm:col-span-2 sm:mb-0">{{ user.last_name }}</div>
|
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="lastName">
|
||||||
|
{{ user.last_name }}
|
||||||
|
</div>
|
||||||
<label class="block font-semibold leading-6">
|
<label class="block font-semibold leading-6">
|
||||||
{{ $t("a.E-Mail Adresse") }}
|
{{ $t("a.E-Mail Adresse") }}
|
||||||
</label>
|
</label>
|
||||||
<div class="mb-3 sm:col-span-2 sm:mb-0">{{ user.email }}</div>
|
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="email">{{ user.email }}</div>
|
||||||
|
<label class="block font-semibold leading-6">
|
||||||
|
{{ $t("a.Telefonnummer") }}
|
||||||
|
</label>
|
||||||
|
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="phone">
|
||||||
|
<span v-if="user.phone_number">
|
||||||
|
{{ displaySwissPhoneNumber(user.phone_number) }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="text-gray-800">{{ $t("a.Keine Angabe") }}</span>
|
||||||
|
</div>
|
||||||
|
<label class="block font-semibold leading-6">
|
||||||
|
{{ $t("a.Geburtsdatum") }}
|
||||||
|
</label>
|
||||||
|
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="birthDate">
|
||||||
|
<span v-if="user.birth_date">
|
||||||
|
{{ dayjs(user.birth_date).format("DD.MM.YYYY") }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="text-gray-800">{{ $t("a.Keine Angabe") }}</span>
|
||||||
|
</div>
|
||||||
<label class="block font-semibold leading-6">
|
<label class="block font-semibold leading-6">
|
||||||
{{ $t("a.Privatadresse") }}
|
{{ $t("a.Privatadresse") }}
|
||||||
</label>
|
</label>
|
||||||
<div class="mb-3 sm:col-span-2 sm:mb-0">
|
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="privateAddress">
|
||||||
<template v-if="privateAddress">
|
<div v-if="privateAddress.length">
|
||||||
{{ privateAddress }}
|
<span v-for="(line, index) in privateAddress" :key="index">
|
||||||
</template>
|
{{ line }}
|
||||||
|
<br />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<span v-else class="text-gray-800">{{ $t("a.Keine Angabe") }}</span>
|
<span v-else class="text-gray-800">{{ $t("a.Keine Angabe") }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -89,14 +118,19 @@ const invoiceAddress = computed(() => {
|
||||||
<h3 class="my-2">{{ $t("a.Geschäftsdaten") }}</h3>
|
<h3 class="my-2">{{ $t("a.Geschäftsdaten") }}</h3>
|
||||||
<div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-8 sm:py-6">
|
<div class="sm:grid sm:grid-cols-3 sm:items-start sm:gap-8 sm:py-6">
|
||||||
<label class="block font-semibold leading-6">{{ $t("a.Unternehmen") }}</label>
|
<label class="block font-semibold leading-6">{{ $t("a.Unternehmen") }}</label>
|
||||||
<div class="mb-3 sm:col-span-2 sm:mb-0">{{ organisationName }}</div>
|
<div class="mb-3 sm:col-span-2 sm:mb-0" data-cy="organisationDetailName">
|
||||||
|
{{ organisationName }}
|
||||||
|
</div>
|
||||||
<label class="block font-semibold leading-6">
|
<label class="block font-semibold leading-6">
|
||||||
{{ $t("a.Firmenanschrift") }}
|
{{ $t("a.Firmenanschrift") }}
|
||||||
</label>
|
</label>
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<template v-if="orgAddress">
|
<div v-if="orgAddress" data-cy="organisationAddress">
|
||||||
{{ orgAddress }}
|
<span v-for="(line, index) in orgAddress" :key="index">
|
||||||
</template>
|
{{ line }}
|
||||||
|
<br />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<span v-else class="text-gray-800">{{ $t("a.Keine Angabe") }}</span>
|
<span v-else class="text-gray-800">{{ $t("a.Keine Angabe") }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import VueDatePicker from "@vuepic/vue-datepicker";
|
||||||
|
import "@vuepic/vue-datepicker/dist/main.css";
|
||||||
|
import { defineProps, withDefaults, defineModel } from "vue";
|
||||||
|
|
||||||
|
const model = defineModel<string>();
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
noToday?: boolean;
|
||||||
|
required?: boolean;
|
||||||
|
placeholder?: string;
|
||||||
|
startDate?: string;
|
||||||
|
maxDate?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
required: false,
|
||||||
|
placeholder: "15.06.1982",
|
||||||
|
startDate: "1982-01-01",
|
||||||
|
maxDate: "2007-01-01",
|
||||||
|
});
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VueDatePicker
|
||||||
|
v-model="model"
|
||||||
|
format="dd.MM.yyyy"
|
||||||
|
model-type="yyyy-MM-dd"
|
||||||
|
name="date"
|
||||||
|
prevent-min-max-navigation
|
||||||
|
:required="props.required"
|
||||||
|
:enable-time-picker="false"
|
||||||
|
text-input
|
||||||
|
:no-today="props.noToday"
|
||||||
|
:max-date="props.maxDate"
|
||||||
|
:placeholder="props.placeholder"
|
||||||
|
:start-date="props.startDate"
|
||||||
|
:locale="userStore.language"
|
||||||
|
:cancel-text="$t('a.Abbrechen')"
|
||||||
|
:select-text="$t('a.Auswählen')"
|
||||||
|
></VueDatePicker>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dp__theme_light {
|
||||||
|
--dp-text-color: #585f63;
|
||||||
|
--dp-primary-color: #41b5fa;
|
||||||
|
--dp-border-color-focus: #3d6dcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--dp-font-family: "Buenos Aires" sans-serif;
|
||||||
|
--dp-border-radius: none;
|
||||||
|
--dp-font-size: 0.875rem;
|
||||||
|
--dp-cell-border-radius: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,18 +1,30 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ItRow from "@/components/ui/ItRow.vue";
|
import ItRow from "@/components/ui/ItRow.vue";
|
||||||
|
|
||||||
defineProps<{
|
export interface Props {
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
name: string;
|
name: string;
|
||||||
}>();
|
extraInfo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
extraInfo: "",
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ItRow>
|
<ItRow>
|
||||||
<template #firstRow>
|
<template #firstRow>
|
||||||
<slot name="leading"></slot>
|
<slot name="leading"></slot>
|
||||||
<img class="mr-2 h-11 w-11 rounded-full" :src="avatarUrl" />
|
<img class="mr-2 h-11 w-11 rounded-full" :src="props.avatarUrl" />
|
||||||
<p class="text-bold lg:leading-[45px]">{{ name }}</p>
|
<div :class="props.extraInfo ? 'leading-5' : ''">
|
||||||
|
<p class="text-bold" :class="props.extraInfo ? '' : 'lg:leading-[45px]'">
|
||||||
|
{{ props.name }}
|
||||||
|
</p>
|
||||||
|
<p v-if="props.extraInfo" class="font-normal" data-cy="extra-info">
|
||||||
|
{{ props.extraInfo }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #center>
|
<template #center>
|
||||||
<slot name="center"></slot>
|
<slot name="center"></slot>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Course IDs
|
||||||
|
export const COURSE_TEST_ID = -1;
|
||||||
|
export const COURSE_UK = -3;
|
||||||
|
export const COURSE_VERSICHERUNGSVERMITTLERIN_ID = -4;
|
||||||
|
export const COURSE_UK_FR = -5;
|
||||||
|
export const COURSE_UK_TRAINING = -6;
|
||||||
|
export const COURSE_UK_TRAINING_FR = -7;
|
||||||
|
export const COURSE_UK_IT = -8;
|
||||||
|
export const COURSE_UK_TRAINING_IT = -9;
|
||||||
|
export const COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID = -10;
|
||||||
|
export const COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID = -11;
|
||||||
|
export const COURSE_VERSICHERUNGSVERMITTLERIN_PRUEFUNG_ID = -12;
|
||||||
|
export const COURSE_MOTORFAHRZEUG_PRUEFUNG_ID = -13;
|
||||||
|
|
||||||
|
// Organization IDs
|
||||||
|
export const ORGANISATION_OTHER_BROKER_ID = 1;
|
||||||
|
export const ORGANISATION_OTHER_HEALTH_INSURANCE_ID = 2;
|
||||||
|
export const ORGANISATION_OTHER_PRIVATE_INSURANCE_ID = 3;
|
||||||
|
export const ORGANISATION_NO_COMPANY_ID = 31;
|
||||||
|
|
@ -20,7 +20,7 @@ const documents = {
|
||||||
"\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_points_deducted\n evaluation_points_deducted_reason\n evaluation_points_final\n\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n": types.AssignmentCompletionQueryDocument,
|
"\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_points_deducted\n evaluation_points_deducted_reason\n evaluation_points_final\n\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n": types.AssignmentCompletionQueryDocument,
|
||||||
"\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n competence_certificate_weight\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_points_deducted\n evaluation_points_final\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateQueryDocument,
|
"\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n competence_certificate_weight\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_points_deducted\n evaluation_points_final\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateQueryDocument,
|
||||||
"\n query competenceCertificateForUserQuery(\n $courseSlug: String!\n $courseSessionId: ID!\n $userId: UUID!\n ) {\n competence_certificate_list_for_user(course_slug: $courseSlug, user_id: $userId) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n competence_certificate_weight\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_points_final\n evaluation_points_deducted\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateForUserQueryDocument,
|
"\n query competenceCertificateForUserQuery(\n $courseSlug: String!\n $courseSessionId: ID!\n $userId: UUID!\n ) {\n competence_certificate_list_for_user(course_slug: $courseSlug, user_id: $userId) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n competence_certificate_weight\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_points_final\n evaluation_points_deducted\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateForUserQueryDocument,
|
||||||
"\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n": types.CourseSessionDetailDocument,
|
"\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n optional_attendance\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n": types.CourseSessionDetailDocument,
|
||||||
"\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n is_uk\n }\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n": types.CourseQueryDocument,
|
"\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n is_uk\n }\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n": types.CourseQueryDocument,
|
||||||
"\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n is_uk\n }\n }\n }\n": types.DashboardConfigDocument,
|
"\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n is_uk\n }\n }\n }\n": types.DashboardConfigDocument,
|
||||||
"\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n competence {\n _id\n total_count\n success_count\n fail_count\n }\n assignment {\n _id\n total_count\n points_max_count\n points_achieved_count\n }\n }\n }\n": types.DashboardProgressDocument,
|
"\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n competence {\n _id\n total_count\n success_count\n fail_count\n }\n assignment {\n _id\n total_count\n points_max_count\n points_achieved_count\n }\n }\n }\n": types.DashboardProgressDocument,
|
||||||
|
|
@ -75,7 +75,7 @@ export function graphql(source: "\n query competenceCertificateForUserQuery(\n
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(source: "\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n"];
|
export function graphql(source: "\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n optional_attendance\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n optional_attendance\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n"];
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -713,6 +713,7 @@ type CourseSessionUserObjectsType {
|
||||||
avatar_url: String!
|
avatar_url: String!
|
||||||
role: String!
|
role: String!
|
||||||
circles: [CourseSessionUserExpertCircleType!]!
|
circles: [CourseSessionUserExpertCircleType!]!
|
||||||
|
optional_attendance: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type CourseSessionUserExpertCircleType {
|
type CourseSessionUserExpertCircleType {
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,7 @@ export const COURSE_SESSION_DETAIL_QUERY = graphql(`
|
||||||
title
|
title
|
||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
|
optional_attendance
|
||||||
}
|
}
|
||||||
attendance_courses {
|
attendance_courses {
|
||||||
id
|
id
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import VerticalBarChart from "@/components/ui/VerticalBarChart.vue";
|
||||||
import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
|
import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
|
||||||
import logger from "loglevel";
|
import logger from "loglevel";
|
||||||
import { reactive, ref } from "vue";
|
import { reactive, ref } from "vue";
|
||||||
|
import "@vuepic/vue-datepicker/dist/main.css";
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
checkboxValue: true,
|
checkboxValue: true,
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,9 @@ watch(
|
||||||
:name="`${csu.first_name} ${csu.last_name}`"
|
:name="`${csu.first_name} ${csu.last_name}`"
|
||||||
:avatar-url="csu.avatar_url"
|
:avatar-url="csu.avatar_url"
|
||||||
:class="0 === index ? 'border-none' : ''"
|
:class="0 === index ? 'border-none' : ''"
|
||||||
|
:extra-info="
|
||||||
|
csu.optional_attendance ? `${$t('a.Optionale Anwesenheit')}` : ''
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<template #leading>
|
<template #leading>
|
||||||
<ItCheckbox
|
<ItCheckbox
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,14 @@ import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import type { DashboardPersonCourseSessionType } from "@/services/dashboard";
|
import {
|
||||||
|
type DashboardPersonCourseSessionType,
|
||||||
|
exportPersons,
|
||||||
|
} from "@/services/dashboard";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import type { DashboardPersonsPageMode } from "@/types";
|
import type { DashboardPersonsPageMode, StatisticsFilterItem } from "@/types";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import { exportDataAsXls } from "@/utils/export";
|
||||||
|
|
||||||
log.debug("DashboardPersonsPage created");
|
log.debug("DashboardPersonsPage created");
|
||||||
|
|
||||||
|
|
@ -28,6 +33,7 @@ type MenuItem = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const { loading, dashboardPersons } = useDashboardPersonsDueDates(props.mode);
|
const { loading, dashboardPersons } = useDashboardPersonsDueDates(props.mode);
|
||||||
|
|
||||||
|
|
@ -227,6 +233,32 @@ function personRoleDisplayValue(personCourseSession: DashboardPersonCourseSessio
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exportData() {
|
||||||
|
const courseSessionIdsSet = new Set<string>();
|
||||||
|
// get all course session ids from users
|
||||||
|
if (selectedSession.value.id === UNFILTERED) {
|
||||||
|
for (const person of filteredPersons.value) {
|
||||||
|
for (const courseSession of person.course_sessions) {
|
||||||
|
courseSessionIdsSet.add(courseSession.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
courseSessionIdsSet.add(selectedSession.value.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct StatisticsFilterItems for export call
|
||||||
|
const items: StatisticsFilterItem[] = [];
|
||||||
|
for (const csId of courseSessionIdsSet) {
|
||||||
|
items.push({
|
||||||
|
_id: "",
|
||||||
|
course_session_id: csId,
|
||||||
|
generation: "",
|
||||||
|
circle_id: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exportDataAsXls(items, exportPersons, userStore.language);
|
||||||
|
}
|
||||||
|
|
||||||
watch(selectedCourse, () => {
|
watch(selectedCourse, () => {
|
||||||
selectedRegion.value = regions.value[0];
|
selectedRegion.value = regions.value[0];
|
||||||
});
|
});
|
||||||
|
|
@ -253,7 +285,18 @@ watch(selectedRegion, () => {
|
||||||
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
|
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
|
||||||
<span class="inline">{{ $t("general.back") }}</span>
|
<span class="inline">{{ $t("general.back") }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<h2 class="my-4">{{ $t("a.Personen") }}</h2>
|
<div class="mb-10 flex items-center justify-between">
|
||||||
|
<h2 class="my-4">{{ $t("a.Personen") }}</h2>
|
||||||
|
<button
|
||||||
|
v-if="userStore.course_session_experts.length > 0"
|
||||||
|
class="flex"
|
||||||
|
data-cy="export-button"
|
||||||
|
@click="exportData"
|
||||||
|
>
|
||||||
|
<it-icon-export></it-icon-export>
|
||||||
|
<span class="ml inline-block">{{ $t("a.Als Excel exportieren") }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="bg-white px-4 py-2">
|
<div class="bg-white px-4 py-2">
|
||||||
<section
|
<section
|
||||||
v-if="filtersVisible"
|
v-if="filtersVisible"
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,6 @@ const submissionDeadline = computed(() => {
|
||||||
?.submission_deadline;
|
?.submission_deadline;
|
||||||
});
|
});
|
||||||
|
|
||||||
// FIXME daniel: `useRouteQuery` from usevue is currently the reason that we have to
|
|
||||||
// fix the version of @vueuse/router and @vueuse/core to 10.1.0
|
|
||||||
// it fails with version 10.2.0. I have a reminder to check out the situation
|
|
||||||
// at the end of July 2023
|
|
||||||
// 0 = introduction, 1 - n = tasks, n+1 = submission
|
// 0 = introduction, 1 - n = tasks, n+1 = submission
|
||||||
const stepIndex = useRouteQuery("step", "0", { transform: Number, mode: "push" });
|
const stepIndex = useRouteQuery("step", "0", { transform: Number, mode: "push" });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ import WizardPage from "@/components/onboarding/WizardPage.vue";
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import { profileNextRoute } from "@/services/onboarding";
|
import { isOtherOrganisation, profileNextRoute } from "@/services/onboarding";
|
||||||
import { useEntities } from "@/services/entities";
|
import { useEntities } from "@/services/entities";
|
||||||
import AvatarImage from "@/components/ui/AvatarImage.vue";
|
import AvatarImage from "@/components/ui/AvatarImage.vue";
|
||||||
|
|
||||||
|
|
@ -13,9 +13,12 @@ const { t } = useTranslation();
|
||||||
|
|
||||||
const user = useUserStore();
|
const user = useUserStore();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const { organisations } = useEntities();
|
const { organisations } = useEntities();
|
||||||
|
|
||||||
|
const organisationDetailName = ref<string>("");
|
||||||
|
|
||||||
const selectedOrganisation = ref({
|
const selectedOrganisation = ref({
|
||||||
id: 0,
|
id: 0,
|
||||||
name: t("a.Auswählen"),
|
name: t("a.Auswählen"),
|
||||||
|
|
@ -35,7 +38,11 @@ watch(
|
||||||
);
|
);
|
||||||
|
|
||||||
const validOrganisation = computed(() => {
|
const validOrganisation = computed(() => {
|
||||||
return selectedOrganisation.value.id !== 0;
|
const organisationSelected = selectedOrganisation.value.id !== 0;
|
||||||
|
const organisationNameSet =
|
||||||
|
!isOtherOrganisation(selectedOrganisation.value.id) ||
|
||||||
|
!!organisationDetailName.value.trim();
|
||||||
|
return organisationSelected && organisationNameSet;
|
||||||
});
|
});
|
||||||
|
|
||||||
const avatarError = ref(false);
|
const avatarError = ref(false);
|
||||||
|
|
@ -56,15 +63,21 @@ async function avatarUpload(e: Event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(selectedOrganisation, async (organisation) => {
|
async function updateUserProfile() {
|
||||||
await user.updateUserProfile({
|
await user.updateUserProfile({
|
||||||
organisation: organisation.id,
|
organisation: selectedOrganisation.value.id,
|
||||||
|
organisation_detail_name: organisationDetailName.value.trim(),
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
const nextRoute = computed(() => {
|
const nextRoute = computed(() => {
|
||||||
return profileNextRoute(route.params.courseType);
|
return profileNextRoute(route.params.courseType);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function navigateNextRoute() {
|
||||||
|
await updateUserProfile();
|
||||||
|
await router.push({ name: nextRoute.value });
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -86,6 +99,19 @@ const nextRoute = computed(() => {
|
||||||
|
|
||||||
<ItDropdownSelect v-model="selectedOrganisation" :items="organisations" />
|
<ItDropdownSelect v-model="selectedOrganisation" :items="organisations" />
|
||||||
|
|
||||||
|
<div v-if="isOtherOrganisation(selectedOrganisation.id)" class="my-8">
|
||||||
|
<label for="organisationDetailName" class="heading-3 block pb-1.5">
|
||||||
|
{{ $t("a.Firmenname") }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="organisationDetailName"
|
||||||
|
v-model="organisationDetailName"
|
||||||
|
type="text"
|
||||||
|
name="phone"
|
||||||
|
class="block w-full border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 disabled:cursor-not-allowed disabled:text-gray-500 disabled:ring-gray-200 sm:max-w-sm sm:text-sm sm:leading-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-16 flex flex-col justify-between gap-12 lg:flex-row lg:gap-24">
|
<div class="mt-16 flex flex-col justify-between gap-12 lg:flex-row lg:gap-24">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-3">{{ $t("a.Profilbild") }}</h3>
|
<h3 class="mb-3">{{ $t("a.Profilbild") }}</h3>
|
||||||
|
|
@ -117,18 +143,16 @@ const nextRoute = computed(() => {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<router-link v-slot="{ navigate }" :to="{ name: nextRoute }" custom>
|
<button
|
||||||
<button
|
:disabled="!validOrganisation"
|
||||||
:disabled="!validOrganisation"
|
class="btn-blue flex items-center"
|
||||||
class="btn-blue flex items-center"
|
role="link"
|
||||||
role="link"
|
data-cy="continue-button"
|
||||||
data-cy="continue-button"
|
@click="navigateNextRoute"
|
||||||
@click="navigate"
|
>
|
||||||
>
|
{{ $t("general.next") }}
|
||||||
{{ $t("general.next") }}
|
<it-icon-arrow-right class="it-icon ml-2 h-6 w-6" />
|
||||||
<it-icon-arrow-right class="it-icon ml-2 h-6 w-6" />
|
</button>
|
||||||
</button>
|
|
||||||
</router-link>
|
|
||||||
</template>
|
</template>
|
||||||
</WizardPage>
|
</WizardPage>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,12 @@ import DatatransCembraDeviceFingerprint from "@/components/onboarding/DatatransC
|
||||||
import { getLocalSessionKey } from "@/statistics";
|
import { getLocalSessionKey } from "@/statistics";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { normalizeSwissPhoneNumber, validatePhoneNumber } from "@/utils/phone";
|
import { normalizeSwissPhoneNumber, validatePhoneNumber } from "@/utils/phone";
|
||||||
|
import {
|
||||||
|
ORGANISATION_NO_COMPANY_ID,
|
||||||
|
ORGANISATION_OTHER_BROKER_ID,
|
||||||
|
ORGANISATION_OTHER_HEALTH_INSURANCE_ID,
|
||||||
|
ORGANISATION_OTHER_PRIVATE_INSURANCE_ID,
|
||||||
|
} from "@/consts";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
courseType: {
|
courseType: {
|
||||||
|
|
@ -31,11 +37,14 @@ const userOrganisationName = computed(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Those IDs do not represent a company
|
// Those IDs do not represent a company
|
||||||
// 1: Other broker
|
if (
|
||||||
// 2: Other insurance
|
[
|
||||||
// 3: Other private insurance
|
ORGANISATION_OTHER_BROKER_ID,
|
||||||
// 31: No company relation
|
ORGANISATION_OTHER_HEALTH_INSURANCE_ID,
|
||||||
if ([1, 2, 3, 31].includes(user.organisation)) {
|
ORGANISATION_OTHER_PRIVATE_INSURANCE_ID,
|
||||||
|
ORGANISATION_NO_COMPANY_ID,
|
||||||
|
].includes(user.organisation)
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,11 +265,10 @@ const executePayment = async () => {
|
||||||
|
|
||||||
<p v-if="paymentError" class="text-bold mt-12 text-lg text-red-700">
|
<p v-if="paymentError" class="text-bold mt-12 text-lg text-red-700">
|
||||||
{{
|
{{
|
||||||
$t("a.Fehler bei der Zahlung. Bitte versuche es erneut oder kontaktiere uns")
|
$t(
|
||||||
}}:
|
"a.Fehler bei der Zahlung. Bitte versuche es erneut oder wähle eine andere Zahlungsmethode."
|
||||||
<a href="mailto:vermittler@vbv-afa.ch" class="underline">
|
)
|
||||||
vermittler@vbv-afa.ch
|
}}
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mb-4 mt-10">{{ $t("a.Adresse") }}</h3>
|
<h3 class="mb-4 mt-10">{{ $t("a.Adresse") }}</h3>
|
||||||
|
|
@ -285,7 +293,7 @@ const executePayment = async () => {
|
||||||
{{ formErrors.personal.join(", ") }}
|
{{ formErrors.personal.join(", ") }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<section v-if="address.payment_method !== 'cembra_byjuno'">
|
<section>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<button
|
<button
|
||||||
v-if="!withCompanyAddress"
|
v-if="!withCompanyAddress"
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,11 @@ function startEditMode() {
|
||||||
|
|
||||||
<div class="flex flex-grow flex-col space-y-4 px-8 py-8 md:px-16">
|
<div class="flex flex-grow flex-col space-y-4 px-8 py-8 md:px-16">
|
||||||
<div v-if="!editMode" class="flex justify-end space-x-4">
|
<div v-if="!editMode" class="flex justify-end space-x-4">
|
||||||
<button class="btn btn-secondary" @click="startEditMode">
|
<button
|
||||||
|
class="btn btn-secondary"
|
||||||
|
data-cy="editProfileButton"
|
||||||
|
@click="startEditMode"
|
||||||
|
>
|
||||||
{{ $t("a.Profil bearbeiten") }}
|
{{ $t("a.Profil bearbeiten") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,17 @@ onMounted(() => {
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<h2 class="mb-2">{{ user.first_name }} {{ user.last_name }}</h2>
|
<h2 class="mb-2">{{ user.first_name }} {{ user.last_name }}</h2>
|
||||||
<p class="mb-2">{{ user.email }}</p>
|
<p class="mb-2">{{ user.email }}</p>
|
||||||
<p class="text-gray-800">{{ $t("a.Teilnehmer") }}</p>
|
<p class="text-gray-800">
|
||||||
|
{{ $t("a.Teilnehmer") }}
|
||||||
|
<span
|
||||||
|
v-if="
|
||||||
|
user.optional_attendance.some((id: string) => id === courseSession.id)
|
||||||
|
"
|
||||||
|
data-cy="optional-attendance"
|
||||||
|
>
|
||||||
|
{{ $t("a.Optionale Anwesenheit") }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,15 @@ export async function exportCompetenceElements(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function exportPersons(
|
||||||
|
data: XlsExportRequestData,
|
||||||
|
language: string
|
||||||
|
): Promise<XlsExportResponseData> {
|
||||||
|
return await itPost("/api/dashboard/export/persons/", data, {
|
||||||
|
headers: { "Accept-Language": language },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function courseIdForCourseSlug(
|
export function courseIdForCourseSlug(
|
||||||
dashboardConfigs: DashboardCourseConfigType[],
|
dashboardConfigs: DashboardCourseConfigType[],
|
||||||
courseSlug: string
|
courseSlug: string
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
import { isString, startsWith } from "lodash";
|
import { isString, startsWith } from "lodash";
|
||||||
|
import {
|
||||||
|
ORGANISATION_OTHER_BROKER_ID,
|
||||||
|
ORGANISATION_OTHER_HEALTH_INSURANCE_ID,
|
||||||
|
ORGANISATION_OTHER_PRIVATE_INSURANCE_ID,
|
||||||
|
} from "@/consts";
|
||||||
|
|
||||||
export function profileNextRoute(courseType: string | string[]) {
|
export function profileNextRoute(courseType: string | string[]) {
|
||||||
if (courseType === "uk") {
|
if (courseType === "uk") {
|
||||||
|
|
@ -10,3 +15,11 @@ export function profileNextRoute(courseType: string | string[]) {
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isOtherOrganisation(orgId: number) {
|
||||||
|
return [
|
||||||
|
ORGANISATION_OTHER_BROKER_ID,
|
||||||
|
ORGANISATION_OTHER_HEALTH_INSURANCE_ID,
|
||||||
|
ORGANISATION_OTHER_PRIVATE_INSURANCE_ID,
|
||||||
|
].includes(orgId);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,22 +17,31 @@ describe("checkout.cy.js", () => {
|
||||||
|
|
||||||
cy.get('[data-cy="account-confirm-title"]').should(
|
cy.get('[data-cy="account-confirm-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Konto erstellen"
|
"Konto erstellen",
|
||||||
);
|
);
|
||||||
cy.get('[data-cy="continue-button"]').click();
|
cy.get('[data-cy="continue-button"]').click();
|
||||||
|
|
||||||
cy.get('[data-cy="account-profile-title"]').should(
|
cy.get('[data-cy="account-profile-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Profil ergänzen"
|
"Profil ergänzen",
|
||||||
);
|
);
|
||||||
cy.get('[data-cy="dropdown-select"]').click();
|
cy.get('[data-cy="dropdown-select"]').click();
|
||||||
cy.get('[data-cy="dropdown-select-option-Baloise"]').click();
|
cy.get(
|
||||||
|
'[data-cy="dropdown-select-option-andere Krankenversicherer"]',
|
||||||
|
).click();
|
||||||
|
cy.get("#organisationDetailName").type("FdH GmbH");
|
||||||
cy.get('[data-cy="continue-button"]').click();
|
cy.get('[data-cy="continue-button"]').click();
|
||||||
|
|
||||||
|
cy.loadUser("id", TEST_USER_EMPTY_ID).then((u) => {
|
||||||
|
expect(u.organisation_detail_name).to.equal("FdH GmbH");
|
||||||
|
// 2 -> andere Krankenversicherer
|
||||||
|
expect(u.organisation).to.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
// Adressdaten ausfüllen
|
// Adressdaten ausfüllen
|
||||||
cy.get('[data-cy="account-checkout-title"]').should(
|
cy.get('[data-cy="account-checkout-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Lehrgang kaufen"
|
"Lehrgang kaufen",
|
||||||
);
|
);
|
||||||
cy.get("#street-address").type("Eggersmatt");
|
cy.get("#street-address").type("Eggersmatt");
|
||||||
cy.get("#street-number").type("32");
|
cy.get("#street-number").type("32");
|
||||||
|
|
@ -40,7 +49,7 @@ describe("checkout.cy.js", () => {
|
||||||
cy.get("#city").type("Zumholz");
|
cy.get("#city").type("Zumholz");
|
||||||
cy.get('[data-cy="add-company-address"]').click();
|
cy.get('[data-cy="add-company-address"]').click();
|
||||||
|
|
||||||
cy.get("#company-name").type("Iterativ GmbH");
|
// cy.get("#company-name").clear().type("Iterativ GmbH");
|
||||||
cy.get("#company-street-address").type("Brückfeldstrasse");
|
cy.get("#company-street-address").type("Brückfeldstrasse");
|
||||||
cy.get("#company-street-number").type("16");
|
cy.get("#company-street-number").type("16");
|
||||||
cy.get("#company-postal-code").type("3012");
|
cy.get("#company-postal-code").type("3012");
|
||||||
|
|
@ -60,7 +69,7 @@ describe("checkout.cy.js", () => {
|
||||||
expect(ci.country).to.equal("CH");
|
expect(ci.country).to.equal("CH");
|
||||||
|
|
||||||
expect(ci.invoice_address).to.equal("org");
|
expect(ci.invoice_address).to.equal("org");
|
||||||
expect(ci.organisation_detail_name).to.equal("Iterativ GmbH");
|
expect(ci.organisation_detail_name).to.equal("FdH GmbH");
|
||||||
expect(ci.organisation_street).to.equal("Brückfeldstrasse");
|
expect(ci.organisation_street).to.equal("Brückfeldstrasse");
|
||||||
expect(ci.organisation_street_number).to.equal("16");
|
expect(ci.organisation_street_number).to.equal("16");
|
||||||
expect(ci.organisation_postal_code).to.equal("3012");
|
expect(ci.organisation_postal_code).to.equal("3012");
|
||||||
|
|
@ -72,12 +81,32 @@ describe("checkout.cy.js", () => {
|
||||||
expect(ci.state).to.equal("ongoing");
|
expect(ci.state).to.equal("ongoing");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cy.loadUser("id", TEST_USER_EMPTY_ID).then((u) => {
|
||||||
|
expect(u.first_name).to.equal("Flasche");
|
||||||
|
expect(u.last_name).to.equal("Leer");
|
||||||
|
|
||||||
|
expect(u.street).to.equal("Eggersmatt");
|
||||||
|
expect(u.street_number).to.equal("32");
|
||||||
|
expect(u.postal_code).to.equal("1719");
|
||||||
|
expect(u.city).to.equal("Zumholz");
|
||||||
|
expect(u.country).to.equal("CH");
|
||||||
|
|
||||||
|
expect(u.invoice_address).to.equal("org");
|
||||||
|
expect(u.organisation_detail_name).to.equal("FdH GmbH");
|
||||||
|
expect(u.organisation_street).to.equal("Brückfeldstrasse");
|
||||||
|
expect(u.organisation_street_number).to.equal("16");
|
||||||
|
expect(u.organisation_postal_code).to.equal("3012");
|
||||||
|
expect(u.organisation_city).to.equal("Bern");
|
||||||
|
// 2 -> andere Krankenversicherer
|
||||||
|
expect(u.organisation).to.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
// pay
|
// pay
|
||||||
cy.get('[data-cy="pay-button"]').click();
|
cy.get('[data-cy="pay-button"]').click();
|
||||||
|
|
||||||
cy.get('[data-cy="checkout-success-title"]').should(
|
cy.get('[data-cy="checkout-success-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Gratuliere"
|
"Gratuliere",
|
||||||
);
|
);
|
||||||
// wait for payment callback
|
// wait for payment callback
|
||||||
cy.wait(3000);
|
cy.wait(3000);
|
||||||
|
|
@ -86,7 +115,7 @@ describe("checkout.cy.js", () => {
|
||||||
// back on dashboard page
|
// back on dashboard page
|
||||||
cy.get('[data-cy="db-course-title"]').should(
|
cy.get('[data-cy="db-course-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Versicherungsvermittler"
|
"Versicherungsvermittler",
|
||||||
);
|
);
|
||||||
|
|
||||||
cy.loadCheckoutInformation("user_id", TEST_USER_EMPTY_ID).then((ci) => {
|
cy.loadCheckoutInformation("user_id", TEST_USER_EMPTY_ID).then((ci) => {
|
||||||
|
|
@ -102,13 +131,13 @@ describe("checkout.cy.js", () => {
|
||||||
|
|
||||||
cy.get('[data-cy="account-confirm-title"]').should(
|
cy.get('[data-cy="account-confirm-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Konto erstellen"
|
"Konto erstellen",
|
||||||
);
|
);
|
||||||
cy.get('[data-cy="continue-button"]').click();
|
cy.get('[data-cy="continue-button"]').click();
|
||||||
|
|
||||||
cy.get('[data-cy="account-profile-title"]').should(
|
cy.get('[data-cy="account-profile-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Profil ergänzen"
|
"Profil ergänzen",
|
||||||
);
|
);
|
||||||
cy.get('[data-cy="dropdown-select"]').click();
|
cy.get('[data-cy="dropdown-select"]').click();
|
||||||
cy.get('[data-cy="dropdown-select-option-Baloise"]').click();
|
cy.get('[data-cy="dropdown-select-option-Baloise"]').click();
|
||||||
|
|
@ -117,10 +146,10 @@ describe("checkout.cy.js", () => {
|
||||||
// Adressdaten ausfüllen
|
// Adressdaten ausfüllen
|
||||||
cy.get('[data-cy="account-checkout-title"]').should(
|
cy.get('[data-cy="account-checkout-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"Lehrgang kaufen"
|
"Lehrgang kaufen",
|
||||||
);
|
);
|
||||||
|
|
||||||
cy.get('#paymentMethod').select('cembra_byjuno');
|
cy.get("#paymentMethod").select("cembra_byjuno");
|
||||||
|
|
||||||
cy.get("#street-address").type("Eggersmatt");
|
cy.get("#street-address").type("Eggersmatt");
|
||||||
cy.get("#street-number").type("32");
|
cy.get("#street-number").type("32");
|
||||||
|
|
@ -132,32 +161,38 @@ describe("checkout.cy.js", () => {
|
||||||
|
|
||||||
cy.get('[data-cy="continue-pay"]').click();
|
cy.get('[data-cy="continue-pay"]').click();
|
||||||
|
|
||||||
cy.loadExternalApiRequestLog("request_username", "empty@example.com").then((entry) => {
|
cy.loadExternalApiRequestLog("request_username", "empty@example.com").then(
|
||||||
// ends with "/v1/transactions""
|
(entry) => {
|
||||||
expect(entry.api_url).to.contain("/v1/transactions");
|
// ends with "/v1/transactions""
|
||||||
expect(entry.request_username).to.contain("empty@example.com");
|
expect(entry.api_url).to.contain("/v1/transactions");
|
||||||
|
expect(entry.request_username).to.contain("empty@example.com");
|
||||||
|
|
||||||
expect(entry.api_request_data.amount).to.equal(32400);
|
expect(entry.api_request_data.amount).to.equal(32400);
|
||||||
expect(entry.api_request_data.currency).to.equal("CHF");
|
expect(entry.api_request_data.currency).to.equal("CHF");
|
||||||
expect(entry.api_request_data.autoSettle).to.equal(true);
|
expect(entry.api_request_data.autoSettle).to.equal(true);
|
||||||
|
|
||||||
expect(entry.api_request_data.customer.firstName).to.equal("Flasche");
|
expect(entry.api_request_data.customer.firstName).to.equal("Flasche");
|
||||||
expect(entry.api_request_data.customer.lastName).to.equal("Leer");
|
expect(entry.api_request_data.customer.lastName).to.equal("Leer");
|
||||||
expect(entry.api_request_data.customer.street).to.equal("Eggersmatt 32");
|
expect(entry.api_request_data.customer.street).to.equal(
|
||||||
expect(entry.api_request_data.customer.zipCode).to.equal("1719");
|
"Eggersmatt 32",
|
||||||
expect(entry.api_request_data.customer.city).to.equal("Zumholz");
|
);
|
||||||
expect(entry.api_request_data.customer.country).to.equal("CH");
|
expect(entry.api_request_data.customer.zipCode).to.equal("1719");
|
||||||
expect(entry.api_request_data.customer.type).to.equal("P");
|
expect(entry.api_request_data.customer.city).to.equal("Zumholz");
|
||||||
expect(entry.api_request_data.customer.phone).to.equal("+41792018586");
|
expect(entry.api_request_data.customer.country).to.equal("CH");
|
||||||
expect(entry.api_request_data.customer.birthDate).to.equal("1982-06-09");
|
expect(entry.api_request_data.customer.type).to.equal("P");
|
||||||
|
expect(entry.api_request_data.customer.phone).to.equal("+41792018586");
|
||||||
|
expect(entry.api_request_data.customer.birthDate).to.equal(
|
||||||
|
"1982-06-09",
|
||||||
|
);
|
||||||
|
|
||||||
expect(entry.api_request_data.INT.repaymentType).to.equal(3);
|
expect(entry.api_request_data.INT.repaymentType).to.equal(3);
|
||||||
expect(entry.api_request_data.INT.riskOwner).to.equal("IJ");
|
expect(entry.api_request_data.INT.riskOwner).to.equal("IJ");
|
||||||
expect(entry.api_request_data.INT.subtype).to.equal("INVOICE");
|
expect(entry.api_request_data.INT.subtype).to.equal("INVOICE");
|
||||||
expect(entry.api_request_data.INT.deviceFingerprintId).to.not.be.empty;
|
expect(entry.api_request_data.INT.deviceFingerprintId).to.not.be.empty;
|
||||||
|
|
||||||
expect(true).to.be.true;
|
expect(true).to.be.true;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// check that results are stored on server
|
// check that results are stored on server
|
||||||
cy.loadCheckoutInformation("user_id", TEST_USER_EMPTY_ID).then((ci) => {
|
cy.loadCheckoutInformation("user_id", TEST_USER_EMPTY_ID).then((ci) => {
|
||||||
|
|
@ -183,5 +218,23 @@ describe("checkout.cy.js", () => {
|
||||||
expect(ci.ip_address).to.not.be.empty;
|
expect(ci.ip_address).to.not.be.empty;
|
||||||
expect(ci.device_fingerprint_session_key).to.not.be.empty;
|
expect(ci.device_fingerprint_session_key).to.not.be.empty;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cy.loadUser("id", TEST_USER_EMPTY_ID).then((u) => {
|
||||||
|
expect(u.first_name).to.equal("Flasche");
|
||||||
|
expect(u.last_name).to.equal("Leer");
|
||||||
|
|
||||||
|
expect(u.street).to.equal("Eggersmatt");
|
||||||
|
expect(u.street_number).to.equal("32");
|
||||||
|
expect(u.postal_code).to.equal("1719");
|
||||||
|
expect(u.city).to.equal("Zumholz");
|
||||||
|
expect(u.country).to.equal("CH");
|
||||||
|
expect(u.phone_number).to.equal("+41792018586");
|
||||||
|
expect(u.birth_date).to.equal("1982-06-09");
|
||||||
|
|
||||||
|
expect(u.invoice_address).to.equal("prv");
|
||||||
|
|
||||||
|
// 7 -> Baloise
|
||||||
|
expect(u.organisation).to.equal(7);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { login } from "../helpers";
|
||||||
|
|
||||||
|
describe("cockpitAttendaceCheck.cy.js", () => {
|
||||||
|
it("will display optional participants", () => {
|
||||||
|
cy.manageCommand("cypress_reset --set-optional-attendance-flag");
|
||||||
|
login("test-trainer1@example.com", "test");
|
||||||
|
cy.visit("/course/test-lehrgang/cockpit/attendance");
|
||||||
|
|
||||||
|
cy.get('[data-cy="extra-info"]').should("contain", "Optionale Anwesenheit");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -26,7 +26,7 @@ function getCurrentDate() {
|
||||||
function verifyExportFileExists(fileName) {
|
function verifyExportFileExists(fileName) {
|
||||||
const downloadsFolder = Cypress.config("downloadsFolder");
|
const downloadsFolder = Cypress.config("downloadsFolder");
|
||||||
cy.readFile(
|
cy.readFile(
|
||||||
path.join(downloadsFolder, `${fileName}_${getCurrentDate()}.xlsx`)
|
path.join(downloadsFolder, `${fileName}_${getCurrentDate()}.xlsx`),
|
||||||
).should("exist");
|
).should("exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ function testExport(url, fileName) {
|
||||||
describe("dashboardExport.cy.js", () => {
|
describe("dashboardExport.cy.js", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.manageCommand(
|
cy.manageCommand(
|
||||||
"cypress_reset --create-assignment-evaluation --create-feedback-responses --create-course-completion-performance-criteria --create-attendance-days"
|
"cypress_reset --create-assignment-evaluation --create-feedback-responses --create-course-completion-performance-criteria --create-attendance-days",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -55,13 +55,17 @@ describe("dashboardExport.cy.js", () => {
|
||||||
it("should download the competence elements export", () => {
|
it("should download the competence elements export", () => {
|
||||||
testExport(
|
testExport(
|
||||||
"/statistic/test-lehrgang/assignment",
|
"/statistic/test-lehrgang/assignment",
|
||||||
"export_kompetenznachweis_elemente"
|
"export_kompetenznachweis_elemente",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should download the feedback export", () => {
|
it("should download the feedback export", () => {
|
||||||
testExport("/statistic/test-lehrgang/feedback", "export_feedback");
|
testExport("/statistic/test-lehrgang/feedback", "export_feedback");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should download the person export", () => {
|
||||||
|
testExport("/dashboard/persons", "export_personen");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("as trainer", () => {
|
describe("as trainer", () => {
|
||||||
|
|
@ -76,12 +80,16 @@ describe("dashboardExport.cy.js", () => {
|
||||||
it("should download the competence elements export", () => {
|
it("should download the competence elements export", () => {
|
||||||
testExport(
|
testExport(
|
||||||
"/statistic/test-lehrgang/assignment",
|
"/statistic/test-lehrgang/assignment",
|
||||||
"export_kompetenznachweis_elemente"
|
"export_kompetenznachweis_elemente",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should download the feedback export", () => {
|
it("should download the feedback export", () => {
|
||||||
testExport("/statistic/test-lehrgang/feedback", "export_feedback");
|
testExport("/statistic/test-lehrgang/feedback", "export_feedback");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should download the person export", () => {
|
||||||
|
testExport("/dashboard/persons", "export_personen");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { TEST_USER_EMPTY_ID } from "../../consts";
|
||||||
|
import { login } from "../helpers";
|
||||||
|
|
||||||
|
describe("personalProfile.cy.js", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.manageCommand("cypress_reset");
|
||||||
|
|
||||||
|
login("empty@example.com", "test");
|
||||||
|
cy.visit("/profile");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can edit all profile fields", () => {
|
||||||
|
cy.get('[data-cy="editProfileButton"]').click();
|
||||||
|
|
||||||
|
cy.get("#phone").type("079 201 85 86");
|
||||||
|
cy.get('[data-test="dp-input"]').type("09.06.1982{enter}");
|
||||||
|
|
||||||
|
cy.get("#street-address").type("Hafen");
|
||||||
|
cy.get("#street-number").type("123");
|
||||||
|
cy.get("#postal-code").type("DE-20095");
|
||||||
|
cy.get("#city").type("Hamburg");
|
||||||
|
cy.get("#country").select("DE");
|
||||||
|
|
||||||
|
// andere broker
|
||||||
|
cy.get("#organisation").select("1");
|
||||||
|
|
||||||
|
cy.get("#org-detail-name").type("Judihui GmbH");
|
||||||
|
cy.get("#org-street-address").type("Auf der Alm");
|
||||||
|
cy.get("#org-street-number").type("17");
|
||||||
|
cy.get("#org-postal-code").type("AT-6020");
|
||||||
|
cy.get("#org-city").type("Innsbruck");
|
||||||
|
cy.get("#org-country").select("AT");
|
||||||
|
|
||||||
|
cy.get("#invoice-address-organisation").click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="saveButton"]').click();
|
||||||
|
|
||||||
|
// check displayed data
|
||||||
|
cy.get('[data-cy="firstName"]').should("contain", "Flasche");
|
||||||
|
cy.get('[data-cy="lastName"]').should("contain", "Leer");
|
||||||
|
cy.get('[data-cy="email"]').should("contain", "empty@example.com");
|
||||||
|
cy.get('[data-cy="phone"]').should("contain", "079 201 85 86");
|
||||||
|
cy.get('[data-cy="birthDate"]').should("contain", "09.06.1982");
|
||||||
|
|
||||||
|
cy.get('[data-cy="privateAddress"]').should("contain", "Hafen 123");
|
||||||
|
cy.get('[data-cy="privateAddress"]').should("contain", "DE-20095 Hamburg");
|
||||||
|
cy.get('[data-cy="privateAddress"]').should("contain", "Deutschland");
|
||||||
|
|
||||||
|
cy.get('[data-cy="organisationDetailName"]').should(
|
||||||
|
"contain",
|
||||||
|
"andere Broker",
|
||||||
|
);
|
||||||
|
cy.get('[data-cy="organisationAddress"]').should("contain", "Judihui GmbH");
|
||||||
|
cy.get('[data-cy="organisationAddress"]').should(
|
||||||
|
"contain",
|
||||||
|
"Auf der Alm 17",
|
||||||
|
);
|
||||||
|
cy.get('[data-cy="organisationAddress"]').should(
|
||||||
|
"contain",
|
||||||
|
"AT-6020 Innsbruck",
|
||||||
|
);
|
||||||
|
cy.get('[data-cy="organisationAddress"]').should("contain", "Österreich");
|
||||||
|
|
||||||
|
// check stored data
|
||||||
|
cy.loadUser("id", TEST_USER_EMPTY_ID).then((u) => {
|
||||||
|
expect(u.first_name).to.equal("Flasche");
|
||||||
|
expect(u.last_name).to.equal("Leer");
|
||||||
|
|
||||||
|
expect(u.street).to.equal("Hafen");
|
||||||
|
expect(u.street_number).to.equal("123");
|
||||||
|
expect(u.postal_code).to.equal("DE-20095");
|
||||||
|
expect(u.city).to.equal("Hamburg");
|
||||||
|
expect(u.country).to.equal("DE");
|
||||||
|
|
||||||
|
expect(u.invoice_address).to.equal("org");
|
||||||
|
expect(u.organisation_detail_name).to.equal("Judihui GmbH");
|
||||||
|
expect(u.organisation_street).to.equal("Auf der Alm");
|
||||||
|
expect(u.organisation_street_number).to.equal("17");
|
||||||
|
expect(u.organisation_postal_code).to.equal("AT-6020");
|
||||||
|
expect(u.organisation_city).to.equal("Innsbruck");
|
||||||
|
// 1 -> andere Broker
|
||||||
|
expect(u.organisation).to.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { login } from "../helpers";
|
||||||
|
import { TEST_STUDENT1_USER_ID } from "../../consts";
|
||||||
|
|
||||||
|
describe("publicProfileAttendance.cy.js", () => {
|
||||||
|
it("will display optional attendance", () => {
|
||||||
|
cy.manageCommand("cypress_reset --set-optional-attendance-flag");
|
||||||
|
login("test-trainer1@example.com", "test");
|
||||||
|
cy.visit(
|
||||||
|
`course/test-lehrgang/profile/${TEST_STUDENT1_USER_ID}/learning-path`,
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.get('[data-cy="optional-attendance"]').should(
|
||||||
|
"contain",
|
||||||
|
"Optionale Anwesenheit",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -178,6 +178,17 @@ Cypress.Commands.add("loadCheckoutInformation", (key, value) => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add("loadUser", (key, value) => {
|
||||||
|
return loadObjectJson(
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
"vbv_lernwelt.core.models.User",
|
||||||
|
"vbv_lernwelt.core.serializers.CypressUserSerializer",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add("makeSelfEvaluation", (answers) => {
|
Cypress.Commands.add("makeSelfEvaluation", (answers) => {
|
||||||
for (let i = 0; i < answers.length; i++) {
|
for (let i = 0; i < answers.length; i++) {
|
||||||
const answer = answers[i]
|
const answer = answers[i]
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -201,6 +201,7 @@ MIDDLEWARE = [
|
||||||
"vbv_lernwelt.core.middleware.security.SecurityRequestResponseLoggingMiddleware",
|
"vbv_lernwelt.core.middleware.security.SecurityRequestResponseLoggingMiddleware",
|
||||||
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
|
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
|
||||||
"vbv_lernwelt.core.middleware.auth.UserLoggedInCookieMiddleWare",
|
"vbv_lernwelt.core.middleware.auth.UserLoggedInCookieMiddleWare",
|
||||||
|
# "vbv_lernwelt.debugtools.middleware.QueryCountDebugMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
# STATIC
|
# STATIC
|
||||||
|
|
@ -332,7 +333,6 @@ X_FRAME_OPTIONS = "DENY"
|
||||||
# EMAIL
|
# EMAIL
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
||||||
# FIXME how to send emails?
|
|
||||||
EMAIL_BACKEND = env(
|
EMAIL_BACKEND = env(
|
||||||
"DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend"
|
"DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend"
|
||||||
)
|
)
|
||||||
|
|
@ -682,10 +682,12 @@ if APP_ENVIRONMENT.startswith("prod"):
|
||||||
DATATRANS_PAY_URL = "https://pay.datatrans.com"
|
DATATRANS_PAY_URL = "https://pay.datatrans.com"
|
||||||
else:
|
else:
|
||||||
DATATRANS_API_ENDPOINT = env(
|
DATATRANS_API_ENDPOINT = env(
|
||||||
"DATATRANS_API_ENDPOINT", default="https://api.sandbox.datatrans.com"
|
"DATATRANS_API_ENDPOINT",
|
||||||
|
default="http://localhost:8000/server/fakeapi/datatrans/api",
|
||||||
)
|
)
|
||||||
DATATRANS_PAY_URL = env(
|
DATATRANS_PAY_URL = env(
|
||||||
"DATATRANS_PAY_URL", default="https://pay.sandbox.datatrans.com"
|
"DATATRANS_PAY_URL",
|
||||||
|
default="http://localhost:8000/server/fakeapi/datatrans/pay",
|
||||||
)
|
)
|
||||||
|
|
||||||
# default settings for python sftpserver test-server
|
# default settings for python sftpserver test-server
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ from vbv_lernwelt.dashboard.views import (
|
||||||
export_attendance_as_xsl,
|
export_attendance_as_xsl,
|
||||||
export_competence_elements_as_xsl,
|
export_competence_elements_as_xsl,
|
||||||
export_feedback_as_xsl,
|
export_feedback_as_xsl,
|
||||||
|
export_persons_as_xsl,
|
||||||
get_dashboard_config,
|
get_dashboard_config,
|
||||||
get_dashboard_due_dates,
|
get_dashboard_due_dates,
|
||||||
get_dashboard_persons,
|
get_dashboard_persons,
|
||||||
|
|
@ -143,6 +144,7 @@ urlpatterns = [
|
||||||
path(r"api/dashboard/export/competence_elements/", export_competence_elements_as_xsl,
|
path(r"api/dashboard/export/competence_elements/", export_competence_elements_as_xsl,
|
||||||
name="export_certificate_as_xsl"),
|
name="export_certificate_as_xsl"),
|
||||||
path(r"api/dashboard/export/feedback/", export_feedback_as_xsl, name="export_feedback_as_xsl"),
|
path(r"api/dashboard/export/feedback/", export_feedback_as_xsl, name="export_feedback_as_xsl"),
|
||||||
|
path(r"api/dashboard/export/persons/", export_persons_as_xsl, name="export_persons_as_xsl"),
|
||||||
|
|
||||||
# course
|
# course
|
||||||
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
|
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-06-18 15:24+0200\n"
|
"POT-Creation-Date: 2024-07-27 20:59+0200\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
|
@ -38,32 +38,32 @@ msgstr ""
|
||||||
msgid "Nicht bestanden"
|
msgid "Nicht bestanden"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/assignment/export.py:203 vbv_lernwelt/assignment/export.py:206
|
#: vbv_lernwelt/assignment/export.py:204 vbv_lernwelt/assignment/export.py:208
|
||||||
#: vbv_lernwelt/assignment/export.py:207
|
#: vbv_lernwelt/assignment/export.py:209
|
||||||
msgid "Keine Daten"
|
msgid "Keine Daten"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/core/admin.py:32
|
#: vbv_lernwelt/core/admin.py:38 vbv_lernwelt/sso/admin.py:83
|
||||||
msgid "Personal info"
|
msgid "Personal info"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/core/admin.py:34
|
#: vbv_lernwelt/core/admin.py:40
|
||||||
msgid "Permissions"
|
msgid "Permissions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/core/admin.py:45
|
#: vbv_lernwelt/core/admin.py:51
|
||||||
msgid "Important dates"
|
msgid "Important dates"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/core/admin.py:47
|
#: vbv_lernwelt/core/admin.py:53
|
||||||
msgid "Profile"
|
msgid "Profile"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/core/admin.py:62
|
#: vbv_lernwelt/core/admin.py:70
|
||||||
msgid "Organisation"
|
msgid "Organisation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/core/admin.py:75
|
#: vbv_lernwelt/core/admin.py:83 vbv_lernwelt/sso/admin.py:86
|
||||||
msgid "Additional data"
|
msgid "Additional data"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -87,31 +87,31 @@ msgstr ""
|
||||||
msgid "Lehrgang-Seite"
|
msgid "Lehrgang-Seite"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:272
|
#: vbv_lernwelt/dashboard/person_export.py:116
|
||||||
msgid "Teilnehmer"
|
msgid "Teilnehmer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:273
|
#: vbv_lernwelt/course/models.py:279
|
||||||
msgid "Experte/Trainer"
|
msgid "Experte/Trainer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:332
|
#: vbv_lernwelt/course/models.py:339
|
||||||
msgid "Dokumente im Circle ein/aus"
|
msgid "Dokumente im Circle ein/aus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:336
|
#: vbv_lernwelt/course/models.py:343
|
||||||
msgid "Lernmentor-Funktion ein/aus"
|
msgid "Lernmentor-Funktion ein/aus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:340
|
#: vbv_lernwelt/course/models.py:347
|
||||||
msgid "Kompetenzweise ein/aus"
|
msgid "Kompetenzweise ein/aus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:343
|
#: vbv_lernwelt/course/models.py:350
|
||||||
msgid "Versicherungsvermittler-Lehrgang"
|
msgid "Versicherungsvermittler-Lehrgang"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:344
|
#: vbv_lernwelt/course/models.py:350
|
||||||
msgid "ÜK-Lehrgang"
|
msgid "ÜK-Lehrgang"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -119,34 +119,66 @@ msgstr ""
|
||||||
msgid "export_anwesenheit"
|
msgid "export_anwesenheit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:92
|
#: vbv_lernwelt/course_session/services/export_attendance.py:86
|
||||||
|
msgid "Optionale Anwesenheit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: vbv_lernwelt/course_session/services/export_attendance.py:96
|
||||||
msgid "Anwesenheit"
|
msgid "Anwesenheit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:116
|
#: vbv_lernwelt/course_session/services/export_attendance.py:119
|
||||||
|
msgid "Ja"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: vbv_lernwelt/course_session/services/export_attendance.py:119
|
||||||
|
msgid "Nein"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: vbv_lernwelt/course_session/services/export_attendance.py:128
|
||||||
msgid "Anwesend"
|
msgid "Anwesend"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:116
|
#: vbv_lernwelt/course_session/services/export_attendance.py:128
|
||||||
msgid "Nicht anwesend"
|
msgid "Nicht anwesend"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:123
|
#: vbv_lernwelt/course_session/services/export_attendance.py:135
|
||||||
msgid "Vorname"
|
msgid "Vorname"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:124
|
#: vbv_lernwelt/course_session/services/export_attendance.py:136
|
||||||
msgid "Nachname"
|
msgid "Nachname"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:125
|
#: vbv_lernwelt/course_session/services/export_attendance.py:137
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Email"
|
msgstr "Email"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:126
|
#: vbv_lernwelt/course_session/services/export_attendance.py:127
|
||||||
msgid "Lehrvertragsnummer"
|
msgid "Lehrvertragsnummer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:16
|
||||||
|
msgid "export_personen"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:68
|
||||||
|
msgid "Telefon"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:69
|
||||||
|
msgid "Rolle"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:118
|
||||||
|
msgid "Trainer"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:120
|
||||||
|
msgid "Regionenleiter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/feedback/export.py:19
|
#: vbv_lernwelt/feedback/export.py:19
|
||||||
msgid "export_feedback"
|
msgid "export_feedback"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-07-12 09:37+0200\n"
|
"POT-Creation-Date: 2024-07-30 11:16+0200\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
|
@ -88,30 +88,31 @@ msgid "Lehrgang-Seite"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:278
|
#: vbv_lernwelt/course/models.py:278
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:116
|
||||||
msgid "Teilnehmer"
|
msgid "Teilnehmer"
|
||||||
msgstr ""
|
msgstr "Participant"
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:279
|
#: vbv_lernwelt/course/models.py:279
|
||||||
msgid "Experte/Trainer"
|
msgid "Experte/Trainer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:338
|
#: vbv_lernwelt/course/models.py:339
|
||||||
msgid "Dokumente im Circle ein/aus"
|
msgid "Dokumente im Circle ein/aus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:342
|
#: vbv_lernwelt/course/models.py:343
|
||||||
msgid "Lernmentor-Funktion ein/aus"
|
msgid "Lernmentor-Funktion ein/aus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:346
|
#: vbv_lernwelt/course/models.py:347
|
||||||
msgid "Kompetenzweise ein/aus"
|
msgid "Kompetenzweise ein/aus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:349
|
#: vbv_lernwelt/course/models.py:350
|
||||||
msgid "Versicherungsvermittler-Lehrgang"
|
msgid "Versicherungsvermittler-Lehrgang"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:350
|
#: vbv_lernwelt/course/models.py:351
|
||||||
msgid "ÜK-Lehrgang"
|
msgid "ÜK-Lehrgang"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -119,34 +120,66 @@ msgstr ""
|
||||||
msgid "export_anwesenheit"
|
msgid "export_anwesenheit"
|
||||||
msgstr "export_presence"
|
msgstr "export_presence"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:92
|
#: vbv_lernwelt/course_session/services/export_attendance.py:86
|
||||||
|
msgid "Optionale Anwesenheit"
|
||||||
|
msgstr "Présence facultative"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/course_session/services/export_attendance.py:96
|
||||||
msgid "Anwesenheit"
|
msgid "Anwesenheit"
|
||||||
msgstr "Présence"
|
msgstr "Présence"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:117
|
#: vbv_lernwelt/course_session/services/export_attendance.py:119
|
||||||
|
msgid "Ja"
|
||||||
|
msgstr "Oui"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/course_session/services/export_attendance.py:119
|
||||||
|
msgid "Nein"
|
||||||
|
msgstr "Non"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/course_session/services/export_attendance.py:128
|
||||||
msgid "Anwesend"
|
msgid "Anwesend"
|
||||||
msgstr "Présent"
|
msgstr "Présent"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:117
|
#: vbv_lernwelt/course_session/services/export_attendance.py:128
|
||||||
msgid "Nicht anwesend"
|
msgid "Nicht anwesend"
|
||||||
msgstr "Pas présent"
|
msgstr "Pas présent"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:124
|
#: vbv_lernwelt/course_session/services/export_attendance.py:135
|
||||||
msgid "Vorname"
|
msgid "Vorname"
|
||||||
msgstr "Prénom"
|
msgstr "Prénom"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:125
|
#: vbv_lernwelt/course_session/services/export_attendance.py:136
|
||||||
msgid "Nachname"
|
msgid "Nachname"
|
||||||
msgstr "Nom de famille"
|
msgstr "Nom de famille"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:126
|
#: vbv_lernwelt/course_session/services/export_attendance.py:137
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "E-mail"
|
msgstr "E-mail"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:127
|
#: vbv_lernwelt/course_session/services/export_attendance.py:138
|
||||||
msgid "Lehrvertragsnummer"
|
msgid "Lehrvertragsnummer"
|
||||||
msgstr "Numéro de contrat d'apprentissage"
|
msgstr "Numéro de contrat d'apprentissage"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:16
|
||||||
|
msgid "export_personen"
|
||||||
|
msgstr "export_personnes"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:68
|
||||||
|
msgid "Telefon"
|
||||||
|
msgstr "Téléphone"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:69
|
||||||
|
msgid "Rolle"
|
||||||
|
msgstr "Rôle"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:118
|
||||||
|
msgid "Trainer"
|
||||||
|
msgstr "Formateur / Formatrice"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:120
|
||||||
|
msgid "Regionenleiter"
|
||||||
|
msgstr "Responsable CI"
|
||||||
|
|
||||||
#: vbv_lernwelt/feedback/export.py:19
|
#: vbv_lernwelt/feedback/export.py:19
|
||||||
msgid "export_feedback"
|
msgid "export_feedback"
|
||||||
msgstr "export_feedback"
|
msgstr "export_feedback"
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-07-12 09:36+0200\n"
|
"POT-Creation-Date: 2024-07-30 11:16+0200\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
|
@ -88,30 +88,31 @@ msgid "Lehrgang-Seite"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:278
|
#: vbv_lernwelt/course/models.py:278
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:116
|
||||||
msgid "Teilnehmer"
|
msgid "Teilnehmer"
|
||||||
msgstr ""
|
msgstr "Partecipante"
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:279
|
#: vbv_lernwelt/course/models.py:279
|
||||||
msgid "Experte/Trainer"
|
msgid "Experte/Trainer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:338
|
#: vbv_lernwelt/course/models.py:339
|
||||||
msgid "Dokumente im Circle ein/aus"
|
msgid "Dokumente im Circle ein/aus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:342
|
#: vbv_lernwelt/course/models.py:343
|
||||||
msgid "Lernmentor-Funktion ein/aus"
|
msgid "Lernmentor-Funktion ein/aus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:346
|
#: vbv_lernwelt/course/models.py:347
|
||||||
msgid "Kompetenzweise ein/aus"
|
msgid "Kompetenzweise ein/aus"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:349
|
#: vbv_lernwelt/course/models.py:350
|
||||||
msgid "Versicherungsvermittler-Lehrgang"
|
msgid "Versicherungsvermittler-Lehrgang"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: vbv_lernwelt/course/models.py:350
|
#: vbv_lernwelt/course/models.py:351
|
||||||
msgid "ÜK-Lehrgang"
|
msgid "ÜK-Lehrgang"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -119,34 +120,68 @@ msgstr ""
|
||||||
msgid "export_anwesenheit"
|
msgid "export_anwesenheit"
|
||||||
msgstr "esportazione_presenza"
|
msgstr "esportazione_presenza"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:92
|
#: vbv_lernwelt/course_session/services/export_attendance.py:86
|
||||||
|
msgid "Optionale Anwesenheit"
|
||||||
|
msgstr "Presenza opzionale"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/course_session/services/export_attendance.py:96
|
||||||
msgid "Anwesenheit"
|
msgid "Anwesenheit"
|
||||||
msgstr "Presenza"
|
msgstr "Presenza"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:117
|
#: vbv_lernwelt/course_session/services/export_attendance.py:119
|
||||||
|
msgid "Ja"
|
||||||
|
msgstr "Sì"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/course_session/services/export_attendance.py:119
|
||||||
|
msgid "Nein"
|
||||||
|
msgstr "No"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/course_session/services/export_attendance.py:128
|
||||||
msgid "Anwesend"
|
msgid "Anwesend"
|
||||||
msgstr "Presente"
|
msgstr "Presente"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:117
|
#: vbv_lernwelt/course_session/services/export_attendance.py:128
|
||||||
msgid "Nicht anwesend"
|
msgid "Nicht anwesend"
|
||||||
msgstr "Non presente"
|
msgstr "Non presente"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:124
|
#: vbv_lernwelt/course_session/services/export_attendance.py:135
|
||||||
msgid "Vorname"
|
msgid "Vorname"
|
||||||
msgstr "Nome"
|
msgstr "Nome"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:125
|
#: vbv_lernwelt/course_session/services/export_attendance.py:136
|
||||||
msgid "Nachname"
|
msgid "Nachname"
|
||||||
msgstr "Cognome"
|
msgstr "Cognome"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:126
|
#: vbv_lernwelt/course_session/services/export_attendance.py:137
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Email"
|
msgstr "Email"
|
||||||
|
|
||||||
#: vbv_lernwelt/course_session/services/export_attendance.py:127
|
#: vbv_lernwelt/course_session/services/export_attendance.py:138
|
||||||
msgid "Lehrvertragsnummer"
|
msgid "Lehrvertragsnummer"
|
||||||
msgstr "Numero di contratto di tirocinio"
|
msgstr "Numero di contratto di tirocinio"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:16
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "export_anwesenheit"
|
||||||
|
msgid "export_personen"
|
||||||
|
msgstr "esportazione_persone"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:68
|
||||||
|
msgid "Telefon"
|
||||||
|
msgstr "Telefono"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:69
|
||||||
|
msgid "Rolle"
|
||||||
|
msgstr "Ruolo"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:118
|
||||||
|
msgid "Trainer"
|
||||||
|
msgstr "Trainer"
|
||||||
|
|
||||||
|
#: vbv_lernwelt/dashboard/person_export.py:120
|
||||||
|
msgid "Regionenleiter"
|
||||||
|
msgstr "Responsabile CI"
|
||||||
|
|
||||||
#: vbv_lernwelt/feedback/export.py:19
|
#: vbv_lernwelt/feedback/export.py:19
|
||||||
msgid "export_feedback"
|
msgid "export_feedback"
|
||||||
msgstr "esportazione_feedback"
|
msgstr "esportazione_feedback"
|
||||||
|
|
|
||||||
|
|
@ -71,5 +71,6 @@ class ProfileViewTest(APITestCase):
|
||||||
"organisation_postal_code": "",
|
"organisation_postal_code": "",
|
||||||
"organisation_city": "",
|
"organisation_city": "",
|
||||||
"organisation_country": None,
|
"organisation_country": None,
|
||||||
|
"optional_attendance": [],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,11 @@ def query_competence_course_session_assignments(course_session_ids, circle_ids=N
|
||||||
AssignmentType.CASEWORK.value,
|
AssignmentType.CASEWORK.value,
|
||||||
],
|
],
|
||||||
learning_content__content_assignment__competence_certificate__isnull=False,
|
learning_content__content_assignment__competence_certificate__isnull=False,
|
||||||
|
).select_related(
|
||||||
|
"submission_deadline",
|
||||||
|
"learning_content",
|
||||||
|
"course_session",
|
||||||
|
"learning_content__content_assignment",
|
||||||
):
|
):
|
||||||
if circle_ids and csa.learning_content.get_circle().id not in circle_ids:
|
if circle_ids and csa.learning_content.get_circle().id not in circle_ids:
|
||||||
continue
|
continue
|
||||||
|
|
@ -34,6 +39,11 @@ def query_competence_course_session_edoniq_tests(course_session_ids, circle_ids=
|
||||||
for cset in CourseSessionEdoniqTest.objects.filter(
|
for cset in CourseSessionEdoniqTest.objects.filter(
|
||||||
course_session_id__in=course_session_ids,
|
course_session_id__in=course_session_ids,
|
||||||
learning_content__content_assignment__competence_certificate__isnull=False,
|
learning_content__content_assignment__competence_certificate__isnull=False,
|
||||||
|
).select_related(
|
||||||
|
"deadline",
|
||||||
|
"learning_content",
|
||||||
|
"course_session",
|
||||||
|
"learning_content__content_assignment",
|
||||||
):
|
):
|
||||||
if circle_ids and cset.learning_content.get_circle().id not in circle_ids:
|
if circle_ids and cset.learning_content.get_circle().id not in circle_ids:
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ TEST_MENTOR1_USER_ID = "d1f5f5a9-5b0a-4e1a-9e1a-9e9b5b5e1b1b"
|
||||||
TEST_STUDENT1_VV_USER_ID = "5ff59857-8de5-415e-a387-4449f9a0337a"
|
TEST_STUDENT1_VV_USER_ID = "5ff59857-8de5-415e-a387-4449f9a0337a"
|
||||||
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID = "7e8ebf0b-e6e2-4022-88f4-6e663ba0a9db"
|
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID = "7e8ebf0b-e6e2-4022-88f4-6e663ba0a9db"
|
||||||
TEST_USER_EMPTY_ID = "daecbabe-4ab9-4edf-a71f-4119042ccb02"
|
TEST_USER_EMPTY_ID = "daecbabe-4ab9-4edf-a71f-4119042ccb02"
|
||||||
|
TEST_USER_DATATRANS_HANNA_ID = "6bec1a0d-f852-47aa-a4de-072df6e07ad1"
|
||||||
|
|
||||||
TEST_COURSE_SESSION_BERN_ID = -1
|
TEST_COURSE_SESSION_BERN_ID = -1
|
||||||
TEST_COURSE_SESSION_ZURICH_ID = -2
|
TEST_COURSE_SESSION_ZURICH_ID = -2
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from django.contrib.auth.models import Group, Permission
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
from environs import Env
|
from environs import Env
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.model_utils import add_countries
|
||||||
from vbv_lernwelt.media_files.models import UserImage
|
from vbv_lernwelt.media_files.models import UserImage
|
||||||
|
|
||||||
env = Env()
|
env = Env()
|
||||||
|
|
@ -20,6 +21,7 @@ from vbv_lernwelt.core.constants import (
|
||||||
TEST_SUPERVISOR1_USER_ID,
|
TEST_SUPERVISOR1_USER_ID,
|
||||||
TEST_TRAINER1_USER_ID,
|
TEST_TRAINER1_USER_ID,
|
||||||
TEST_TRAINER2_USER_ID,
|
TEST_TRAINER2_USER_ID,
|
||||||
|
TEST_USER_DATATRANS_HANNA_ID,
|
||||||
TEST_USER_EMPTY_ID,
|
TEST_USER_EMPTY_ID,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.core.models import User
|
from vbv_lernwelt.core.models import User
|
||||||
|
|
@ -78,7 +80,30 @@ default_users = [
|
||||||
AVATAR_DIR = settings.APPS_DIR / "static" / "avatars"
|
AVATAR_DIR = settings.APPS_DIR / "static" / "avatars"
|
||||||
|
|
||||||
|
|
||||||
|
def create_datatrans_hanna_user():
|
||||||
|
hanna, _ = User.objects.get_or_create(
|
||||||
|
id=TEST_USER_DATATRANS_HANNA_ID,
|
||||||
|
)
|
||||||
|
hanna.username = "datatrans.hanna.vbv@example.com"
|
||||||
|
hanna.email = "datatrans.hanna.vbv@example.com"
|
||||||
|
hanna.language = "de"
|
||||||
|
hanna.first_name = "Hanna"
|
||||||
|
hanna.last_name = "Vbv"
|
||||||
|
hanna.street = "Bahnstrasse"
|
||||||
|
hanna.street_number = "2"
|
||||||
|
hanna.postal_code = "8603"
|
||||||
|
hanna.city = "Schwerzenbach"
|
||||||
|
hanna.country_id = "CH"
|
||||||
|
hanna.birth_date = "1970-01-01"
|
||||||
|
hanna.phone_number = "+41792018586"
|
||||||
|
hanna.password = make_password("test")
|
||||||
|
hanna.save()
|
||||||
|
return hanna
|
||||||
|
|
||||||
|
|
||||||
def create_default_users(default_password="test", set_avatar=False):
|
def create_default_users(default_password="test", set_avatar=False):
|
||||||
|
add_countries(small_set=True)
|
||||||
|
|
||||||
admin_group, created = Group.objects.get_or_create(name="admin_group")
|
admin_group, created = Group.objects.get_or_create(name="admin_group")
|
||||||
_content_creator_group, _created = Group.objects.get_or_create(
|
_content_creator_group, _created = Group.objects.get_or_create(
|
||||||
name="content_creator_grop"
|
name="content_creator_grop"
|
||||||
|
|
@ -202,6 +227,8 @@ def create_default_users(default_password="test", set_avatar=False):
|
||||||
language="de",
|
language="de",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hanna = create_datatrans_hanna_user()
|
||||||
|
|
||||||
for user_data in default_users:
|
for user_data in default_users:
|
||||||
_create_student_user(**user_data)
|
_create_student_user(**user_data)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,10 @@ from vbv_lernwelt.core.constants import (
|
||||||
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||||
TEST_STUDENT3_USER_ID,
|
TEST_STUDENT3_USER_ID,
|
||||||
TEST_TRAINER1_USER_ID,
|
TEST_TRAINER1_USER_ID,
|
||||||
|
TEST_USER_DATATRANS_HANNA_ID,
|
||||||
TEST_USER_EMPTY_ID,
|
TEST_USER_EMPTY_ID,
|
||||||
)
|
)
|
||||||
|
from vbv_lernwelt.core.create_default_users import create_datatrans_hanna_user
|
||||||
from vbv_lernwelt.core.models import Organisation, User
|
from vbv_lernwelt.core.models import Organisation, User
|
||||||
from vbv_lernwelt.course.consts import (
|
from vbv_lernwelt.course.consts import (
|
||||||
COURSE_TEST_ID,
|
COURSE_TEST_ID,
|
||||||
|
|
@ -148,6 +150,11 @@ from vbv_lernwelt.shop.models import CheckoutInformation
|
||||||
default=False,
|
default=False,
|
||||||
help="Will set only the is_vv flag for the test course and enable learning mentors for the course",
|
help="Will set only the is_vv flag for the test course and enable learning mentors for the course",
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
"--set-optional-attendance-flag/--no-optional-attendance-flag",
|
||||||
|
default=False,
|
||||||
|
help="Will set the optional attendance flag for the test-student1@example.com",
|
||||||
|
)
|
||||||
def command(
|
def command(
|
||||||
create_assignment_completion,
|
create_assignment_completion,
|
||||||
assignment_completion_user_id,
|
assignment_completion_user_id,
|
||||||
|
|
@ -167,6 +174,7 @@ def command(
|
||||||
create_learning_mentor,
|
create_learning_mentor,
|
||||||
set_only_is_uk_flag,
|
set_only_is_uk_flag,
|
||||||
set_only_is_vv_flag,
|
set_only_is_vv_flag,
|
||||||
|
set_optional_attendance_flag,
|
||||||
):
|
):
|
||||||
print("cypress reset data")
|
print("cypress reset data")
|
||||||
CourseCompletion.objects.all().delete()
|
CourseCompletion.objects.all().delete()
|
||||||
|
|
@ -195,6 +203,9 @@ def command(
|
||||||
password=make_password("test"),
|
password=make_password("test"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
User.objects.filter(id=TEST_USER_DATATRANS_HANNA_ID).delete()
|
||||||
|
create_datatrans_hanna_user()
|
||||||
|
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute("truncate core_securityrequestresponselog;")
|
cursor.execute("truncate core_securityrequestresponselog;")
|
||||||
cursor.execute("truncate core_externalapirequestlog;")
|
cursor.execute("truncate core_externalapirequestlog;")
|
||||||
|
|
@ -515,4 +526,17 @@ def command(
|
||||||
course.configuration.is_uk = False
|
course.configuration.is_uk = False
|
||||||
course.configuration.enable_learning_mentor = False
|
course.configuration.enable_learning_mentor = False
|
||||||
|
|
||||||
|
if set_optional_attendance_flag:
|
||||||
|
course_session_user = CourseSessionUser.objects.get(
|
||||||
|
user__id=TEST_STUDENT1_USER_ID
|
||||||
|
)
|
||||||
|
course_session_user.optional_attendance = True
|
||||||
|
course_session_user.save()
|
||||||
|
else:
|
||||||
|
course_session_user = CourseSessionUser.objects.get(
|
||||||
|
user_id=TEST_STUDENT1_USER_ID
|
||||||
|
)
|
||||||
|
course_session_user.optional_attendance = False
|
||||||
|
course_session_user.save()
|
||||||
|
|
||||||
course.configuration.save()
|
course.configuration.save()
|
||||||
|
|
|
||||||
|
|
@ -114,8 +114,9 @@ class User(AbstractUser):
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# fields gathered from cembra pay form
|
|
||||||
birth_date = models.DateField(null=True, blank=True)
|
birth_date = models.DateField(null=True, blank=True)
|
||||||
|
|
||||||
|
# phone number should be stored in the format +41792018586 (not validated)
|
||||||
phone_number = models.CharField(max_length=255, blank=True, default="")
|
phone_number = models.CharField(max_length=255, blank=True, default="")
|
||||||
|
|
||||||
# is only set by abacus invoice export code
|
# is only set by abacus invoice export code
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
course_session_experts = serializers.SerializerMethodField()
|
course_session_experts = serializers.SerializerMethodField()
|
||||||
country = CountrySerializer()
|
country = CountrySerializer()
|
||||||
organisation_country = CountrySerializer()
|
organisation_country = CountrySerializer()
|
||||||
|
optional_attendance = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
|
|
@ -83,6 +84,7 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
"organisation_postal_code",
|
"organisation_postal_code",
|
||||||
"organisation_city",
|
"organisation_city",
|
||||||
"organisation_country",
|
"organisation_country",
|
||||||
|
"optional_attendance",
|
||||||
]
|
]
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
"id",
|
"id",
|
||||||
|
|
@ -108,6 +110,12 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
return [str(_id) for _id in (supervisor_in_session_ids | expert_in_session_ids)]
|
return [str(_id) for _id in (supervisor_in_session_ids | expert_in_session_ids)]
|
||||||
|
|
||||||
|
def get_optional_attendance(self, obj: User) -> bool:
|
||||||
|
optional_attendance_ids = CourseSessionUser.objects.filter(
|
||||||
|
user=obj, optional_attendance=True
|
||||||
|
).values_list("course_session__id", flat=True)
|
||||||
|
return [str(id) for id in optional_attendance_ids]
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
country_data = validated_data.pop("country", None)
|
country_data = validated_data.pop("country", None)
|
||||||
organisation_country_data = validated_data.pop("organisation_country", None)
|
organisation_country_data = validated_data.pop("organisation_country", None)
|
||||||
|
|
@ -131,6 +139,12 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class CypressUserSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class OrganisationSerializer(serializers.ModelSerializer):
|
class OrganisationSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.IntegerField(source="organisation_id", read_only=True)
|
id = serializers.IntegerField(source="organisation_id", read_only=True)
|
||||||
name = serializers.SerializerMethodField()
|
name = serializers.SerializerMethodField()
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ class CourseSessionUserAdmin(admin.ModelAdmin):
|
||||||
"course_session",
|
"course_session",
|
||||||
"role",
|
"role",
|
||||||
"circles",
|
"circles",
|
||||||
|
"optional_attendance",
|
||||||
# "created_at",
|
# "created_at",
|
||||||
# "updated_at",
|
# "updated_at",
|
||||||
]
|
]
|
||||||
|
|
@ -76,6 +77,7 @@ class CourseSessionUserAdmin(admin.ModelAdmin):
|
||||||
list_filter = [
|
list_filter = [
|
||||||
"role",
|
"role",
|
||||||
"course_session",
|
"course_session",
|
||||||
|
"optional_attendance",
|
||||||
]
|
]
|
||||||
raw_id_fields = [
|
raw_id_fields = [
|
||||||
"user",
|
"user",
|
||||||
|
|
@ -97,7 +99,7 @@ class CourseSessionUserAdmin(admin.ModelAdmin):
|
||||||
return ", ".join([c.title for c in obj.expert.all()])
|
return ", ".join([c.title for c in obj.expert.all()])
|
||||||
|
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
(None, {"fields": ("user", "course_session", "role")}),
|
(None, {"fields": ("user", "course_session", "role", "optional_attendance")}),
|
||||||
(
|
(
|
||||||
"Expert/Trainer",
|
"Expert/Trainer",
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,10 @@ UK_COURSE_IDS = [
|
||||||
COURSE_UK_TRAINING_FR,
|
COURSE_UK_TRAINING_FR,
|
||||||
COURSE_UK_TRAINING_IT,
|
COURSE_UK_TRAINING_IT,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Organization IDs
|
||||||
|
ORGANISATION_OTHER_BROKER_ID = 1
|
||||||
|
ORGANISATION_OTHER_HEALTH_INSURANCE_ID = 2
|
||||||
|
ORGANISATION_OTHER_PRIVATE_INSURANCE_ID = 3
|
||||||
|
ORGANISATION_NO_COMPANY_ID = 31
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,7 @@ class CourseSessionUserObjectsType(ObjectType):
|
||||||
circles = graphene.List(
|
circles = graphene.List(
|
||||||
graphene.NonNull(CourseSessionUserExpertCircleType), required=True
|
graphene.NonNull(CourseSessionUserExpertCircleType), required=True
|
||||||
)
|
)
|
||||||
|
optional_attendance = graphene.Boolean(required=False)
|
||||||
|
|
||||||
|
|
||||||
class CircleDocumentObjectType(DjangoObjectType):
|
class CircleDocumentObjectType(DjangoObjectType):
|
||||||
|
|
@ -233,6 +234,7 @@ class CourseSessionObjectType(DjangoObjectType):
|
||||||
)
|
)
|
||||||
for circle in course_session_user.expert.all() # noqa
|
for circle in course_session_user.expert.all() # noqa
|
||||||
],
|
],
|
||||||
|
optional_attendance=course_session_user.optional_attendance, # noqa
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Generated by Django 4.2.13 on 2024-07-25 05:47
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import vbv_lernwelt.course.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("course", "0008_auto_20240403_1132"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="coursesessionuser",
|
||||||
|
name="optional_attendance",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="coursecompletion",
|
||||||
|
name="completion_status",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("SUCCESS", "Success"),
|
||||||
|
("FAIL", "Fail"),
|
||||||
|
("UNKNOWN", "Unknown"),
|
||||||
|
],
|
||||||
|
default=vbv_lernwelt.course.models.CourseCompletionStatus["UNKNOWN"],
|
||||||
|
max_length=255,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -283,6 +283,7 @@ class CourseSessionUser(models.Model):
|
||||||
expert = models.ManyToManyField(
|
expert = models.ManyToManyField(
|
||||||
"learnpath.Circle", related_name="expert", blank=True
|
"learnpath.Circle", related_name="expert", blank=True
|
||||||
)
|
)
|
||||||
|
optional_attendance = models.BooleanField(default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
constraints = [
|
constraints = [
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import djclick as click
|
||||||
|
import structlog
|
||||||
|
from django.db.models import Count
|
||||||
|
|
||||||
|
from vbv_lernwelt.course.models import CourseSessionUser
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
def command():
|
||||||
|
VV_COURSE_SESSIONS = [1, 2, 3] # DE, FR, IT
|
||||||
|
|
||||||
|
# Aggregation of users per organisation for a specific course session
|
||||||
|
user_counts = (
|
||||||
|
CourseSessionUser.objects.filter(course_session__id__in=VV_COURSE_SESSIONS)
|
||||||
|
.values("user__organisation__organisation_id", "user__organisation__name_de")
|
||||||
|
.annotate(user_count=Count("id"))
|
||||||
|
.order_by("user__organisation__organisation_id")
|
||||||
|
)
|
||||||
|
|
||||||
|
for entry in user_counts:
|
||||||
|
print(
|
||||||
|
f"Organisation Name: {entry['user__organisation__name_de']}, "
|
||||||
|
f"User Count: {entry['user_count']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"Total number of users: {sum([entry['user_count'] for entry in user_counts])}"
|
||||||
|
)
|
||||||
|
|
@ -82,6 +82,10 @@ def _create_sheet(
|
||||||
# headers
|
# headers
|
||||||
# common user headers..., <attendance_course> <date>, status <attendance_course>, ..
|
# common user headers..., <attendance_course> <date>, status <attendance_course>, ..
|
||||||
col_idx = add_user_headers(sheet)
|
col_idx = add_user_headers(sheet)
|
||||||
|
|
||||||
|
sheet.cell(row=1, column=col_idx, value=str(_("Optionale Anwesenheit")))
|
||||||
|
col_idx += 1
|
||||||
|
|
||||||
attendance_data = {}
|
attendance_data = {}
|
||||||
|
|
||||||
for course in attendance_courses:
|
for course in attendance_courses:
|
||||||
|
|
@ -110,6 +114,13 @@ def _create_sheet(
|
||||||
def _add_rows(sheet, users: list[CourseSessionUser], attendance_data):
|
def _add_rows(sheet, users: list[CourseSessionUser], attendance_data):
|
||||||
for row_idx, user in enumerate(users, start=2):
|
for row_idx, user in enumerate(users, start=2):
|
||||||
col_idx = add_user_export_data(sheet, user, row_idx)
|
col_idx = add_user_export_data(sheet, user, row_idx)
|
||||||
|
|
||||||
|
optional_attendance_text = (
|
||||||
|
str(_("Ja")) if user.optional_attendance else str(_("Nein"))
|
||||||
|
)
|
||||||
|
sheet.cell(row=row_idx, column=col_idx, value=optional_attendance_text)
|
||||||
|
col_idx += 1
|
||||||
|
|
||||||
for key, user_dict_map in attendance_data.items():
|
for key, user_dict_map in attendance_data.items():
|
||||||
user_dict = user_dict_map.get(str(user.user.id), {})
|
user_dict = user_dict_map.get(str(user.user.id), {})
|
||||||
status = user_dict.get("status", "") if user_dict else ""
|
status = user_dict.get("status", "") if user_dict else ""
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from vbv_lernwelt.core.constants import TEST_STUDENT1_USER_ID, TEST_STUDENT2_USE
|
||||||
from vbv_lernwelt.core.create_default_users import create_default_users
|
from vbv_lernwelt.core.create_default_users import create_default_users
|
||||||
from vbv_lernwelt.core.models import User
|
from vbv_lernwelt.core.models import User
|
||||||
from vbv_lernwelt.course.creators.test_course import create_test_course
|
from vbv_lernwelt.course.creators.test_course import create_test_course
|
||||||
from vbv_lernwelt.course.models import CourseSession
|
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
||||||
from vbv_lernwelt.course_session.services.export_attendance import export_attendance
|
from vbv_lernwelt.course_session.services.export_attendance import export_attendance
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -42,6 +42,11 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
self.test_student1 = User.objects.get(id=TEST_STUDENT1_USER_ID)
|
self.test_student1 = User.objects.get(id=TEST_STUDENT1_USER_ID)
|
||||||
self.test_student1.additional_json_data = {"Lehrvertragsnummer": 1234567890}
|
self.test_student1.additional_json_data = {"Lehrvertragsnummer": 1234567890}
|
||||||
self.test_student1.save()
|
self.test_student1.save()
|
||||||
|
csu1 = CourseSessionUser.objects.get(
|
||||||
|
user=self.test_student1, course_session=self.course_session_be
|
||||||
|
)
|
||||||
|
csu1.optional_attendance = True
|
||||||
|
csu1.save()
|
||||||
self.test_student2 = User.objects.get(id=TEST_STUDENT2_USER_ID)
|
self.test_student2 = User.objects.get(id=TEST_STUDENT2_USER_ID)
|
||||||
self.test_student2.additional_json_data = {"Lehrvertragsnummer": 1987654321}
|
self.test_student2.additional_json_data = {"Lehrvertragsnummer": 1987654321}
|
||||||
self.test_student2.save()
|
self.test_student2.save()
|
||||||
|
|
@ -64,6 +69,7 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
self.test_student1.last_name,
|
self.test_student1.last_name,
|
||||||
self.test_student1.email,
|
self.test_student1.email,
|
||||||
self.test_student1.additional_json_data["Lehrvertragsnummer"],
|
self.test_student1.additional_json_data["Lehrvertragsnummer"],
|
||||||
|
"Ja",
|
||||||
"Anwesend",
|
"Anwesend",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|
@ -71,6 +77,7 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
self.test_student2.last_name,
|
self.test_student2.last_name,
|
||||||
self.test_student2.email,
|
self.test_student2.email,
|
||||||
self.test_student2.additional_json_data["Lehrvertragsnummer"],
|
self.test_student2.additional_json_data["Lehrvertragsnummer"],
|
||||||
|
"Nein",
|
||||||
"Nicht anwesend",
|
"Nicht anwesend",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|
@ -78,6 +85,7 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
self.test_student3.last_name,
|
self.test_student3.last_name,
|
||||||
self.test_student3.email,
|
self.test_student3.email,
|
||||||
None,
|
None,
|
||||||
|
"Nein",
|
||||||
"Nicht anwesend",
|
"Nicht anwesend",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
@ -95,6 +103,7 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
"Nachname",
|
"Nachname",
|
||||||
"Email",
|
"Email",
|
||||||
"Lehrvertragsnummer",
|
"Lehrvertragsnummer",
|
||||||
|
"Optionale Anwesenheit",
|
||||||
f"Anwesenheit {csac.get_circle().title} {csac.due_date.start.strftime('%d.%m.%Y')}",
|
f"Anwesenheit {csac.get_circle().title} {csac.due_date.start.strftime('%d.%m.%Y')}",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -103,7 +112,7 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
self.assertEqual(len(wb.sheetnames), 1)
|
self.assertEqual(len(wb.sheetnames), 1)
|
||||||
self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a")
|
self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a")
|
||||||
|
|
||||||
self._check_export(wb, self.expected_data_be, 4, 5)
|
self._check_export(wb, self.expected_data_be, 4, 6)
|
||||||
|
|
||||||
def test_attendance_export_multiple_cs(self):
|
def test_attendance_export_multiple_cs(self):
|
||||||
self.attendance_course_zh.attendance_user_list = [
|
self.attendance_course_zh.attendance_user_list = [
|
||||||
|
|
@ -123,6 +132,7 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
self.test_student2.last_name,
|
self.test_student2.last_name,
|
||||||
self.test_student2.email,
|
self.test_student2.email,
|
||||||
self.test_student2.additional_json_data["Lehrvertragsnummer"],
|
self.test_student2.additional_json_data["Lehrvertragsnummer"],
|
||||||
|
"Nein",
|
||||||
"Anwesend",
|
"Anwesend",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
@ -136,10 +146,10 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a")
|
self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a")
|
||||||
self.assertEqual(wb.sheetnames[1], "Test Zürich 2022 a")
|
self.assertEqual(wb.sheetnames[1], "Test Zürich 2022 a")
|
||||||
|
|
||||||
self._check_export(wb, self.expected_data_be, 4, 5)
|
self._check_export(wb, self.expected_data_be, 4, 6)
|
||||||
|
|
||||||
wb.active = wb["Test Zürich 2022 a"]
|
wb.active = wb["Test Zürich 2022 a"]
|
||||||
self._check_export(wb, expected_data_zh, 2, 5)
|
self._check_export(wb, expected_data_zh, 2, 6)
|
||||||
|
|
||||||
def test_french_export(self):
|
def test_french_export(self):
|
||||||
activate("fr")
|
activate("fr")
|
||||||
|
|
@ -150,6 +160,7 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
"Nom de famille",
|
"Nom de famille",
|
||||||
"E-mail",
|
"E-mail",
|
||||||
"Numéro de contrat d'apprentissage",
|
"Numéro de contrat d'apprentissage",
|
||||||
|
"Présence facultative",
|
||||||
f"Présence {self.attendance_course_be.get_circle().title} {self.attendance_course_be.due_date.start.strftime('%d.%m.%Y')}",
|
f"Présence {self.attendance_course_be.get_circle().title} {self.attendance_course_be.due_date.start.strftime('%d.%m.%Y')}",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -160,6 +171,7 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
self.test_student1.last_name,
|
self.test_student1.last_name,
|
||||||
self.test_student1.email,
|
self.test_student1.email,
|
||||||
self.test_student1.additional_json_data["Lehrvertragsnummer"],
|
self.test_student1.additional_json_data["Lehrvertragsnummer"],
|
||||||
|
"Oui",
|
||||||
"Présent",
|
"Présent",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|
@ -167,6 +179,7 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
self.test_student2.last_name,
|
self.test_student2.last_name,
|
||||||
self.test_student2.email,
|
self.test_student2.email,
|
||||||
self.test_student2.additional_json_data["Lehrvertragsnummer"],
|
self.test_student2.additional_json_data["Lehrvertragsnummer"],
|
||||||
|
"Non",
|
||||||
"Pas présent",
|
"Pas présent",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|
@ -174,10 +187,11 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
self.test_student3.last_name,
|
self.test_student3.last_name,
|
||||||
self.test_student3.email,
|
self.test_student3.email,
|
||||||
None,
|
None,
|
||||||
|
"Non",
|
||||||
"Pas présent",
|
"Pas présent",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
self._check_export(wb, expected_data_be, 4, 5)
|
self._check_export(wb, expected_data_be, 4, 6)
|
||||||
|
|
||||||
def test_italian_export(self):
|
def test_italian_export(self):
|
||||||
activate("it")
|
activate("it")
|
||||||
|
|
@ -188,6 +202,7 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
"Cognome",
|
"Cognome",
|
||||||
"Email",
|
"Email",
|
||||||
"Numero di contratto di tirocinio",
|
"Numero di contratto di tirocinio",
|
||||||
|
"Presenza opzionale",
|
||||||
f"Presenza {self.attendance_course_be.get_circle().title} {self.attendance_course_be.due_date.start.strftime('%d.%m.%Y')}",
|
f"Presenza {self.attendance_course_be.get_circle().title} {self.attendance_course_be.due_date.start.strftime('%d.%m.%Y')}",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -198,6 +213,7 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
self.test_student1.last_name,
|
self.test_student1.last_name,
|
||||||
self.test_student1.email,
|
self.test_student1.email,
|
||||||
self.test_student1.additional_json_data["Lehrvertragsnummer"],
|
self.test_student1.additional_json_data["Lehrvertragsnummer"],
|
||||||
|
"Sì",
|
||||||
"Presente",
|
"Presente",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|
@ -205,6 +221,7 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
self.test_student2.last_name,
|
self.test_student2.last_name,
|
||||||
self.test_student2.email,
|
self.test_student2.email,
|
||||||
self.test_student2.additional_json_data["Lehrvertragsnummer"],
|
self.test_student2.additional_json_data["Lehrvertragsnummer"],
|
||||||
|
"No",
|
||||||
"Non presente",
|
"Non presente",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|
@ -212,7 +229,8 @@ class AttendanceExportTestCase(ExportBaseTestCase):
|
||||||
self.test_student3.last_name,
|
self.test_student3.last_name,
|
||||||
self.test_student3.email,
|
self.test_student3.email,
|
||||||
None,
|
None,
|
||||||
|
"No",
|
||||||
"Non presente",
|
"Non presente",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
self._check_export(wb, expected_data_be, 4, 5)
|
self._check_export(wb, expected_data_be, 4, 6)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import math
|
import math
|
||||||
from typing import List
|
from typing import List, Tuple
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
|
|
@ -7,7 +7,6 @@ import vbv_lernwelt.assignment.models
|
||||||
from vbv_lernwelt.assignment.models import (
|
from vbv_lernwelt.assignment.models import (
|
||||||
AssignmentCompletion,
|
AssignmentCompletion,
|
||||||
AssignmentCompletionStatus,
|
AssignmentCompletionStatus,
|
||||||
AssignmentType,
|
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.competence.services import (
|
from vbv_lernwelt.competence.services import (
|
||||||
query_competence_course_session_assignments,
|
query_competence_course_session_assignments,
|
||||||
|
|
@ -98,14 +97,23 @@ def get_assignment_completion_metrics(
|
||||||
assignment: vbv_lernwelt.assignment.models.Assignment,
|
assignment: vbv_lernwelt.assignment.models.Assignment,
|
||||||
user_selection_ids: List[str] | None,
|
user_selection_ids: List[str] | None,
|
||||||
urql_id_postfix: str = "",
|
urql_id_postfix: str = "",
|
||||||
|
context=None,
|
||||||
) -> AssignmentCompletionMetricsType:
|
) -> AssignmentCompletionMetricsType:
|
||||||
|
if not context:
|
||||||
|
context = {}
|
||||||
|
|
||||||
if user_selection_ids:
|
if user_selection_ids:
|
||||||
course_session_users = user_selection_ids
|
course_session_users = user_selection_ids
|
||||||
else:
|
else:
|
||||||
course_session_users = CourseSessionUser.objects.filter(
|
key = f"CourseSessionUser_{course_session.id}"
|
||||||
course_session=course_session,
|
if not key in context:
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
course_session_users = CourseSessionUser.objects.filter(
|
||||||
).values_list("user", flat=True)
|
course_session=course_session,
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
).values_list("user", flat=True)
|
||||||
|
context[key] = course_session_users
|
||||||
|
else:
|
||||||
|
course_session_users = context[key]
|
||||||
|
|
||||||
evaluation_results = AssignmentCompletion.objects.filter(
|
evaluation_results = AssignmentCompletion.objects.filter(
|
||||||
completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value,
|
completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value,
|
||||||
|
|
@ -139,33 +147,54 @@ def create_record(
|
||||||
course_session_assignment: CourseSessionAssignment | CourseSessionEdoniqTest,
|
course_session_assignment: CourseSessionAssignment | CourseSessionEdoniqTest,
|
||||||
user_selection_ids: List[str] | None,
|
user_selection_ids: List[str] | None,
|
||||||
urql_id_postfix: str = "",
|
urql_id_postfix: str = "",
|
||||||
) -> AssignmentStatisticsRecordType:
|
context=None,
|
||||||
if isinstance(course_session_assignment, CourseSessionAssignment):
|
) -> Tuple[AssignmentStatisticsRecordType, dict]:
|
||||||
due_date = course_session_assignment.submission_deadline
|
if not context:
|
||||||
else:
|
context = {}
|
||||||
due_date = course_session_assignment.deadline
|
|
||||||
|
assignment_type = (
|
||||||
|
"CourseSessionAssignment"
|
||||||
|
if isinstance(course_session_assignment, CourseSessionAssignment)
|
||||||
|
else "CourseSessionEdoniqTest"
|
||||||
|
)
|
||||||
|
due_date = (
|
||||||
|
course_session_assignment.submission_deadline
|
||||||
|
if assignment_type == "CourseSessionAssignment"
|
||||||
|
else course_session_assignment.deadline
|
||||||
|
)
|
||||||
|
|
||||||
|
key = f"{assignment_type}_{course_session_assignment.learning_content.id}"
|
||||||
|
|
||||||
|
if not key in context:
|
||||||
|
context[key] = course_session_assignment.learning_content.get_circle().id
|
||||||
|
|
||||||
|
circle_id = context[key]
|
||||||
|
|
||||||
learning_content = course_session_assignment.learning_content
|
learning_content = course_session_assignment.learning_content
|
||||||
|
|
||||||
return AssignmentStatisticsRecordType(
|
return (
|
||||||
# make sure it's unique, across all types of assignments!
|
AssignmentStatisticsRecordType(
|
||||||
_id=f"{course_session_assignment._meta.model_name}#{course_session_assignment.id}@{urql_id_postfix}",
|
# make sure it's unique, across all types of assignments!
|
||||||
# noqa
|
_id=f"{course_session_assignment._meta.model_name}#{course_session_assignment.id}@{urql_id_postfix}",
|
||||||
course_session_id=str(course_session_assignment.course_session.id), # noqa
|
# noqa
|
||||||
circle_id=learning_content.get_circle().id, # noqa
|
course_session_id=str(course_session_assignment.course_session.id), # noqa
|
||||||
course_session_assignment_id=str(course_session_assignment.id), # noqa
|
circle_id=circle_id, # noqa
|
||||||
generation=course_session_assignment.course_session.generation, # noqa
|
course_session_assignment_id=str(course_session_assignment.id), # noqa
|
||||||
assignment_type_translation_key=due_date.assignment_type_translation_key,
|
generation=course_session_assignment.course_session.generation, # noqa
|
||||||
# noqa
|
assignment_type_translation_key=due_date.assignment_type_translation_key,
|
||||||
assignment_title=learning_content.content_assignment.title, # noqa
|
# noqa
|
||||||
metrics=get_assignment_completion_metrics( # noqa
|
assignment_title=learning_content.content_assignment.title, # noqa
|
||||||
course_session=course_session_assignment.course_session, # noqa
|
metrics=get_assignment_completion_metrics( # noqa
|
||||||
assignment=learning_content.content_assignment, # noqa
|
course_session=course_session_assignment.course_session, # noqa
|
||||||
user_selection_ids=user_selection_ids, # noqa
|
assignment=learning_content.content_assignment, # noqa
|
||||||
urql_id_postfix=urql_id_postfix, # noqa
|
user_selection_ids=user_selection_ids, # noqa
|
||||||
|
urql_id_postfix=urql_id_postfix, # noqa
|
||||||
|
context=context, # noqa
|
||||||
|
),
|
||||||
|
details_url=due_date.url_expert, # noqa
|
||||||
|
deadline=due_date.start, # noqa
|
||||||
),
|
),
|
||||||
details_url=due_date.url_expert, # noqa
|
context,
|
||||||
deadline=due_date.start, # noqa
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -178,28 +207,26 @@ def assignments(
|
||||||
) -> AssignmentsStatisticsType:
|
) -> AssignmentsStatisticsType:
|
||||||
if urql_id is None:
|
if urql_id is None:
|
||||||
urql_id = str(course_id)
|
urql_id = str(course_id)
|
||||||
|
|
||||||
course_sessions = CourseSession.objects.filter(
|
course_sessions = CourseSession.objects.filter(
|
||||||
id__in=course_session_selection_ids,
|
id__in=course_session_selection_ids,
|
||||||
)
|
)
|
||||||
records: List[AssignmentStatisticsRecordType] = []
|
records: List[AssignmentStatisticsRecordType] = []
|
||||||
|
context = {}
|
||||||
|
|
||||||
for course_session in course_sessions:
|
csas = query_competence_course_session_assignments(course_sessions, circle_ids)
|
||||||
for csa in query_competence_course_session_assignments(
|
csets = query_competence_course_session_edoniq_tests(course_sessions, circle_ids)
|
||||||
[course_session.id], circle_ids
|
|
||||||
):
|
|
||||||
record = create_record(csa, user_selection_ids, urql_id_postfix=urql_id)
|
|
||||||
records.append(record)
|
|
||||||
|
|
||||||
for cset in query_competence_course_session_edoniq_tests(
|
for csa in csas:
|
||||||
[course_session.id], circle_ids
|
record, context = create_record(
|
||||||
):
|
csa, user_selection_ids, urql_id_postfix=urql_id, context=context
|
||||||
record = create_record(
|
)
|
||||||
course_session_assignment=cset,
|
records.append(record)
|
||||||
user_selection_ids=user_selection_ids,
|
|
||||||
urql_id_postfix=urql_id,
|
for cset in csets:
|
||||||
)
|
record, context = create_record(
|
||||||
records.append(record)
|
cset, user_selection_ids, urql_id_postfix=urql_id, context=context
|
||||||
|
)
|
||||||
|
records.append(record)
|
||||||
|
|
||||||
return AssignmentsStatisticsType(
|
return AssignmentsStatisticsType(
|
||||||
_id=urql_id, # noqa
|
_id=urql_id, # noqa
|
||||||
|
|
|
||||||
|
|
@ -41,25 +41,23 @@ def competences(
|
||||||
completions = CourseCompletion.objects.filter(
|
completions = CourseCompletion.objects.filter(
|
||||||
course_session_id__in=course_session_selection_ids,
|
course_session_id__in=course_session_selection_ids,
|
||||||
page_type="competence.PerformanceCriteria",
|
page_type="competence.PerformanceCriteria",
|
||||||
).prefetch_related("course_session", "page")
|
).select_related("course_session", "page")
|
||||||
|
|
||||||
if user_selection_ids is not None:
|
if user_selection_ids is not None:
|
||||||
completions = completions.filter(user_id__in=user_selection_ids)
|
completions = completions.filter(user_id__in=user_selection_ids)
|
||||||
|
|
||||||
competence_records = {}
|
page_ids = completions.values_list("page_id", flat=True).distinct()
|
||||||
|
pages = Page.objects.filter(id__in=page_ids).specific()
|
||||||
|
learning_units = {page.id: page.specific.learning_unit for page in pages}
|
||||||
|
|
||||||
unique_page_ids = {completion.page.id for completion in completions}
|
|
||||||
learning_units = {
|
|
||||||
page_id: Page.objects.get(id=page_id).specific.learning_unit
|
|
||||||
for page_id in unique_page_ids
|
|
||||||
}
|
|
||||||
circles = {
|
circles = {
|
||||||
lu.id: c
|
lu.id: c
|
||||||
for lu in learning_units.values()
|
for lu in learning_units.values()
|
||||||
if (lu is not None and (c := lu.get_circle()) is not None)
|
if lu and (c := lu.get_circle()) and (circle_ids is None or c.id in circle_ids)
|
||||||
and (circle_ids is None or c.id in circle_ids)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
competence_records = {}
|
||||||
|
|
||||||
for completion in completions:
|
for completion in completions:
|
||||||
learning_unit = learning_units.get(completion.page.id)
|
learning_unit = learning_units.get(completion.page.id)
|
||||||
|
|
||||||
|
|
@ -73,20 +71,23 @@ def competences(
|
||||||
|
|
||||||
combined_id = f"{circle.id}-{completion.course_session.id}@{urql_id_postfix}"
|
combined_id = f"{circle.id}-{completion.course_session.id}@{urql_id_postfix}"
|
||||||
|
|
||||||
competence_records.setdefault(combined_id, {}).setdefault(
|
if combined_id not in competence_records:
|
||||||
learning_unit,
|
competence_records[combined_id] = {}
|
||||||
CompetenceRecordStatisticsType(
|
|
||||||
_id=combined_id, # noqa
|
if learning_unit not in competence_records[combined_id]:
|
||||||
title=learning_unit.title, # noqa
|
competence_records[combined_id][
|
||||||
course_session_id=completion.course_session.id, # noqa
|
learning_unit
|
||||||
generation=completion.course_session.generation, # noqa
|
] = CompetenceRecordStatisticsType(
|
||||||
circle_id=circle.id, # noqa
|
_id=combined_id,
|
||||||
success_count=0, # noqa
|
title=learning_unit.title,
|
||||||
fail_count=0, # noqa
|
course_session_id=completion.course_session.id,
|
||||||
|
generation=completion.course_session.generation,
|
||||||
|
circle_id=circle.id,
|
||||||
|
success_count=0,
|
||||||
|
fail_count=0,
|
||||||
details_url=f"/course/{course_slug}/cockpit?courseSessionId={completion.course_session.id}",
|
details_url=f"/course/{course_slug}/cockpit?courseSessionId={completion.course_session.id}",
|
||||||
# noqa
|
# noqa
|
||||||
),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if completion.completion_status == CourseCompletionStatus.SUCCESS.value:
|
if completion.completion_status == CourseCompletionStatus.SUCCESS.value:
|
||||||
competence_records[combined_id][learning_unit].success_count += 1
|
competence_records[combined_id][learning_unit].success_count += 1
|
||||||
|
|
@ -99,7 +100,7 @@ def competences(
|
||||||
for record in circle_records.values()
|
for record in circle_records.values()
|
||||||
]
|
]
|
||||||
|
|
||||||
success_count = sum([c.success_count for c in values])
|
success_count = sum(c.success_count for c in values)
|
||||||
fail_count = sum([c.fail_count for c in values])
|
fail_count = sum(c.fail_count for c in values)
|
||||||
|
|
||||||
return values, success_count, fail_count
|
return values, success_count, fail_count
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
from io import BytesIO
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from openpyxl import Workbook
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.models import User
|
||||||
|
from vbv_lernwelt.course.models import CourseSession
|
||||||
|
from vbv_lernwelt.course_session.services.export_attendance import (
|
||||||
|
add_user_headers,
|
||||||
|
make_export_filename,
|
||||||
|
sanitize_sheet_name,
|
||||||
|
)
|
||||||
|
from vbv_lernwelt.dashboard.utils import create_person_list_with_roles
|
||||||
|
|
||||||
|
PERSONS_EXPORT_FILENAME = _("export_personen")
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def export_persons(
|
||||||
|
user: User,
|
||||||
|
course_session_ids: list[str],
|
||||||
|
save_as_file: bool = False,
|
||||||
|
) -> Optional[bytes]:
|
||||||
|
if not course_session_ids:
|
||||||
|
return
|
||||||
|
|
||||||
|
wb = Workbook()
|
||||||
|
# remove the first sheet is just easier than keeping track of the active sheet
|
||||||
|
wb.remove(wb.active)
|
||||||
|
|
||||||
|
user_with_roles = create_person_list_with_roles(user, course_session_ids)
|
||||||
|
course_sessions = CourseSession.objects.filter(id__in=course_session_ids)
|
||||||
|
|
||||||
|
for cs in course_sessions:
|
||||||
|
_create_sheet(
|
||||||
|
wb,
|
||||||
|
cs.title,
|
||||||
|
cs.id,
|
||||||
|
user_with_roles,
|
||||||
|
)
|
||||||
|
|
||||||
|
if save_as_file:
|
||||||
|
wb.save(make_export_filename(PERSONS_EXPORT_FILENAME))
|
||||||
|
else:
|
||||||
|
output = BytesIO()
|
||||||
|
wb.save(output)
|
||||||
|
|
||||||
|
output.seek(0)
|
||||||
|
return output.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def _create_sheet(
|
||||||
|
wb: Workbook,
|
||||||
|
title: str,
|
||||||
|
cs_id: int,
|
||||||
|
user_with_roles,
|
||||||
|
):
|
||||||
|
sheet = wb.create_sheet(title=sanitize_sheet_name(title))
|
||||||
|
|
||||||
|
if len(user_with_roles) == 0:
|
||||||
|
return sheet
|
||||||
|
|
||||||
|
# headers
|
||||||
|
# common user headers, Circle <title> <learningcontenttitle> bestanden, Circle <title> <learningcontenttitle> Resultat, ...
|
||||||
|
col_idx = add_user_headers(sheet)
|
||||||
|
sheet.cell(row=1, column=col_idx, value=str(_("Telefon")))
|
||||||
|
sheet.cell(row=1, column=col_idx + 1, value=str(_("Rolle")))
|
||||||
|
|
||||||
|
_add_rows(sheet, user_with_roles, cs_id)
|
||||||
|
|
||||||
|
return sheet
|
||||||
|
|
||||||
|
|
||||||
|
def _add_rows(
|
||||||
|
sheet,
|
||||||
|
users,
|
||||||
|
course_session_id,
|
||||||
|
):
|
||||||
|
idx_offset = 0
|
||||||
|
|
||||||
|
for row_idx, user in enumerate(users, start=2):
|
||||||
|
|
||||||
|
def get_user_cs_by_id(user_cs, cs_id):
|
||||||
|
return next((cs for cs in user_cs if int(cs.get("id")) == cs_id), None)
|
||||||
|
|
||||||
|
user_cs = get_user_cs_by_id(user["course_sessions"], course_session_id)
|
||||||
|
|
||||||
|
if not user_cs:
|
||||||
|
logger.warning(
|
||||||
|
"User not found in course session",
|
||||||
|
user_id=user["user_id"],
|
||||||
|
course_session_id=course_session_id,
|
||||||
|
)
|
||||||
|
idx_offset += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
user_role = _role_as_string(user_cs.get("user_role"))
|
||||||
|
idx = row_idx - idx_offset
|
||||||
|
|
||||||
|
sheet.cell(row=idx, column=1, value=user["first_name"])
|
||||||
|
sheet.cell(row=idx, column=2, value=user["last_name"])
|
||||||
|
sheet.cell(row=idx, column=3, value=user["email"])
|
||||||
|
sheet.cell(row=idx, column=4, value=user.get("Lehrvertragsnummer", ""))
|
||||||
|
sheet.cell(
|
||||||
|
row=idx,
|
||||||
|
column=5,
|
||||||
|
value=user.get("phone_number", ""),
|
||||||
|
)
|
||||||
|
sheet.cell(row=idx, column=6, value=user_role)
|
||||||
|
|
||||||
|
|
||||||
|
def _role_as_string(role):
|
||||||
|
if role == "MEMBER":
|
||||||
|
return str(_("Teilnehmer"))
|
||||||
|
elif role == "EXPERT":
|
||||||
|
return str(_("Trainer"))
|
||||||
|
elif role == "SUPERVISOR":
|
||||||
|
return str(_("Regionenleiter"))
|
||||||
|
else:
|
||||||
|
return role
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
import io
|
||||||
|
|
||||||
|
from django.utils.translation import activate
|
||||||
|
from openpyxl import load_workbook
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.constants import (
|
||||||
|
TEST_STUDENT1_USER_ID,
|
||||||
|
TEST_STUDENT2_USER_ID,
|
||||||
|
TEST_STUDENT3_USER_ID,
|
||||||
|
TEST_TRAINER1_USER_ID,
|
||||||
|
TEST_TRAINER2_USER_ID,
|
||||||
|
)
|
||||||
|
from vbv_lernwelt.core.create_default_users import create_default_users
|
||||||
|
from vbv_lernwelt.core.models import User
|
||||||
|
from vbv_lernwelt.course.creators.test_course import create_test_course
|
||||||
|
from vbv_lernwelt.course.models import CourseSession
|
||||||
|
from vbv_lernwelt.course_session.tests.test_attendance_export import ExportBaseTestCase
|
||||||
|
from vbv_lernwelt.dashboard.person_export import export_persons
|
||||||
|
from vbv_lernwelt.learnpath.models import Circle
|
||||||
|
|
||||||
|
|
||||||
|
class PersonsExportTestCase(ExportBaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
create_default_users()
|
||||||
|
create_test_course(include_vv=False, with_sessions=True)
|
||||||
|
|
||||||
|
self.course_session_be = CourseSession.objects.get(title="Test Bern 2022 a")
|
||||||
|
self.course_session_zh = CourseSession.objects.get(title="Test Zürich 2022 a")
|
||||||
|
|
||||||
|
self.circle_fahrzeug = Circle.objects.get(
|
||||||
|
slug="test-lehrgang-lp-circle-fahrzeug"
|
||||||
|
)
|
||||||
|
self.circle_reisen = Circle.objects.get(slug="test-lehrgang-lp-circle-fahrzeug")
|
||||||
|
|
||||||
|
self.test_trainer1 = User.objects.get(id=TEST_TRAINER1_USER_ID)
|
||||||
|
self.test_trainer2 = User.objects.get(id=TEST_TRAINER2_USER_ID)
|
||||||
|
self.test_student1 = User.objects.get(id=TEST_STUDENT1_USER_ID)
|
||||||
|
self.test_student2 = User.objects.get(id=TEST_STUDENT2_USER_ID)
|
||||||
|
self.test_student3 = User.objects.get(id=TEST_STUDENT3_USER_ID)
|
||||||
|
|
||||||
|
self.test_student1_row = [
|
||||||
|
self.test_student1.first_name,
|
||||||
|
self.test_student1.last_name,
|
||||||
|
self.test_student1.email,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
"Teilnehmer",
|
||||||
|
]
|
||||||
|
self.test_student2_row = [
|
||||||
|
self.test_student2.first_name,
|
||||||
|
self.test_student2.last_name,
|
||||||
|
self.test_student2.email,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
"Teilnehmer",
|
||||||
|
]
|
||||||
|
self.test_student3_row = [
|
||||||
|
self.test_student3.first_name,
|
||||||
|
self.test_student3.last_name,
|
||||||
|
self.test_student3.email,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
"Teilnehmer",
|
||||||
|
]
|
||||||
|
self.test_trainer1_row = [
|
||||||
|
self.test_trainer1.first_name,
|
||||||
|
self.test_trainer1.last_name,
|
||||||
|
self.test_trainer1.email,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
"Trainer",
|
||||||
|
]
|
||||||
|
self.test_trainer2_row = [
|
||||||
|
self.test_trainer2.first_name,
|
||||||
|
self.test_trainer2.last_name,
|
||||||
|
self.test_trainer2.email,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
"Trainer",
|
||||||
|
]
|
||||||
|
|
||||||
|
def _generate_expected_data(self, rows):
|
||||||
|
expected_data = [
|
||||||
|
self._make_header(),
|
||||||
|
]
|
||||||
|
for r in rows:
|
||||||
|
expected_data.append(r)
|
||||||
|
|
||||||
|
return expected_data
|
||||||
|
|
||||||
|
def _generate_workbook(self, user, course_session_ids):
|
||||||
|
export_data = io.BytesIO(
|
||||||
|
export_persons(user, course_session_ids, save_as_file=False)
|
||||||
|
)
|
||||||
|
return load_workbook(export_data)
|
||||||
|
|
||||||
|
def _make_header(self):
|
||||||
|
return [
|
||||||
|
"Vorname",
|
||||||
|
"Nachname",
|
||||||
|
"Email",
|
||||||
|
"Lehrvertragsnummer",
|
||||||
|
"Telefon",
|
||||||
|
"Rolle",
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_export_persons(self):
|
||||||
|
wb = self._generate_workbook(self.test_trainer1, [self.course_session_be.id])
|
||||||
|
self.assertEqual(len(wb.sheetnames), 1)
|
||||||
|
self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a")
|
||||||
|
wb.active = wb["Test Bern 2022 a"]
|
||||||
|
|
||||||
|
data = self._generate_expected_data(
|
||||||
|
[
|
||||||
|
self.test_student1_row,
|
||||||
|
self.test_student2_row,
|
||||||
|
self.test_student3_row,
|
||||||
|
self.test_trainer1_row,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
self._check_export(wb, data, 4, 6)
|
||||||
|
|
||||||
|
wb = self._generate_workbook(self.test_trainer2, [self.course_session_zh.id])
|
||||||
|
self.assertEqual(len(wb.sheetnames), 1)
|
||||||
|
self.assertEqual(wb.sheetnames[0], "Test Zürich 2022 a")
|
||||||
|
wb.active = wb["Test Zürich 2022 a"]
|
||||||
|
|
||||||
|
data = self._generate_expected_data(
|
||||||
|
[self.test_student2_row, self.test_trainer2_row]
|
||||||
|
)
|
||||||
|
self._check_export(wb, data, 3, 6)
|
||||||
|
|
||||||
|
def test_cannot_export_other_session(self):
|
||||||
|
wb = self._generate_workbook(self.test_trainer1, [self.course_session_zh.id])
|
||||||
|
self.assertEqual(len(wb.sheetnames), 1)
|
||||||
|
self.assertEqual(wb.sheetnames[0], "Test Zürich 2022 a")
|
||||||
|
wb.active = wb["Test Zürich 2022 a"]
|
||||||
|
|
||||||
|
data = self._generate_expected_data([[None] * 6])
|
||||||
|
|
||||||
|
self._check_export(wb, data, 1, 6)
|
||||||
|
|
||||||
|
def test_export_in_fr(self):
|
||||||
|
activate("fr")
|
||||||
|
wb = self._generate_workbook(self.test_trainer1, [self.course_session_be.id])
|
||||||
|
self.assertEqual(len(wb.sheetnames), 1)
|
||||||
|
self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a")
|
||||||
|
wb.active = wb["Test Bern 2022 a"]
|
||||||
|
|
||||||
|
header = [
|
||||||
|
"Prénom",
|
||||||
|
"Nom de famille",
|
||||||
|
"E-mail",
|
||||||
|
"Numéro de contrat d'apprentissage",
|
||||||
|
"Téléphone",
|
||||||
|
"Rôle",
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertEqual([cell.value for cell in wb.active[1]], header)
|
||||||
|
self.assertEqual(wb.active.cell(row=2, column=6).value, "Participant")
|
||||||
|
self.assertEqual(
|
||||||
|
wb.active.cell(row=5, column=6).value, "Formateur / Formatrice"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_export_in_it(self):
|
||||||
|
activate("it")
|
||||||
|
wb = self._generate_workbook(self.test_trainer1, [self.course_session_be.id])
|
||||||
|
self.assertEqual(len(wb.sheetnames), 1)
|
||||||
|
self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a")
|
||||||
|
wb.active = wb["Test Bern 2022 a"]
|
||||||
|
|
||||||
|
header = [
|
||||||
|
"Nome",
|
||||||
|
"Cognome",
|
||||||
|
"Email",
|
||||||
|
"Numero di contratto di tirocinio",
|
||||||
|
"Telefono",
|
||||||
|
"Ruolo",
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertEqual([cell.value for cell in wb.active[1]], header)
|
||||||
|
self.assertEqual(wb.active.cell(row=2, column=6).value, "Partecipante")
|
||||||
|
self.assertEqual(wb.active.cell(row=5, column=6).value, "Trainer")
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import List, Set
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.models import User
|
||||||
|
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
||||||
|
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||||
|
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class CourseSessionWithRoles:
|
||||||
|
_original: CourseSession
|
||||||
|
roles: Set[str]
|
||||||
|
|
||||||
|
def __getattr__(self, name: str):
|
||||||
|
# Delegate attribute access to the _original CourseSession object
|
||||||
|
return getattr(self._original, name)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
raise NotImplementedError("This proxy object cannot be saved.")
|
||||||
|
|
||||||
|
|
||||||
|
def get_course_sessions_with_roles_for_user(user: User) -> List[CourseSessionWithRoles]:
|
||||||
|
result_course_sessions = {}
|
||||||
|
|
||||||
|
# participant/member/expert course sessions
|
||||||
|
csu_qs = CourseSessionUser.objects.filter(user=user).prefetch_related(
|
||||||
|
"course_session", "course_session__course"
|
||||||
|
)
|
||||||
|
for csu in csu_qs:
|
||||||
|
cs = csu.course_session
|
||||||
|
# member/expert is mutually exclusive...
|
||||||
|
cs.roles = {csu.role}
|
||||||
|
result_course_sessions[cs.id] = cs
|
||||||
|
|
||||||
|
# enrich with supervisor course sessions
|
||||||
|
csg_qs = CourseSessionGroup.objects.filter(supervisor=user).prefetch_related(
|
||||||
|
"course_session", "course_session__course"
|
||||||
|
)
|
||||||
|
for csg in csg_qs:
|
||||||
|
for cs in csg.course_session.all():
|
||||||
|
cs.roles = set()
|
||||||
|
cs = result_course_sessions.get(cs.id, cs)
|
||||||
|
|
||||||
|
cs.roles.add("SUPERVISOR")
|
||||||
|
result_course_sessions[cs.id] = cs
|
||||||
|
|
||||||
|
# enrich with mentor course sessions
|
||||||
|
lm_qs = LearningMentor.objects.filter(mentor=user).prefetch_related(
|
||||||
|
"course_session", "course_session__course"
|
||||||
|
)
|
||||||
|
for lm in lm_qs:
|
||||||
|
cs = lm.course_session
|
||||||
|
cs.roles = set()
|
||||||
|
cs = result_course_sessions.get(cs.id, cs)
|
||||||
|
|
||||||
|
cs.roles.add("LEARNING_MENTOR")
|
||||||
|
result_course_sessions[cs.id] = cs
|
||||||
|
|
||||||
|
return [
|
||||||
|
CourseSessionWithRoles(cs, cs.roles) for cs in result_course_sessions.values()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def has_cs_role(roles: Set[str]) -> bool:
|
||||||
|
return bool(roles & {"SUPERVISOR", "EXPERT", "MEMBER"})
|
||||||
|
|
||||||
|
|
||||||
|
def user_role(roles: Set[str]) -> str:
|
||||||
|
if "SUPERVISOR" in roles:
|
||||||
|
return "SUPERVISOR"
|
||||||
|
if "EXPERT" in roles:
|
||||||
|
return "EXPERT"
|
||||||
|
if "MEMBER" in roles:
|
||||||
|
return "MEMBER"
|
||||||
|
return "LEARNING_MENTOR"
|
||||||
|
|
||||||
|
|
||||||
|
def create_course_session_dict(course_session_object, my_role, user_role):
|
||||||
|
return {
|
||||||
|
"id": str(course_session_object.id),
|
||||||
|
"session_title": course_session_object.title,
|
||||||
|
"course_id": str(course_session_object.course.id),
|
||||||
|
"course_title": course_session_object.course.title,
|
||||||
|
"course_slug": course_session_object.course.slug,
|
||||||
|
"region": course_session_object.region,
|
||||||
|
"generation": course_session_object.generation,
|
||||||
|
"my_role": my_role,
|
||||||
|
"user_role": user_role,
|
||||||
|
"is_uk": course_session_object.course.configuration.is_uk,
|
||||||
|
"is_vv": course_session_object.course.configuration.is_vv,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def create_person_list_with_roles(
|
||||||
|
user, course_session_ids=None, include_private_data=False
|
||||||
|
):
|
||||||
|
def create_user_dict(user_object):
|
||||||
|
user_data = {
|
||||||
|
"user_id": user_object.id,
|
||||||
|
"first_name": user_object.first_name,
|
||||||
|
"last_name": user_object.last_name,
|
||||||
|
"email": user_object.email,
|
||||||
|
"avatar_url_small": user_object.avatar_url_small,
|
||||||
|
"avatar_url": user_object.avatar_url,
|
||||||
|
"course_sessions": [],
|
||||||
|
}
|
||||||
|
if include_private_data:
|
||||||
|
user_data["phone_number"] = user_object.phone_number
|
||||||
|
user_data["Lehrvertragsnummer"] = user_object.additional_json_data.get(
|
||||||
|
"Lehrvertragsnummer", ""
|
||||||
|
)
|
||||||
|
|
||||||
|
return user_data
|
||||||
|
|
||||||
|
course_sessions = get_course_sessions_with_roles_for_user(user)
|
||||||
|
|
||||||
|
result_persons = {}
|
||||||
|
for cs in course_sessions:
|
||||||
|
if has_cs_role(cs.roles) and cs.course.configuration.is_uk:
|
||||||
|
course_session_users = CourseSessionUser.objects.filter(
|
||||||
|
course_session=cs.id
|
||||||
|
).select_related("user")
|
||||||
|
my_role = user_role(cs.roles)
|
||||||
|
for csu in course_session_users:
|
||||||
|
person_data = result_persons.get(
|
||||||
|
csu.user.id, create_user_dict(csu.user)
|
||||||
|
)
|
||||||
|
person_data["course_sessions"].append(
|
||||||
|
create_course_session_dict(cs, my_role, csu.role)
|
||||||
|
)
|
||||||
|
result_persons[csu.user.id] = person_data
|
||||||
|
|
||||||
|
# add persons where request.user is mentor
|
||||||
|
for cs in course_sessions:
|
||||||
|
if "LEARNING_MENTOR" in cs.roles:
|
||||||
|
lm = LearningMentor.objects.filter(
|
||||||
|
mentor=user, course_session=cs.id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
for participant in lm.participants.all():
|
||||||
|
course_session_entry = create_course_session_dict(
|
||||||
|
cs,
|
||||||
|
"LEARNING_MENTOR",
|
||||||
|
"LEARNING_MENTEE",
|
||||||
|
)
|
||||||
|
|
||||||
|
if participant.user.id not in result_persons:
|
||||||
|
person_data = create_user_dict(participant.user)
|
||||||
|
person_data["course_sessions"] = [course_session_entry]
|
||||||
|
result_persons[participant.user.id] = person_data
|
||||||
|
else:
|
||||||
|
# user is already in result_persons
|
||||||
|
result_persons[participant.user.id]["course_sessions"].append(
|
||||||
|
course_session_entry
|
||||||
|
)
|
||||||
|
|
||||||
|
# add persons where request.user is mentee
|
||||||
|
mentor_relation_qs = LearningMentor.objects.filter(
|
||||||
|
participants__user=user
|
||||||
|
).prefetch_related("mentor", "course_session")
|
||||||
|
for mentor_relation in mentor_relation_qs:
|
||||||
|
cs = mentor_relation.course_session
|
||||||
|
course_session_entry = create_course_session_dict(
|
||||||
|
cs,
|
||||||
|
"LEARNING_MENTEE",
|
||||||
|
"LEARNING_MENTOR",
|
||||||
|
)
|
||||||
|
|
||||||
|
if mentor_relation.mentor.id not in result_persons:
|
||||||
|
person_data = create_user_dict(mentor_relation.mentor)
|
||||||
|
person_data["course_sessions"] = [course_session_entry]
|
||||||
|
result_persons[mentor_relation.mentor.id] = person_data
|
||||||
|
else:
|
||||||
|
# user is already in result_persons
|
||||||
|
result_persons[mentor_relation.mentor.id]["course_sessions"].append(
|
||||||
|
course_session_entry
|
||||||
|
)
|
||||||
|
return result_persons.values()
|
||||||
|
|
@ -2,7 +2,7 @@ import base64
|
||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Set, Tuple
|
from typing import List, Tuple
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
|
@ -24,25 +24,27 @@ from vbv_lernwelt.competence.services import (
|
||||||
query_competence_course_session_edoniq_tests,
|
query_competence_course_session_edoniq_tests,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.core.models import User
|
from vbv_lernwelt.core.models import User
|
||||||
from vbv_lernwelt.course.models import (
|
from vbv_lernwelt.course.models import CourseConfiguration, CourseSessionUser
|
||||||
CourseConfiguration,
|
|
||||||
CourseSession,
|
|
||||||
CourseSessionUser,
|
|
||||||
)
|
|
||||||
from vbv_lernwelt.course.views import logger
|
from vbv_lernwelt.course.views import logger
|
||||||
from vbv_lernwelt.course_session.services.export_attendance import (
|
from vbv_lernwelt.course_session.services.export_attendance import (
|
||||||
ATTENDANCE_EXPORT_FILENAME,
|
ATTENDANCE_EXPORT_FILENAME,
|
||||||
export_attendance,
|
export_attendance,
|
||||||
make_export_filename,
|
make_export_filename,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
from vbv_lernwelt.dashboard.person_export import export_persons, PERSONS_EXPORT_FILENAME
|
||||||
|
from vbv_lernwelt.dashboard.utils import (
|
||||||
|
CourseSessionWithRoles,
|
||||||
|
create_course_session_dict,
|
||||||
|
create_person_list_with_roles,
|
||||||
|
get_course_sessions_with_roles_for_user,
|
||||||
|
user_role,
|
||||||
|
)
|
||||||
from vbv_lernwelt.duedate.models import DueDate
|
from vbv_lernwelt.duedate.models import DueDate
|
||||||
from vbv_lernwelt.duedate.serializers import DueDateSerializer
|
from vbv_lernwelt.duedate.serializers import DueDateSerializer
|
||||||
from vbv_lernwelt.feedback.export import (
|
from vbv_lernwelt.feedback.export import (
|
||||||
export_feedback_with_circle_restriction,
|
export_feedback_with_circle_restriction,
|
||||||
FEEDBACK_EXPORT_FILE_NAME,
|
FEEDBACK_EXPORT_FILE_NAME,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.learning_mentor.models import LearningMentor
|
|
||||||
from vbv_lernwelt.learnpath.models import Circle
|
from vbv_lernwelt.learnpath.models import Circle
|
||||||
from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback
|
from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback
|
||||||
|
|
||||||
|
|
@ -65,19 +67,6 @@ class RoleKeyType(Enum):
|
||||||
TRAINER = "Trainer"
|
TRAINER = "Trainer"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class CourseSessionWithRoles:
|
|
||||||
_original: CourseSession
|
|
||||||
roles: Set[str]
|
|
||||||
|
|
||||||
def __getattr__(self, name: str):
|
|
||||||
# Delegate attribute access to the _original CourseSession object
|
|
||||||
return getattr(self._original, name)
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
raise NotImplementedError("This proxy object cannot be saved.")
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class CourseConfig:
|
class CourseConfig:
|
||||||
course_id: str
|
course_id: str
|
||||||
|
|
@ -92,157 +81,6 @@ class CourseConfig:
|
||||||
session_to_continue_id: str | None
|
session_to_continue_id: str | None
|
||||||
|
|
||||||
|
|
||||||
def get_course_sessions_with_roles_for_user(user: User) -> List[CourseSessionWithRoles]:
|
|
||||||
result_course_sessions = {}
|
|
||||||
|
|
||||||
# participant/member/expert course sessions
|
|
||||||
csu_qs = CourseSessionUser.objects.filter(user=user).prefetch_related(
|
|
||||||
"course_session", "course_session__course"
|
|
||||||
)
|
|
||||||
for csu in csu_qs:
|
|
||||||
cs = csu.course_session
|
|
||||||
# member/expert is mutually exclusive...
|
|
||||||
cs.roles = {csu.role}
|
|
||||||
result_course_sessions[cs.id] = cs
|
|
||||||
|
|
||||||
# enrich with supervisor course sessions
|
|
||||||
csg_qs = CourseSessionGroup.objects.filter(supervisor=user).prefetch_related(
|
|
||||||
"course_session", "course_session__course"
|
|
||||||
)
|
|
||||||
for csg in csg_qs:
|
|
||||||
for cs in csg.course_session.all():
|
|
||||||
cs.roles = set()
|
|
||||||
cs = result_course_sessions.get(cs.id, cs)
|
|
||||||
|
|
||||||
cs.roles.add("SUPERVISOR")
|
|
||||||
result_course_sessions[cs.id] = cs
|
|
||||||
|
|
||||||
# enrich with mentor course sessions
|
|
||||||
lm_qs = LearningMentor.objects.filter(mentor=user).prefetch_related(
|
|
||||||
"course_session", "course_session__course"
|
|
||||||
)
|
|
||||||
for lm in lm_qs:
|
|
||||||
cs = lm.course_session
|
|
||||||
cs.roles = set()
|
|
||||||
cs = result_course_sessions.get(cs.id, cs)
|
|
||||||
|
|
||||||
cs.roles.add("LEARNING_MENTOR")
|
|
||||||
result_course_sessions[cs.id] = cs
|
|
||||||
|
|
||||||
return [
|
|
||||||
CourseSessionWithRoles(cs, cs.roles) for cs in result_course_sessions.values()
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def has_cs_role(roles: Set[str]) -> bool:
|
|
||||||
return bool(roles & {"SUPERVISOR", "EXPERT", "MEMBER"})
|
|
||||||
|
|
||||||
|
|
||||||
def user_role(roles: Set[str]) -> str:
|
|
||||||
if "SUPERVISOR" in roles:
|
|
||||||
return "SUPERVISOR"
|
|
||||||
if "EXPERT" in roles:
|
|
||||||
return "EXPERT"
|
|
||||||
if "MEMBER" in roles:
|
|
||||||
return "MEMBER"
|
|
||||||
return "LEARNING_MENTOR"
|
|
||||||
|
|
||||||
|
|
||||||
def _create_course_session_dict(course_session_object, my_role, user_role):
|
|
||||||
return {
|
|
||||||
"id": str(course_session_object.id),
|
|
||||||
"session_title": course_session_object.title,
|
|
||||||
"course_id": str(course_session_object.course.id),
|
|
||||||
"course_title": course_session_object.course.title,
|
|
||||||
"course_slug": course_session_object.course.slug,
|
|
||||||
"region": course_session_object.region,
|
|
||||||
"generation": course_session_object.generation,
|
|
||||||
"my_role": my_role,
|
|
||||||
"user_role": user_role,
|
|
||||||
"is_uk": course_session_object.course.configuration.is_uk,
|
|
||||||
"is_vv": course_session_object.course.configuration.is_vv,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _create_person_list_with_roles(user):
|
|
||||||
def create_user_dict(user_object):
|
|
||||||
return {
|
|
||||||
"user_id": user_object.id,
|
|
||||||
"first_name": user_object.first_name,
|
|
||||||
"last_name": user_object.last_name,
|
|
||||||
"email": user_object.email,
|
|
||||||
"avatar_url_small": user_object.avatar_url_small,
|
|
||||||
"avatar_url": user_object.avatar_url,
|
|
||||||
"course_sessions": [],
|
|
||||||
}
|
|
||||||
|
|
||||||
course_sessions = get_course_sessions_with_roles_for_user(user)
|
|
||||||
|
|
||||||
result_persons = {}
|
|
||||||
for cs in course_sessions:
|
|
||||||
if has_cs_role(cs.roles) and cs.course.configuration.is_uk:
|
|
||||||
course_session_users = CourseSessionUser.objects.filter(
|
|
||||||
course_session=cs.id
|
|
||||||
)
|
|
||||||
my_role = user_role(cs.roles)
|
|
||||||
for csu in course_session_users:
|
|
||||||
person_data = result_persons.get(
|
|
||||||
csu.user.id, create_user_dict(csu.user)
|
|
||||||
)
|
|
||||||
person_data["course_sessions"].append(
|
|
||||||
_create_course_session_dict(cs, my_role, csu.role)
|
|
||||||
)
|
|
||||||
result_persons[csu.user.id] = person_data
|
|
||||||
|
|
||||||
# add persons where request.user is mentor
|
|
||||||
for cs in course_sessions:
|
|
||||||
if "LEARNING_MENTOR" in cs.roles:
|
|
||||||
lm = LearningMentor.objects.filter(
|
|
||||||
mentor=user, course_session=cs.id
|
|
||||||
).first()
|
|
||||||
|
|
||||||
for participant in lm.participants.all():
|
|
||||||
course_session_entry = _create_course_session_dict(
|
|
||||||
cs,
|
|
||||||
"LEARNING_MENTOR",
|
|
||||||
"LEARNING_MENTEE",
|
|
||||||
)
|
|
||||||
|
|
||||||
if participant.user.id not in result_persons:
|
|
||||||
person_data = create_user_dict(participant.user)
|
|
||||||
person_data["course_sessions"] = [course_session_entry]
|
|
||||||
result_persons[participant.user.id] = person_data
|
|
||||||
else:
|
|
||||||
# user is already in result_persons
|
|
||||||
result_persons[participant.user.id]["course_sessions"].append(
|
|
||||||
course_session_entry
|
|
||||||
)
|
|
||||||
|
|
||||||
# add persons where request.user is mentee
|
|
||||||
mentor_relation_qs = LearningMentor.objects.filter(
|
|
||||||
participants__user=user
|
|
||||||
).prefetch_related("mentor", "course_session")
|
|
||||||
for mentor_relation in mentor_relation_qs:
|
|
||||||
cs = mentor_relation.course_session
|
|
||||||
course_session_entry = _create_course_session_dict(
|
|
||||||
cs,
|
|
||||||
"LEARNING_MENTEE",
|
|
||||||
"LEARNING_MENTOR",
|
|
||||||
)
|
|
||||||
|
|
||||||
if mentor_relation.mentor.id not in result_persons:
|
|
||||||
person_data = create_user_dict(mentor_relation.mentor)
|
|
||||||
person_data["course_sessions"] = [course_session_entry]
|
|
||||||
result_persons[mentor_relation.mentor.id] = person_data
|
|
||||||
else:
|
|
||||||
# user is already in result_persons
|
|
||||||
result_persons[mentor_relation.mentor.id]["course_sessions"].append(
|
|
||||||
course_session_entry
|
|
||||||
)
|
|
||||||
|
|
||||||
return result_persons.values()
|
|
||||||
|
|
||||||
|
|
||||||
def _persons_list_add_competence_metrics(persons):
|
def _persons_list_add_competence_metrics(persons):
|
||||||
course_session_ids = {cs["id"] for p in persons for cs in p["course_sessions"]}
|
course_session_ids = {cs["id"] for p in persons for cs in p["course_sessions"]}
|
||||||
competence_assignments = query_competence_course_session_assignments(
|
competence_assignments = query_competence_course_session_assignments(
|
||||||
|
|
@ -285,7 +123,7 @@ def _persons_list_add_competence_metrics(persons):
|
||||||
@api_view(["GET"])
|
@api_view(["GET"])
|
||||||
def get_dashboard_persons(request):
|
def get_dashboard_persons(request):
|
||||||
try:
|
try:
|
||||||
persons = list(_create_person_list_with_roles(request.user))
|
persons = list(create_person_list_with_roles(request.user))
|
||||||
|
|
||||||
if request.GET.get("with_competence_metrics", "") == "true":
|
if request.GET.get("with_competence_metrics", "") == "true":
|
||||||
persons = _persons_list_add_competence_metrics(persons)
|
persons = _persons_list_add_competence_metrics(persons)
|
||||||
|
|
@ -307,45 +145,28 @@ def get_dashboard_due_dates(request):
|
||||||
course_sessions = get_course_sessions_with_roles_for_user(request.user)
|
course_sessions = get_course_sessions_with_roles_for_user(request.user)
|
||||||
course_session_ids = [cs.id for cs in course_sessions]
|
course_session_ids = [cs.id for cs in course_sessions]
|
||||||
|
|
||||||
all_due_dates = DueDate.objects.filter(
|
|
||||||
course_session__id__in=course_session_ids
|
|
||||||
)
|
|
||||||
|
|
||||||
# filter only future due dates
|
|
||||||
due_dates = []
|
|
||||||
today = date.today()
|
today = date.today()
|
||||||
for due_date in all_due_dates:
|
|
||||||
# due_dates.append(due_date)
|
|
||||||
if due_date.end:
|
|
||||||
if due_date.end.date() >= today:
|
|
||||||
due_dates.append(due_date)
|
|
||||||
elif due_date.start:
|
|
||||||
if due_date.start.date() >= today:
|
|
||||||
due_dates.append(due_date)
|
|
||||||
|
|
||||||
due_dates.sort(key=lambda x: x.start)
|
# Fetch future due dates in a single query using Q objects for complex filtering
|
||||||
|
future_due_dates = DueDate.objects.filter(
|
||||||
# find course session by id in `course_sessions`
|
Q(course_session_id__in=course_session_ids),
|
||||||
|
Q(end__gte=today) | Q(start__gte=today),
|
||||||
|
).select_related("course_session")
|
||||||
|
|
||||||
result_due_dates = []
|
result_due_dates = []
|
||||||
for due_date in due_dates:
|
course_session_map = {cs.id: cs for cs in course_sessions}
|
||||||
data = DueDateSerializer(due_date).data
|
|
||||||
|
for due_date in sorted(future_due_dates, key=lambda x: x.start):
|
||||||
|
data = DueDateSerializer(due_date).data
|
||||||
|
cs = course_session_map.get(due_date.course_session_id)
|
||||||
|
|
||||||
cs = next(
|
|
||||||
course_session
|
|
||||||
for course_session in course_sessions
|
|
||||||
if course_session.id == due_date.course_session.id
|
|
||||||
)
|
|
||||||
if cs:
|
if cs:
|
||||||
data["course_session"] = _create_course_session_dict(
|
data["course_session"] = create_course_session_dict(
|
||||||
cs, my_role=user_role(cs.roles), user_role=""
|
cs, my_role=user_role(cs.roles), user_role=""
|
||||||
)
|
)
|
||||||
result_due_dates.append(data)
|
result_due_dates.append(data)
|
||||||
|
|
||||||
return Response(
|
return Response(status=200, data=result_due_dates)
|
||||||
status=200,
|
|
||||||
data=result_due_dates,
|
|
||||||
)
|
|
||||||
|
|
||||||
except PermissionDenied as e:
|
except PermissionDenied as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
@ -595,6 +416,20 @@ def export_feedback_as_xsl(request):
|
||||||
return _make_excel_response(data, FEEDBACK_EXPORT_FILE_NAME)
|
return _make_excel_response(data, FEEDBACK_EXPORT_FILE_NAME)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["POST"])
|
||||||
|
def export_persons_as_xsl(request):
|
||||||
|
requested_course_session_ids = request.data.get("courseSessionIds", [])
|
||||||
|
course_sessions_with_roles = _get_permitted_courses_sessions_for_user(
|
||||||
|
request.user, requested_course_session_ids
|
||||||
|
) # noqa
|
||||||
|
|
||||||
|
data = export_persons(
|
||||||
|
request.user,
|
||||||
|
[cswr.id for cswr in course_sessions_with_roles],
|
||||||
|
)
|
||||||
|
return _make_excel_response(data, PERSONS_EXPORT_FILENAME)
|
||||||
|
|
||||||
|
|
||||||
def _get_permitted_courses_sessions_for_user(
|
def _get_permitted_courses_sessions_for_user(
|
||||||
user: User, requested_coursesession_ids: List[str]
|
user: User, requested_coursesession_ids: List[str]
|
||||||
) -> List[CourseSessionWithRoles]:
|
) -> List[CourseSessionWithRoles]:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import time
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
from django.db import connection, reset_queries
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class QueryCountDebugMiddleware:
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
if not request.path.startswith("/api/") and not request.path.startswith(
|
||||||
|
"/server/"
|
||||||
|
):
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
|
reset_queries()
|
||||||
|
start_queries = len(connection.queries)
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
response = self.get_response(request)
|
||||||
|
|
||||||
|
end_queries = len(connection.queries)
|
||||||
|
end_time = time.time()
|
||||||
|
|
||||||
|
total_queries = end_queries - start_queries
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"query_count_middleware",
|
||||||
|
request_path=request.path,
|
||||||
|
queries=total_queries,
|
||||||
|
duration=duration,
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
@ -765,7 +765,7 @@ def create_or_update_course_session_assignment(
|
||||||
csa.submission_deadline.save()
|
csa.submission_deadline.save()
|
||||||
csa.evaluation_deadline.start = timezone.make_aware(
|
csa.evaluation_deadline.start = timezone.make_aware(
|
||||||
start
|
start
|
||||||
) + timezone.timedelta(days=45)
|
) + timezone.timedelta(days=60)
|
||||||
csa.evaluation_deadline.end = None
|
csa.evaluation_deadline.end = None
|
||||||
csa.evaluation_deadline.save()
|
csa.evaluation_deadline.save()
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -78,32 +78,32 @@ def create_customer_xml(checkout_information: CheckoutInformation):
|
||||||
first_name=checkout_information.first_name,
|
first_name=checkout_information.first_name,
|
||||||
company_name=(
|
company_name=(
|
||||||
checkout_information.organisation_detail_name
|
checkout_information.organisation_detail_name
|
||||||
if checkout_information.invoice_address == "org"
|
if checkout_information.abacus_use_organisation_data()
|
||||||
else ""
|
else ""
|
||||||
),
|
),
|
||||||
street=(
|
street=(
|
||||||
checkout_information.organisation_street
|
checkout_information.organisation_street
|
||||||
if checkout_information.invoice_address == "org"
|
if checkout_information.abacus_use_organisation_data()
|
||||||
else checkout_information.street
|
else checkout_information.street
|
||||||
),
|
),
|
||||||
house_number=(
|
house_number=(
|
||||||
checkout_information.organisation_street_number
|
checkout_information.organisation_street_number
|
||||||
if checkout_information.invoice_address == "org"
|
if checkout_information.abacus_use_organisation_data()
|
||||||
else checkout_information.street_number
|
else checkout_information.street_number
|
||||||
),
|
),
|
||||||
zip_code=(
|
zip_code=(
|
||||||
checkout_information.organisation_postal_code
|
checkout_information.organisation_postal_code
|
||||||
if checkout_information.invoice_address == "org"
|
if checkout_information.abacus_use_organisation_data()
|
||||||
else checkout_information.postal_code
|
else checkout_information.postal_code
|
||||||
),
|
),
|
||||||
city=(
|
city=(
|
||||||
checkout_information.organisation_city
|
checkout_information.organisation_city
|
||||||
if checkout_information.invoice_address == "org"
|
if checkout_information.abacus_use_organisation_data()
|
||||||
else checkout_information.city
|
else checkout_information.city
|
||||||
),
|
),
|
||||||
country=(
|
country=(
|
||||||
checkout_information.organisation_country_id
|
checkout_information.organisation_country_id
|
||||||
if checkout_information.invoice_address == "org"
|
if checkout_information.abacus_use_organisation_data()
|
||||||
else checkout_information.country_id
|
else checkout_information.country_id
|
||||||
),
|
),
|
||||||
language=customer.language,
|
language=customer.language,
|
||||||
|
|
|
||||||
|
|
@ -128,3 +128,15 @@ class CheckoutInformation(models.Model):
|
||||||
self.abacus_order_id = new_abacus_order_id
|
self.abacus_order_id = new_abacus_order_id
|
||||||
self.save()
|
self.save()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def abacus_address_type(self) -> str:
|
||||||
|
# always use priv for abacus and CembraPay
|
||||||
|
return (
|
||||||
|
self.INVOICE_ADDRESS_ORGANISATION
|
||||||
|
if self.invoice_address == self.INVOICE_ADDRESS_ORGANISATION
|
||||||
|
and not self.cembra_byjuno_invoice
|
||||||
|
else self.INVOICE_ADDRESS_PRIVATE
|
||||||
|
)
|
||||||
|
|
||||||
|
def abacus_use_organisation_data(self) -> bool:
|
||||||
|
return self.abacus_address_type() == self.INVOICE_ADDRESS_ORGANISATION
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
import uuid
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import structlog
|
import structlog
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from vbv_lernwelt.core.admin import User
|
from vbv_lernwelt.core.admin import User
|
||||||
from vbv_lernwelt.shop.const import VV_PRODUCT_NUMBER
|
|
||||||
from vbv_lernwelt.shop.datatrans.datatrans_api_client import DatatransApiClient
|
from vbv_lernwelt.shop.datatrans.datatrans_api_client import DatatransApiClient
|
||||||
from vbv_lernwelt.shop.models import CheckoutState
|
from vbv_lernwelt.shop.models import CheckoutState
|
||||||
|
|
||||||
|
|
@ -73,6 +71,7 @@ def is_signature_valid(
|
||||||
|
|
||||||
def init_datatrans_transaction(
|
def init_datatrans_transaction(
|
||||||
user: User,
|
user: User,
|
||||||
|
refno: str,
|
||||||
amount_chf_centimes: int,
|
amount_chf_centimes: int,
|
||||||
redirect_url_success: str,
|
redirect_url_success: str,
|
||||||
redirect_url_error: str,
|
redirect_url_error: str,
|
||||||
|
|
@ -91,8 +90,8 @@ def init_datatrans_transaction(
|
||||||
"amount": amount_chf_centimes,
|
"amount": amount_chf_centimes,
|
||||||
"currency": "CHF",
|
"currency": "CHF",
|
||||||
"language": user.language,
|
"language": user.language,
|
||||||
"refno": str(uuid.uuid4()),
|
"refno": str(refno),
|
||||||
"refno2": refno2,
|
"refno2": str(refno2),
|
||||||
"webhook": {"url": webhook_url},
|
"webhook": {"url": webhook_url},
|
||||||
"redirect": {
|
"redirect": {
|
||||||
"successUrl": redirect_url_success,
|
"successUrl": redirect_url_success,
|
||||||
|
|
@ -101,9 +100,8 @@ def init_datatrans_transaction(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# FIXME: test with working cembra byjuno invoice customer?
|
if with_cembra_byjuno_invoice:
|
||||||
# if with_cembra_byjuno_invoice:
|
payload["paymentMethods"] = ["INT"]
|
||||||
# payload["paymentMethods"] = ["INT"]
|
|
||||||
if datatrans_customer_data:
|
if datatrans_customer_data:
|
||||||
payload["customer"] = datatrans_customer_data
|
payload["customer"] = datatrans_customer_data
|
||||||
if datatrans_int_data:
|
if datatrans_int_data:
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,53 @@ class AbacusInvoiceTestCase(TestCase):
|
||||||
assert "<Street>Laupenstrasse</Street>" in customer_xml_content
|
assert "<Street>Laupenstrasse</Street>" in customer_xml_content
|
||||||
assert "<Country>CH</Country>" in customer_xml_content
|
assert "<Country>CH</Country>" in customer_xml_content
|
||||||
|
|
||||||
|
def test_create_customer_xml_byjuno_cembra_with_company_address(self):
|
||||||
|
_pat = User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch")
|
||||||
|
_pat.abacus_debitor_number = 60000011
|
||||||
|
_pat.save()
|
||||||
|
_ignore_checkout_information = CheckoutInformationFactory(
|
||||||
|
user=_pat, abacus_order_id=6_000_000_123
|
||||||
|
)
|
||||||
|
|
||||||
|
feuz = User.objects.get(username="andreas.feuz@eiger-versicherungen.ch")
|
||||||
|
feuz_checkout_info = CheckoutInformationFactory(
|
||||||
|
user=feuz,
|
||||||
|
transaction_id="24021508331287484",
|
||||||
|
first_name="Andreas",
|
||||||
|
last_name="Feuz",
|
||||||
|
street="Eggersmatt",
|
||||||
|
street_number="32",
|
||||||
|
postal_code="1719",
|
||||||
|
city="Zumholz",
|
||||||
|
country_id="CH",
|
||||||
|
invoice_address="org",
|
||||||
|
organisation_detail_name="VBV",
|
||||||
|
organisation_street="Laupenstrasse",
|
||||||
|
organisation_street_number="10",
|
||||||
|
organisation_postal_code="3000",
|
||||||
|
organisation_city="Bern",
|
||||||
|
organisation_country_id="CH",
|
||||||
|
cembra_byjuno_invoice=True,
|
||||||
|
)
|
||||||
|
feuz_checkout_info.created_at = datetime(2024, 2, 15, 8, 33, 12, 0)
|
||||||
|
|
||||||
|
customer_xml_filename, customer_xml_content = create_customer_xml(
|
||||||
|
checkout_information=feuz_checkout_info
|
||||||
|
)
|
||||||
|
print(customer_xml_content)
|
||||||
|
print(customer_xml_filename)
|
||||||
|
|
||||||
|
self.assertEqual("myVBV_debi_60000012.xml", customer_xml_filename)
|
||||||
|
assert "<CustomerNumber>60000012</CustomerNumber>" in customer_xml_content
|
||||||
|
assert (
|
||||||
|
"<Email>andreas.feuz@eiger-versicherungen.ch</Email>"
|
||||||
|
in customer_xml_content
|
||||||
|
)
|
||||||
|
assert "<AddressNumber>60000012</AddressNumber>" in customer_xml_content
|
||||||
|
assert "<Name>Feuz</Name>" in customer_xml_content
|
||||||
|
assert "<Text>VBV</Text>" not in customer_xml_content
|
||||||
|
assert "<Street>Eggersmatt</Street>" in customer_xml_content
|
||||||
|
|
||||||
def test_render_customer_xml(self):
|
def test_render_customer_xml(self):
|
||||||
customer_xml = render_customer_xml(
|
customer_xml = render_customer_xml(
|
||||||
abacus_debitor_number=60000012,
|
abacus_debitor_number=60000012,
|
||||||
|
|
|
||||||
|
|
@ -71,9 +71,8 @@ class CheckoutAPITestCase(APITestCase):
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(
|
self.assertTrue(
|
||||||
f"https://pay.sandbox.datatrans.com/v1/start/1234567890",
|
response.json()["next_step_url"].endswith("v1/start/1234567890")
|
||||||
response.json()["next_step_url"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ci = CheckoutInformation.objects.first()
|
ci = CheckoutInformation.objects.first()
|
||||||
|
|
@ -154,9 +153,11 @@ class CheckoutAPITestCase(APITestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
0,
|
1,
|
||||||
CheckoutInformation.objects.count(),
|
CheckoutInformation.objects.count(),
|
||||||
)
|
)
|
||||||
|
ci = CheckoutInformation.objects.first()
|
||||||
|
self.assertEqual(ci.state, CheckoutState.FAILED)
|
||||||
|
|
||||||
def test_checkout_already_paid(self):
|
def test_checkout_already_paid(self):
|
||||||
# GIVEN
|
# GIVEN
|
||||||
|
|
@ -217,9 +218,8 @@ class CheckoutAPITestCase(APITestCase):
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(
|
self.assertTrue(
|
||||||
f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id_next}",
|
response.json()["next_step_url"].endswith(f"v1/start/{transaction_id_next}")
|
||||||
response.json()["next_step_url"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# check that we have two checkouts
|
# check that we have two checkouts
|
||||||
|
|
@ -277,9 +277,8 @@ class CheckoutAPITestCase(APITestCase):
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(
|
self.assertTrue(
|
||||||
f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id}",
|
response.json()["next_step_url"].endswith(f"v1/start/{transaction_id}")
|
||||||
response.json()["next_step_url"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@patch("vbv_lernwelt.shop.views.init_datatrans_transaction")
|
@patch("vbv_lernwelt.shop.views.init_datatrans_transaction")
|
||||||
|
|
@ -310,7 +309,6 @@ class CheckoutAPITestCase(APITestCase):
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(
|
self.assertTrue(
|
||||||
f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id}",
|
response.json()["next_step_url"].endswith(f"v1/start/{transaction_id}")
|
||||||
response.json()["next_step_url"],
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,8 @@ class DatatransServiceTest(TestCase):
|
||||||
|
|
||||||
@override_settings(DATATRANS_BASIC_AUTH_KEY="BASIC_AUTH_KEY")
|
@override_settings(DATATRANS_BASIC_AUTH_KEY="BASIC_AUTH_KEY")
|
||||||
@patch("vbv_lernwelt.shop.services.requests.post")
|
@patch("vbv_lernwelt.shop.services.requests.post")
|
||||||
@patch("vbv_lernwelt.shop.services.uuid.uuid4")
|
def test_init_transaction_201(self, mock_post):
|
||||||
def test_init_transaction_201(self, mock_uuid, mock_post):
|
|
||||||
# GIVEN
|
# GIVEN
|
||||||
mock_uuid.return_value = uuid.uuid4()
|
|
||||||
mock_post.return_value.status_code = 201
|
mock_post.return_value.status_code = 201
|
||||||
mock_post.return_value.json.return_value = {
|
mock_post.return_value.json.return_value = {
|
||||||
"transactionId": 1234567890,
|
"transactionId": 1234567890,
|
||||||
|
|
@ -38,6 +36,7 @@ class DatatransServiceTest(TestCase):
|
||||||
# WHEN
|
# WHEN
|
||||||
transaction_id = init_datatrans_transaction(
|
transaction_id = init_datatrans_transaction(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
|
refno="123321",
|
||||||
amount_chf_centimes=324_30,
|
amount_chf_centimes=324_30,
|
||||||
redirect_url_success=f"{REDIRECT_URL}/success",
|
redirect_url_success=f"{REDIRECT_URL}/success",
|
||||||
redirect_url_error=f"{REDIRECT_URL}/error",
|
redirect_url_error=f"{REDIRECT_URL}/error",
|
||||||
|
|
@ -64,11 +63,12 @@ class DatatransServiceTest(TestCase):
|
||||||
with self.assertRaises(InitTransactionException):
|
with self.assertRaises(InitTransactionException):
|
||||||
init_datatrans_transaction(
|
init_datatrans_transaction(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
|
refno="123321",
|
||||||
amount_chf_centimes=324_30,
|
amount_chf_centimes=324_30,
|
||||||
redirect_url_success=f"/success",
|
redirect_url_success="/success",
|
||||||
redirect_url_error=f"/error",
|
redirect_url_error="/error",
|
||||||
redirect_url_cancel=f"/cancel",
|
redirect_url_cancel="/cancel",
|
||||||
webhook_url=f"/webhook",
|
webhook_url="/webhook",
|
||||||
refno2="",
|
refno2="",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -80,7 +80,4 @@ class DatatransServiceTest(TestCase):
|
||||||
url = get_payment_url(transaction_id)
|
url = get_payment_url(transaction_id)
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(
|
self.assertTrue(url.endswith(f"v1/start/{transaction_id}"))
|
||||||
url,
|
|
||||||
f"https://pay.sandbox.datatrans.com/v1/start/{transaction_id}",
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ def checkout_vv(request):
|
||||||
sku = request.data["product"]
|
sku = request.data["product"]
|
||||||
base_redirect_url = request.data["redirect_url"]
|
base_redirect_url = request.data["redirect_url"]
|
||||||
|
|
||||||
log.info(f"Checkout requested: sku", user_id=request.user.id, sku=sku)
|
log.info("Checkout requested: sku", user_id=request.user.id, sku=sku)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
product = Product.objects.get(sku=sku)
|
product = Product.objects.get(sku=sku)
|
||||||
|
|
@ -124,6 +124,38 @@ def checkout_vv(request):
|
||||||
disable_save="fakeapi" in settings.DATATRANS_API_ENDPOINT
|
disable_save="fakeapi" in settings.DATATRANS_API_ENDPOINT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
address_data = request.data["address"]
|
||||||
|
country_code = address_data.pop("country_code")
|
||||||
|
address_data["country_id"] = country_code
|
||||||
|
|
||||||
|
organisation_country_code = "CH"
|
||||||
|
if "organisation_country_code" in address_data:
|
||||||
|
organisation_country_code = address_data.pop("organisation_country_code")
|
||||||
|
address_data["organisation_country_id"] = organisation_country_code
|
||||||
|
|
||||||
|
if "birth_date" in address_data and address_data["birth_date"]:
|
||||||
|
address_data["birth_date"] = date.fromisoformat(address_data["birth_date"])
|
||||||
|
|
||||||
|
checkout_info = CheckoutInformation.objects.create(
|
||||||
|
user=request.user,
|
||||||
|
state=CheckoutState.ONGOING,
|
||||||
|
# product
|
||||||
|
product_sku=sku,
|
||||||
|
product_price=product.price,
|
||||||
|
product_name=product.name,
|
||||||
|
product_description=product.description,
|
||||||
|
email=email,
|
||||||
|
ip_address=ip_address,
|
||||||
|
cembra_byjuno_invoice=with_cembra_byjuno_invoice,
|
||||||
|
device_fingerprint_session_key=request.data.get(
|
||||||
|
"device_fingerprint_session_key", ""
|
||||||
|
),
|
||||||
|
# address
|
||||||
|
**request.data["address"],
|
||||||
|
)
|
||||||
|
|
||||||
|
checkout_info.set_increment_abacus_order_id()
|
||||||
|
|
||||||
refno2 = f"{request.user.abacus_debitor_number}_{VV_PRODUCT_NUMBER}"
|
refno2 = f"{request.user.abacus_debitor_number}_{VV_PRODUCT_NUMBER}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -138,10 +170,10 @@ def checkout_vv(request):
|
||||||
"street": f'{request.data["address"]["street"]} {request.data["address"]["street_number"]}',
|
"street": f'{request.data["address"]["street"]} {request.data["address"]["street_number"]}',
|
||||||
"city": request.data["address"]["city"],
|
"city": request.data["address"]["city"],
|
||||||
"zipCode": request.data["address"]["postal_code"],
|
"zipCode": request.data["address"]["postal_code"],
|
||||||
"country": request.data["address"]["country_code"],
|
"country": request.data["address"]["country_id"],
|
||||||
"phone": request.data["address"]["phone_number"],
|
"phone": request.data["address"]["phone_number"],
|
||||||
"email": email,
|
"email": email,
|
||||||
"birthDate": request.data["address"]["birth_date"],
|
"birthDate": str(request.data["address"]["birth_date"]),
|
||||||
"language": request.user.language,
|
"language": request.user.language,
|
||||||
"ipAddress": ip_address,
|
"ipAddress": ip_address,
|
||||||
"type": "P",
|
"type": "P",
|
||||||
|
|
@ -154,6 +186,7 @@ def checkout_vv(request):
|
||||||
}
|
}
|
||||||
transaction_id = init_datatrans_transaction(
|
transaction_id = init_datatrans_transaction(
|
||||||
user=request.user,
|
user=request.user,
|
||||||
|
refno=str(checkout_info.abacus_order_id),
|
||||||
amount_chf_centimes=product.price,
|
amount_chf_centimes=product.price,
|
||||||
redirect_url_success=checkout_success_url(
|
redirect_url_success=checkout_success_url(
|
||||||
base_url=base_redirect_url, product_sku=sku
|
base_url=base_redirect_url, product_sku=sku
|
||||||
|
|
@ -170,6 +203,8 @@ def checkout_vv(request):
|
||||||
with_cembra_byjuno_invoice=with_cembra_byjuno_invoice,
|
with_cembra_byjuno_invoice=with_cembra_byjuno_invoice,
|
||||||
)
|
)
|
||||||
except InitTransactionException as e:
|
except InitTransactionException as e:
|
||||||
|
checkout_info.state = CheckoutState.FAILED.value
|
||||||
|
checkout_info.save()
|
||||||
if not settings.DEBUG:
|
if not settings.DEBUG:
|
||||||
log.error("Transaction initiation failed", exc_info=True, error=str(e))
|
log.error("Transaction initiation failed", exc_info=True, error=str(e))
|
||||||
capture_exception(e)
|
capture_exception(e)
|
||||||
|
|
@ -180,36 +215,8 @@ def checkout_vv(request):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
address_data = request.data["address"]
|
checkout_info.transaction_id = transaction_id
|
||||||
country_code = address_data.pop("country_code")
|
checkout_info.save()
|
||||||
address_data["country_id"] = country_code
|
|
||||||
|
|
||||||
organisation_country_code = "CH"
|
|
||||||
if "organisation_country_code" in address_data:
|
|
||||||
organisation_country_code = address_data.pop("organisation_country_code")
|
|
||||||
address_data["organisation_country_id"] = organisation_country_code
|
|
||||||
|
|
||||||
if "birth_date" in address_data and address_data["birth_date"]:
|
|
||||||
address_data["birth_date"] = date.fromisoformat(address_data["birth_date"])
|
|
||||||
|
|
||||||
checkout_info = CheckoutInformation.objects.create(
|
|
||||||
user=request.user,
|
|
||||||
state=CheckoutState.ONGOING,
|
|
||||||
transaction_id=transaction_id,
|
|
||||||
# product
|
|
||||||
product_sku=sku,
|
|
||||||
product_price=product.price,
|
|
||||||
product_name=product.name,
|
|
||||||
product_description=product.description,
|
|
||||||
email=email,
|
|
||||||
ip_address=ip_address,
|
|
||||||
cembra_byjuno_invoice=with_cembra_byjuno_invoice,
|
|
||||||
device_fingerprint_session_key=request.data.get(
|
|
||||||
"device_fingerprint_session_key", ""
|
|
||||||
),
|
|
||||||
# address
|
|
||||||
**request.data["address"],
|
|
||||||
)
|
|
||||||
|
|
||||||
return next_step_response(url=get_payment_url(transaction_id))
|
return next_step_response(url=get_payment_url(transaction_id))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue