Initial implementation
This commit is contained in:
parent
6f3dac2e97
commit
bd95776ec7
12
README.md
12
README.md
|
|
@ -309,3 +309,15 @@ graphql schema.
|
|||
- The `id` field has to be a string?
|
||||
- What about the generated types from `codegen`? Hand written types seem to be better.
|
||||
- The functions in `cacheExchange` should be nearer the concrete implementation...
|
||||
|
||||
## Load prod data for testing
|
||||
|
||||
1. Checkout the [vbv-devops](https://bitbucket.org/iterativ/iterativ-devops/src/master/) repository
|
||||
2. Change into the `backups` directory
|
||||
3. Run `python3 check_vbv_backup.py`. This downloads the latest backup from S3 and restores it to the `vbv-lernwelt` database.
|
||||
4. Reset all user passwords. Open `shell_plus` in the `server` directory of the `vbv_lernwelt` repository and run
|
||||
|
||||
```python
|
||||
for csu in CourseSessionUser.objects.all():
|
||||
csu.user.set_password("test")
|
||||
```
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.v
|
|||
import type { DashboardCourseConfigType, WidgetType } from "@/services/dashboard";
|
||||
import { getCockpitUrl, getLearningMentorUrl, getLearningPathUrl } from "@/utils/utils";
|
||||
import { computed } from "vue";
|
||||
import TrainingResponsibleStatistics from "./TrainingResponsibleStatistics.vue";
|
||||
|
||||
const mentorWidgets = [
|
||||
"MentorTasksWidget",
|
||||
|
|
@ -71,6 +72,13 @@ const actionButtonProps = computed<{ href: string; text: string; cyKey: string }
|
|||
cyKey: "progress-dashboard-continue-course-link",
|
||||
};
|
||||
}
|
||||
if (props.courseConfig?.role_key === "Ausbildungsverantwortlicher") {
|
||||
return {
|
||||
href: getLearningPathUrl(props.courseConfig?.course_slug),
|
||||
text: "a.Vorschau Teilnehmer",
|
||||
cyKey: "tr-dashboard-link",
|
||||
};
|
||||
}
|
||||
return {
|
||||
href: getLearningPathUrl(props.courseConfig?.course_slug),
|
||||
text: "Weiter lernen",
|
||||
|
|
@ -193,6 +201,12 @@ function hasActionButton(): boolean {
|
|||
:agent-role="courseConfig.role_key"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TrainingResponsibleStatistics
|
||||
v-if="hasWidget('TrainingResponsibleStatisticsWidget')"
|
||||
:course-id="courseConfig.course_id"
|
||||
:course-session-id="courseConfig.session_to_continue_id"
|
||||
></TrainingResponsibleStatistics>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
<script setup lang="ts">
|
||||
import type { TrainingResponsibleStatisticsQuery } from "@/gql/graphql";
|
||||
import { fetchTrainingResponsibleStatistics } from "@/services/dashboard";
|
||||
import { formatCurrencyChfCentimes } from "@/utils/format_currency_chf";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import BaseBox from "./BaseBox.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
courseId: string;
|
||||
courseSessionId: string;
|
||||
}>();
|
||||
|
||||
const statistics = ref<TrainingResponsibleStatisticsQuery | null>(null);
|
||||
|
||||
onMounted(async () => {
|
||||
statistics.value = await fetchTrainingResponsibleStatistics(props.courseSessionId);
|
||||
});
|
||||
|
||||
const totalCostInCurrentYear = computed(() => {
|
||||
return statistics.value?.training_responsible_statistics?.cost_per_year.find(
|
||||
(cost) => cost?.year === new Date().getFullYear()
|
||||
)?.total_cost;
|
||||
});
|
||||
|
||||
const attendanceCountInCurrentYear = computed(() => {
|
||||
return (
|
||||
statistics.value?.training_responsible_statistics?.participants_per_year?.find(
|
||||
(entry) => entry?.year === new Date().getFullYear()
|
||||
)?.participants?.length ?? 0
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col flex-wrap items-stretch md:flex-row">
|
||||
<BaseBox
|
||||
:details-link="`/dashboard/cost/${courseSessionId}`"
|
||||
data-cy="dashboard.mentor.competenceSummary"
|
||||
class="w-1/2"
|
||||
>
|
||||
<template #title>{{ $t("Kosten in") }} {{ new Date().getFullYear() }}</template>
|
||||
<template #content>
|
||||
<div class="flex flex-row space-x-3 bg-white pb-6">
|
||||
<div class="flex h-[74px] items-center justify-center py-1 pr-3">
|
||||
<span class="text-3xl font-bold">
|
||||
{{ formatCurrencyChfCentimes(totalCostInCurrentYear) }}
|
||||
</span>
|
||||
<span class="ml-4">CHF</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BaseBox>
|
||||
<BaseBox
|
||||
:details-link="`/dashboard/persons?course=${courseId}`"
|
||||
data-cy="dashboard.mentor.competenceSummary"
|
||||
>
|
||||
<template #title>{{ $t("Teilnehmer im 2024") }}</template>
|
||||
<template #content>
|
||||
<div class="flex flex-row space-x-3 bg-white pb-6">
|
||||
<div
|
||||
class="flex h-[74px] items-center justify-center py-1 pr-3 text-3xl font-bold"
|
||||
>
|
||||
<span>{{ attendanceCountInCurrentYear }}</span>
|
||||
</div>
|
||||
<p class="ml-3 mt-0 leading-[74px]">
|
||||
{{ $t("Teilnehmer") }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</BaseBox>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -28,6 +28,7 @@ const documents = {
|
|||
"\n query dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n }\n }\n": types.DashboardCourseDataDocument,
|
||||
"\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n region\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n region\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n region\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n region\n assignment_title\n assignment_type_translation_key\n competence_certificate_title\n competence_certificate_id\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_evaluation_percent\n average_passed\n competence_certificate_weight\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n region\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n": types.CourseStatisticsDocument,
|
||||
"\n query mentorCourseStatistics($courseId: ID!, $agentRole: String!) {\n mentor_course_statistics(course_id: $courseId, agent_role: $agentRole) {\n _id\n course_id\n course_title\n course_slug\n course_session_selection_ids\n user_selection_ids\n course_session_properties {\n _id\n sessions {\n id\n name\n region\n }\n generations\n circles {\n id\n name\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n average_evaluation_percent\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n course_session_title\n circle_id\n generation\n region\n assignment_title\n assignment_type_translation_key\n competence_certificate_id\n competence_certificate_title\n details_url\n learning_content_id\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n competence_certificate_weight\n average_evaluation_percent\n average_passed\n }\n }\n }\n }\n }\n": types.MentorCourseStatisticsDocument,
|
||||
"\n query trainingResponsibleStatistics($courseSessionId: ID!) {\n training_responsible_statistics(course_session_id: $courseSessionId) {\n _id\n cost_per_year {\n _id\n year\n total_cost\n }\n participants_per_year {\n _id\n year\n participants {\n id\n chosen_profile\n }\n }\n }\n }\n": types.TrainingResponsibleStatisticsDocument,
|
||||
"\n mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $learningContentType: String!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n learning_content_type: $learningContentType\n data: $data\n submitted: $submitted\n ) {\n feedback_response {\n id\n data\n submitted\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument,
|
||||
};
|
||||
|
||||
|
|
@ -105,6 +106,10 @@ export function graphql(source: "\n query courseStatistics($courseId: ID!) {\n
|
|||
* 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 mentorCourseStatistics($courseId: ID!, $agentRole: String!) {\n mentor_course_statistics(course_id: $courseId, agent_role: $agentRole) {\n _id\n course_id\n course_title\n course_slug\n course_session_selection_ids\n user_selection_ids\n course_session_properties {\n _id\n sessions {\n id\n name\n region\n }\n generations\n circles {\n id\n name\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n average_evaluation_percent\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n course_session_title\n circle_id\n generation\n region\n assignment_title\n assignment_type_translation_key\n competence_certificate_id\n competence_certificate_title\n details_url\n learning_content_id\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n competence_certificate_weight\n average_evaluation_percent\n average_passed\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query mentorCourseStatistics($courseId: ID!, $agentRole: String!) {\n mentor_course_statistics(course_id: $courseId, agent_role: $agentRole) {\n _id\n course_id\n course_title\n course_slug\n course_session_selection_ids\n user_selection_ids\n course_session_properties {\n _id\n sessions {\n id\n name\n region\n }\n generations\n circles {\n id\n name\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n total_passed\n total_failed\n average_evaluation_percent\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n course_session_title\n circle_id\n generation\n region\n assignment_title\n assignment_type_translation_key\n competence_certificate_id\n competence_certificate_title\n details_url\n learning_content_id\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n competence_certificate_weight\n average_evaluation_percent\n average_passed\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.
|
||||
*/
|
||||
export function graphql(source: "\n query trainingResponsibleStatistics($courseSessionId: ID!) {\n training_responsible_statistics(course_session_id: $courseSessionId) {\n _id\n cost_per_year {\n _id\n year\n total_cost\n }\n participants_per_year {\n _id\n year\n participants {\n id\n chosen_profile\n }\n }\n }\n }\n"): (typeof documents)["\n query trainingResponsibleStatistics($courseSessionId: ID!) {\n training_responsible_statistics(course_session_id: $courseSessionId) {\n _id\n cost_per_year {\n _id\n year\n total_cost\n }\n participants_per_year {\n _id\n year\n participants {\n id\n chosen_profile\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* 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
|
|
@ -1,6 +1,7 @@
|
|||
type Query {
|
||||
course_statistics(course_id: ID!): CourseStatisticsType
|
||||
mentor_course_statistics(course_id: ID!, agent_role: String!): BaseStatisticsType
|
||||
training_responsible_statistics(course_session_id: ID!): TrainingResponsibleStatisticsType
|
||||
course_progress(course_id: ID!): CourseProgressType
|
||||
dashboard_config: [DashboardConfigType!]!
|
||||
learning_path(id: ID, slug: String, course_id: ID, course_slug: String): LearningPathObjectType
|
||||
|
|
@ -200,42 +201,61 @@ type BaseStatisticsType {
|
|||
course_session_properties: StatisticsCourseSessionPropertiesType!
|
||||
}
|
||||
|
||||
type CourseProgressType {
|
||||
type TrainingResponsibleStatisticsType {
|
||||
_id: ID!
|
||||
course_id: ID!
|
||||
session_to_continue_id: ID
|
||||
competence: ProgressDashboardCompetenceType
|
||||
assignment: ProgressDashboardAssignmentType
|
||||
course_session_id: ID!
|
||||
cost_per_year: [CostForYear]!
|
||||
participants_per_year: [ParticipantsForYear]!
|
||||
}
|
||||
|
||||
type ProgressDashboardCompetenceType {
|
||||
type CostForYear {
|
||||
_id: ID!
|
||||
total_count: Int!
|
||||
success_count: Int!
|
||||
fail_count: Int!
|
||||
year: Int!
|
||||
total_cost: Int!
|
||||
}
|
||||
|
||||
type ProgressDashboardAssignmentType {
|
||||
type ParticipantsForYear {
|
||||
_id: ID!
|
||||
total_count: Int!
|
||||
points_max_count: Int!
|
||||
points_achieved_count: Int!
|
||||
year: Int!
|
||||
participants: [CourseSessionUserType]!
|
||||
}
|
||||
|
||||
type DashboardConfigType {
|
||||
type CourseSessionUserType {
|
||||
id: UUID!
|
||||
chosen_profile: String!
|
||||
course_session: CourseSessionObjectType!
|
||||
}
|
||||
|
||||
"""
|
||||
Leverages the internal Python implementation of UUID (uuid.UUID) to provide native UUID objects
|
||||
in fields, resolvers and input.
|
||||
"""
|
||||
scalar UUID
|
||||
|
||||
type CourseSessionObjectType {
|
||||
id: ID!
|
||||
name: String!
|
||||
slug: String!
|
||||
dashboard_type: DashboardType!
|
||||
course_configuration: CourseConfigurationObjectType!
|
||||
created_at: DateTime!
|
||||
updated_at: DateTime!
|
||||
course: CourseObjectType!
|
||||
title: String!
|
||||
start_date: Date
|
||||
end_date: Date
|
||||
attendance_courses: [CourseSessionAttendanceCourseObjectType!]!
|
||||
assignments: [CourseSessionAssignmentObjectType!]!
|
||||
edoniq_tests: [CourseSessionEdoniqTestObjectType!]!
|
||||
users: [CourseSessionUserObjectsType!]!
|
||||
}
|
||||
|
||||
enum DashboardType {
|
||||
STATISTICS_DASHBOARD
|
||||
PROGRESS_DASHBOARD
|
||||
SIMPLE_DASHBOARD
|
||||
MENTOR_DASHBOARD
|
||||
PRAXISBILDNER_DASHBOARD
|
||||
type CourseObjectType {
|
||||
id: ID!
|
||||
title: String!
|
||||
category_name: String!
|
||||
slug: String!
|
||||
configuration: CourseConfigurationObjectType!
|
||||
learning_path: LearningPathObjectType!
|
||||
action_competences: [ActionCompetenceObjectType!]!
|
||||
profiles: [String]
|
||||
course_session_users(id: String): [CourseSessionUserType]!
|
||||
}
|
||||
|
||||
type CourseConfigurationObjectType {
|
||||
|
|
@ -270,20 +290,8 @@ interface CoursePageInterface {
|
|||
course: CourseObjectType
|
||||
}
|
||||
|
||||
type CourseObjectType {
|
||||
id: ID!
|
||||
title: String!
|
||||
category_name: String!
|
||||
slug: String!
|
||||
configuration: CourseConfigurationObjectType!
|
||||
learning_path: LearningPathObjectType!
|
||||
action_competences: [ActionCompetenceObjectType!]!
|
||||
profiles: [String]
|
||||
course_session_users(id: String): [CourseSessionUserType]!
|
||||
}
|
||||
|
||||
type ActionCompetenceObjectType implements CoursePageInterface {
|
||||
competence_id: String!
|
||||
type TopicObjectType implements CoursePageInterface {
|
||||
is_visible: Boolean!
|
||||
id: ID!
|
||||
title: String!
|
||||
slug: String!
|
||||
|
|
@ -292,11 +300,13 @@ type ActionCompetenceObjectType implements CoursePageInterface {
|
|||
translation_key: String!
|
||||
frontend_url: String!
|
||||
course: CourseObjectType
|
||||
performance_criteria: [PerformanceCriteriaObjectType!]!
|
||||
circles: [CircleObjectType!]!
|
||||
}
|
||||
|
||||
type PerformanceCriteriaObjectType implements CoursePageInterface {
|
||||
competence_id: String!
|
||||
type CircleObjectType implements CoursePageInterface {
|
||||
description: String!
|
||||
goals: String!
|
||||
is_base_circle: Boolean!
|
||||
id: ID!
|
||||
title: String!
|
||||
slug: String!
|
||||
|
|
@ -305,7 +315,21 @@ type PerformanceCriteriaObjectType implements CoursePageInterface {
|
|||
translation_key: String!
|
||||
frontend_url: String!
|
||||
course: CourseObjectType
|
||||
learning_unit: LearningUnitObjectType
|
||||
learning_sequences: [LearningSequenceObjectType!]!
|
||||
profiles: [String]!
|
||||
}
|
||||
|
||||
type LearningSequenceObjectType implements CoursePageInterface {
|
||||
icon: String!
|
||||
id: ID!
|
||||
title: String!
|
||||
slug: String!
|
||||
content_type: String!
|
||||
live: Boolean!
|
||||
translation_key: String!
|
||||
frontend_url: String!
|
||||
course: CourseObjectType
|
||||
learning_units: [LearningUnitObjectType!]!
|
||||
}
|
||||
|
||||
type LearningUnitObjectType implements CoursePageInterface {
|
||||
|
|
@ -345,30 +369,30 @@ type CircleLightObjectType {
|
|||
slug: String!
|
||||
}
|
||||
|
||||
type CourseSessionUserType {
|
||||
id: UUID!
|
||||
chosen_profile: String!
|
||||
course_session: CourseSessionObjectType!
|
||||
type PerformanceCriteriaObjectType implements CoursePageInterface {
|
||||
competence_id: String!
|
||||
id: ID!
|
||||
title: String!
|
||||
slug: String!
|
||||
content_type: String!
|
||||
live: Boolean!
|
||||
translation_key: String!
|
||||
frontend_url: String!
|
||||
course: CourseObjectType
|
||||
learning_unit: LearningUnitObjectType
|
||||
}
|
||||
|
||||
"""
|
||||
Leverages the internal Python implementation of UUID (uuid.UUID) to provide native UUID objects
|
||||
in fields, resolvers and input.
|
||||
"""
|
||||
scalar UUID
|
||||
|
||||
type CourseSessionObjectType {
|
||||
type ActionCompetenceObjectType implements CoursePageInterface {
|
||||
competence_id: String!
|
||||
id: ID!
|
||||
created_at: DateTime!
|
||||
updated_at: DateTime!
|
||||
course: CourseObjectType!
|
||||
title: String!
|
||||
start_date: Date
|
||||
end_date: Date
|
||||
attendance_courses: [CourseSessionAttendanceCourseObjectType!]!
|
||||
assignments: [CourseSessionAssignmentObjectType!]!
|
||||
edoniq_tests: [CourseSessionEdoniqTestObjectType!]!
|
||||
users: [CourseSessionUserObjectsType!]!
|
||||
slug: String!
|
||||
content_type: String!
|
||||
live: Boolean!
|
||||
translation_key: String!
|
||||
frontend_url: String!
|
||||
course: CourseObjectType
|
||||
performance_criteria: [PerformanceCriteriaObjectType!]!
|
||||
}
|
||||
|
||||
"""
|
||||
|
|
@ -717,46 +741,42 @@ type CourseSessionUserExpertCircleType {
|
|||
slug: String!
|
||||
}
|
||||
|
||||
type TopicObjectType implements CoursePageInterface {
|
||||
is_visible: Boolean!
|
||||
id: ID!
|
||||
title: String!
|
||||
slug: String!
|
||||
content_type: String!
|
||||
live: Boolean!
|
||||
translation_key: String!
|
||||
frontend_url: String!
|
||||
course: CourseObjectType
|
||||
circles: [CircleObjectType!]!
|
||||
type CourseProgressType {
|
||||
_id: ID!
|
||||
course_id: ID!
|
||||
session_to_continue_id: ID
|
||||
competence: ProgressDashboardCompetenceType
|
||||
assignment: ProgressDashboardAssignmentType
|
||||
}
|
||||
|
||||
type CircleObjectType implements CoursePageInterface {
|
||||
description: String!
|
||||
goals: String!
|
||||
is_base_circle: Boolean!
|
||||
id: ID!
|
||||
title: String!
|
||||
slug: String!
|
||||
content_type: String!
|
||||
live: Boolean!
|
||||
translation_key: String!
|
||||
frontend_url: String!
|
||||
course: CourseObjectType
|
||||
learning_sequences: [LearningSequenceObjectType!]!
|
||||
profiles: [String]!
|
||||
type ProgressDashboardCompetenceType {
|
||||
_id: ID!
|
||||
total_count: Int!
|
||||
success_count: Int!
|
||||
fail_count: Int!
|
||||
}
|
||||
|
||||
type LearningSequenceObjectType implements CoursePageInterface {
|
||||
icon: String!
|
||||
type ProgressDashboardAssignmentType {
|
||||
_id: ID!
|
||||
total_count: Int!
|
||||
points_max_count: Int!
|
||||
points_achieved_count: Int!
|
||||
}
|
||||
|
||||
type DashboardConfigType {
|
||||
id: ID!
|
||||
title: String!
|
||||
name: String!
|
||||
slug: String!
|
||||
content_type: String!
|
||||
live: Boolean!
|
||||
translation_key: String!
|
||||
frontend_url: String!
|
||||
course: CourseObjectType
|
||||
learning_units: [LearningUnitObjectType!]!
|
||||
dashboard_type: DashboardType!
|
||||
course_configuration: CourseConfigurationObjectType!
|
||||
}
|
||||
|
||||
enum DashboardType {
|
||||
STATISTICS_DASHBOARD
|
||||
PROGRESS_DASHBOARD
|
||||
SIMPLE_DASHBOARD
|
||||
MENTOR_DASHBOARD
|
||||
PRAXISBILDNER_DASHBOARD
|
||||
}
|
||||
|
||||
type LearningContentMediaLibraryObjectType implements CoursePageInterface & LearningContentInterface {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export const CompetenceRecordStatisticsType = "CompetenceRecordStatisticsType";
|
|||
export const CompetencesStatisticsType = "CompetencesStatisticsType";
|
||||
export const ContentDocumentObjectType = "ContentDocumentObjectType";
|
||||
export const CoreUserLanguageChoices = "CoreUserLanguageChoices";
|
||||
export const CostForYear = "CostForYear";
|
||||
export const CourseConfigurationObjectType = "CourseConfigurationObjectType";
|
||||
export const CourseObjectType = "CourseObjectType";
|
||||
export const CoursePageInterface = "CoursePageInterface";
|
||||
|
|
@ -74,6 +75,7 @@ export const LearningSequenceObjectType = "LearningSequenceObjectType";
|
|||
export const LearningUnitObjectType = "LearningUnitObjectType";
|
||||
export const LearnpathLearningContentAssignmentAssignmentTypeChoices = "LearnpathLearningContentAssignmentAssignmentTypeChoices";
|
||||
export const Mutation = "Mutation";
|
||||
export const ParticipantsForYear = "ParticipantsForYear";
|
||||
export const PerformanceCriteriaObjectType = "PerformanceCriteriaObjectType";
|
||||
export const PresenceRecordStatisticsType = "PresenceRecordStatisticsType";
|
||||
export const ProgressDashboardAssignmentType = "ProgressDashboardAssignmentType";
|
||||
|
|
@ -86,6 +88,7 @@ export const StatisticsCourseSessionPropertiesType = "StatisticsCourseSessionPro
|
|||
export const StatisticsCourseSessionsSelectionMetricType = "StatisticsCourseSessionsSelectionMetricType";
|
||||
export const String = "String";
|
||||
export const TopicObjectType = "TopicObjectType";
|
||||
export const TrainingResponsibleStatisticsType = "TrainingResponsibleStatisticsType";
|
||||
export const UUID = "UUID";
|
||||
export const UpdateCourseProfileError = "UpdateCourseProfileError";
|
||||
export const UpdateCourseProfileResult = "UpdateCourseProfileResult";
|
||||
|
|
|
|||
|
|
@ -607,3 +607,24 @@ export const DASHBOARD_MENTOR_COMPETENCE_SUMMARY = graphql(`
|
|||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const TRAINING_RESPONSIBLE_STATISTICS = graphql(`
|
||||
query trainingResponsibleStatistics($courseSessionId: ID!) {
|
||||
training_responsible_statistics(course_session_id: $courseSessionId) {
|
||||
_id
|
||||
cost_per_year {
|
||||
_id
|
||||
year
|
||||
total_cost
|
||||
}
|
||||
participants_per_year {
|
||||
_id
|
||||
year
|
||||
participants {
|
||||
id
|
||||
chosen_profile
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
<script setup lang="ts">
|
||||
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||
import type { TrainingResponsibleStatisticsQuery } from "@/gql/graphql";
|
||||
import { fetchTrainingResponsibleStatistics } from "@/services/dashboard";
|
||||
import { formatCurrencyChfCentimes } from "@/utils/format_currency_chf";
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSessionId: string;
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const statistics = ref<TrainingResponsibleStatisticsQuery | null>(null);
|
||||
|
||||
onMounted(async () => {
|
||||
statistics.value = await fetchTrainingResponsibleStatistics(props.courseSessionId);
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
const participantsForYear = (year: number) => {
|
||||
return statistics.value?.training_responsible_statistics?.participants_per_year.find(
|
||||
(entry) => entry?.year === year
|
||||
)?.participants.length;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="loading" class="m-8 flex justify-center">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
<div v-else class="h-screen bg-gray-200">
|
||||
<div class="container-large">
|
||||
<router-link
|
||||
:to="`/`"
|
||||
class="btn-text inline-flex items-center p-0"
|
||||
data-cy="back-to-learning-path-button"
|
||||
>
|
||||
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
|
||||
<span class="inline">{{ $t("general.back") }}</span>
|
||||
</router-link>
|
||||
<h2 class="my-8">{{ $t("a.Kosten") }}</h2>
|
||||
<div class="bg-white p-6">
|
||||
<div
|
||||
v-for="entry in statistics?.training_responsible_statistics?.cost_per_year"
|
||||
:key="entry?.year"
|
||||
data-cy="year"
|
||||
class="w-full border-b pb-2 pt-2 first:pt-0 last:border-b-0 last:pb-0"
|
||||
>
|
||||
<div class="flex w-full flex-row justify-between">
|
||||
<p class="text-base">{{ entry?.year }}</p>
|
||||
<p class="text-base">
|
||||
{{ participantsForYear(entry?.year ?? 0) }} {{ $t("a.Teilnehmer") }}
|
||||
</p>
|
||||
<p class="text-base font-bold">
|
||||
{{ formatCurrencyChfCentimes(entry?.total_cost) }} CHF
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -168,6 +168,29 @@ const roles = computed(() => {
|
|||
});
|
||||
const selectedRole = ref<MenuItem>(roles.value[0]);
|
||||
|
||||
const chosenProfiles = computed(() => {
|
||||
const values = _(dashboardPersons.value)
|
||||
.map((cs) => {
|
||||
return Object.assign({}, cs, {
|
||||
name: cs.chosen_profile,
|
||||
id: cs.chosen_profile,
|
||||
});
|
||||
})
|
||||
.filter((cs) => cs.chosen_profile !== "all")
|
||||
.uniqBy("id")
|
||||
.orderBy("name")
|
||||
.value();
|
||||
|
||||
return [
|
||||
{
|
||||
id: "all",
|
||||
name: `${t("Zulassungsprofil")}: ${t("a.Alle")}`,
|
||||
},
|
||||
...values,
|
||||
];
|
||||
});
|
||||
const selectedChosenProfile = ref<MenuItem>(chosenProfiles.value[0]);
|
||||
|
||||
const filteredPersons = computed(() => {
|
||||
return _.orderBy(
|
||||
dashboardPersons.value
|
||||
|
|
@ -208,6 +231,12 @@ const filteredPersons = computed(() => {
|
|||
return person.course_sessions.some(
|
||||
(cs) => cs.user_role_display === selectedRole.value.id
|
||||
);
|
||||
})
|
||||
.filter((person) => {
|
||||
if (selectedChosenProfile.value.id === "") {
|
||||
return true;
|
||||
}
|
||||
return person.chosen_profile === selectedChosenProfile.value.id;
|
||||
}),
|
||||
["last_name", "first_name"]
|
||||
);
|
||||
|
|
@ -219,7 +248,8 @@ const filtersVisible = computed(() => {
|
|||
courseSessions.value.length > 2 ||
|
||||
regions.value.length > 2 ||
|
||||
generations.value.length > 2 ||
|
||||
roles.value.length > 2
|
||||
roles.value.length > 2 ||
|
||||
chosenProfiles.value.length > 2
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -342,6 +372,14 @@ watch(selectedRegion, () => {
|
|||
:items="roles"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
|
||||
<ItDropdownSelect
|
||||
v-if="chosenProfiles.length > 2"
|
||||
v-model="selectedChosenProfile"
|
||||
data-cy="select-chosen-profile"
|
||||
:items="chosenProfiles"
|
||||
borderless
|
||||
></ItDropdownSelect>
|
||||
</section>
|
||||
<div
|
||||
v-for="person in filteredPersons"
|
||||
|
|
|
|||
|
|
@ -73,6 +73,11 @@ const router = createRouter({
|
|||
path: "/dashboard/due-dates",
|
||||
component: () => import("@/pages/dashboard/DashboardDueDatesPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "/dashboard/cost/:courseSessionId",
|
||||
component: () => import("@/pages/dashboard/DashboardCostPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/media",
|
||||
props: true,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
DASHBOARD_COURSE_SESSION_PROGRESS,
|
||||
DASHBOARD_COURSE_STATISTICS,
|
||||
DASHBOARD_MENTOR_COMPETENCE_SUMMARY,
|
||||
TRAINING_RESPONSIBLE_STATISTICS,
|
||||
} from "@/graphql/queries";
|
||||
|
||||
import { itGetCached, itPost } from "@/fetchHelpers";
|
||||
|
|
@ -12,6 +13,8 @@ import type {
|
|||
CourseProgressType,
|
||||
CourseStatisticsType,
|
||||
DashboardConfigType,
|
||||
TrainingResponsibleStatisticsQuery,
|
||||
TrainingResponsibleStatisticsType,
|
||||
} from "@/gql/graphql";
|
||||
import type {
|
||||
DashboardPersonsPageMode,
|
||||
|
|
@ -34,7 +37,8 @@ export type DashboardRoleKeyType =
|
|||
| "Member"
|
||||
| "MentorUK"
|
||||
| "MentorVV"
|
||||
| "Berufsbildner";
|
||||
| "Berufsbildner"
|
||||
| "Ausbildungsverantwortlicher";
|
||||
|
||||
export type WidgetType =
|
||||
| "ProgressWidget"
|
||||
|
|
@ -44,7 +48,8 @@ export type WidgetType =
|
|||
| "MentorCompetenceWidget"
|
||||
| "CompetenceCertificateWidget"
|
||||
| "UKStatisticsWidget"
|
||||
| "UKBerufsbildnerStatisticsWidget";
|
||||
| "UKBerufsbildnerStatisticsWidget"
|
||||
| "TrainingResponsibleStatisticsWidget";
|
||||
|
||||
export type DashboardPersonCourseSessionType = {
|
||||
id: string;
|
||||
|
|
@ -78,6 +83,7 @@ export type DashboardPersonType = {
|
|||
passed_count: number;
|
||||
failed_count: number;
|
||||
};
|
||||
chosen_profile: string;
|
||||
};
|
||||
|
||||
export type DashboardCourseConfigType = {
|
||||
|
|
@ -118,6 +124,31 @@ export const fetchStatisticData = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const fetchTrainingResponsibleStatistics = async (
|
||||
courseSessionId: string
|
||||
): Promise<TrainingResponsibleStatisticsQuery | null> => {
|
||||
try {
|
||||
const res = await graphqlClient.query(TRAINING_RESPONSIBLE_STATISTICS, {
|
||||
courseSessionId,
|
||||
});
|
||||
|
||||
if (res.error) {
|
||||
console.error(
|
||||
"Error fetching training responsible statistics for course session ID:",
|
||||
courseSessionId,
|
||||
res.error
|
||||
);
|
||||
}
|
||||
return res.data || null;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error fetching training responsible statistics for course session ID: ${courseSessionId}`,
|
||||
error
|
||||
);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchProgressData = async (
|
||||
courseId: string
|
||||
): Promise<CourseProgressType | null> => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
export function formatCurrencyChfCentimes(amount: number | undefined) {
|
||||
if (!amount) {
|
||||
return formatCurrencyChf(undefined);
|
||||
}
|
||||
return formatCurrencyChf(amount / 100);
|
||||
}
|
||||
|
||||
// 10378.2 => 10'378
|
||||
export function formatCurrencyChf(amount: number | undefined) {
|
||||
if (!amount) {
|
||||
return "?";
|
||||
}
|
||||
return new Intl.NumberFormat("de-CH", {
|
||||
style: "decimal",
|
||||
maximumFractionDigits: 0,
|
||||
}).format(amount);
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
import django
|
||||
from django.contrib.auth.hashers import make_password
|
||||
|
||||
sys.path.append("../server")
|
||||
|
||||
os.environ.setdefault("IT_APP_ENVIRONMENT", "local")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.base")
|
||||
django.setup()
|
||||
|
||||
from vbv_lernwelt.course.consts import UK_COURSE_IDS
|
||||
from vbv_lernwelt.course.models import CourseSessionUser
|
||||
from vbv_lernwelt.core.admin import User
|
||||
from vbv_lernwelt.learning_mentor.models import (
|
||||
AgentParticipantRelation,
|
||||
AgentParticipantRoleType,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
berufsbildner, _ = User.objects.get_or_create(
|
||||
id="5f984be9-3024-4169-9c7b-c9e827c18fd8"
|
||||
)
|
||||
berufsbildner.username = "training-responsible-mobi@example.com"
|
||||
berufsbildner.email = "training-responsible-mobi@example.com"
|
||||
berufsbildner.language = "de"
|
||||
berufsbildner.first_name = "Ausbildungsverantwortlicher"
|
||||
berufsbildner.last_name = "Mobi"
|
||||
berufsbildner.password = make_password("test")
|
||||
berufsbildner.save()
|
||||
|
||||
for csu in (
|
||||
CourseSessionUser.objects.filter(user__username__contains="@mobi")
|
||||
.filter(course_session__course__configuration__is_uk=False)
|
||||
.filter(role=CourseSessionUser.Role.MEMBER.value)
|
||||
.exclude(course_session_id__in=UK_COURSE_IDS)
|
||||
):
|
||||
AgentParticipantRelation.objects.get_or_create(
|
||||
agent=berufsbildner,
|
||||
participant=csu,
|
||||
role=AgentParticipantRoleType.BERUFSBILDNER.value,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -22,17 +22,17 @@ def main():
|
|||
berufsbildner, _ = User.objects.get_or_create(
|
||||
id="5f984be9-3024-4169-9c7b-c9e827c18fd8"
|
||||
)
|
||||
berufsbildner.username = "berufsbildner-mobi@example.com"
|
||||
berufsbildner.email = "berufsbildner-mobi@example.com"
|
||||
berufsbildner.username = "training-responsible-mobi@example.com"
|
||||
berufsbildner.email = "training-responsible-mobi@example.com"
|
||||
berufsbildner.language = "de"
|
||||
berufsbildner.first_name = "Berufsbildner"
|
||||
berufsbildner.first_name = "Ausbildungsverantwortlicher"
|
||||
berufsbildner.last_name = "Mobi"
|
||||
berufsbildner.password = make_password("test")
|
||||
berufsbildner.save()
|
||||
|
||||
for csu in (
|
||||
CourseSessionUser.objects.filter(user__username__contains="@mobi")
|
||||
.filter(course_session__course__configuration__is_uk=True)
|
||||
.filter(course_session__course__configuration__is_uk=False)
|
||||
.filter(role=CourseSessionUser.Role.MEMBER.value)
|
||||
.exclude(course_session_id__in=[4, 5, 6])
|
||||
):
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ TEST_STUDENT2_USER_ID = "19c40d94-15cc-4198-aaad-ef707c4b0900"
|
|||
TEST_STUDENT3_USER_ID = "bcf94dba-53bc-474b-a22d-e4af39aa042b"
|
||||
TEST_MENTOR1_USER_ID = "d1f5f5a9-5b0a-4e1a-9e1a-9e9b5b5e1b1b"
|
||||
TEST_BERUFSBILDNER1_USER_ID = "bb83dde0-27e7-4859-8acb-a323025d712c"
|
||||
TEST_LERNBEGLEITER1_USER_ID = "ffeedde0-27e7-ff59-8aff-a3230ffd712c"
|
||||
TEST_STUDENT1_VV_USER_ID = "5ff59857-8de5-415e-a387-4449f9a0337a"
|
||||
TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID = "7e8ebf0b-e6e2-4022-88f4-6e663ba0a9db"
|
||||
TEST_USER_EMPTY_ID = "daecbabe-4ab9-4edf-a71f-4119042ccb02"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from environs import Env
|
|||
from vbv_lernwelt.core.constants import (
|
||||
ADMIN_USER_ID,
|
||||
TEST_BERUFSBILDNER1_USER_ID,
|
||||
TEST_LERNBEGLEITER1_USER_ID,
|
||||
TEST_MENTOR1_USER_ID,
|
||||
TEST_STUDENT1_USER_ID,
|
||||
TEST_STUDENT1_VV_USER_ID,
|
||||
|
|
@ -419,6 +420,15 @@ def create_default_users(default_password="test", set_avatar=False):
|
|||
language="de",
|
||||
avatar_image="uk1.patrizia.huggel.jpg",
|
||||
)
|
||||
_create_user(
|
||||
_id=TEST_LERNBEGLEITER1_USER_ID,
|
||||
email="test-lernbegleiter1@example.com",
|
||||
first_name="Bruno",
|
||||
last_name="Banani-Lernbegleiter",
|
||||
password=default_password,
|
||||
language="de",
|
||||
avatar_image="uk1.patrizia.huggel.jpg",
|
||||
)
|
||||
_create_student_user(
|
||||
id=TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID,
|
||||
email="test-student-and-mentor2@example.com",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from hashlib import md5
|
||||
from typing import Dict, List, Set, Tuple
|
||||
|
||||
import graphene
|
||||
|
|
@ -19,6 +20,7 @@ from vbv_lernwelt.dashboard.graphql.types.dashboard import (
|
|||
DashboardType,
|
||||
ProgressDashboardAssignmentType,
|
||||
ProgressDashboardCompetenceType,
|
||||
TrainingResponsibleStatisticsType,
|
||||
)
|
||||
from vbv_lernwelt.iam.permissions import (
|
||||
can_view_course_session,
|
||||
|
|
@ -69,6 +71,11 @@ class DashboardQuery(graphene.ObjectType):
|
|||
agent_role=graphene.String(required=True),
|
||||
)
|
||||
|
||||
training_responsible_statistics = graphene.Field(
|
||||
TrainingResponsibleStatisticsType,
|
||||
course_session_id=graphene.ID(required=True),
|
||||
)
|
||||
|
||||
course_progress = graphene.Field(
|
||||
CourseProgressType, course_id=graphene.ID(required=True)
|
||||
)
|
||||
|
|
@ -124,6 +131,13 @@ class DashboardQuery(graphene.ObjectType):
|
|||
|
||||
return _agent_course_statistics(user, course_id, role=agent_role)
|
||||
|
||||
@staticmethod
|
||||
def resolve_training_responsible_statistics(root, info, course_session_id: str): # noqa
|
||||
return TrainingResponsibleStatisticsType(
|
||||
_id=course_session_id, # noqa
|
||||
course_session_id=course_session_id, # noqa
|
||||
)
|
||||
|
||||
def resolve_dashboard_config(root, info): # noqa
|
||||
user = info.context.user
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
from datetime import datetime
|
||||
from itertools import groupby
|
||||
import graphene
|
||||
from graphene import Enum
|
||||
|
||||
from vbv_lernwelt.course.graphql.types import CourseConfigurationObjectType
|
||||
from vbv_lernwelt.core.admin import User
|
||||
from vbv_lernwelt.course.graphql.types import (
|
||||
CourseConfigurationObjectType,
|
||||
CourseSessionUserType,
|
||||
)
|
||||
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
||||
from vbv_lernwelt.dashboard.graphql.types.assignment import (
|
||||
AssignmentsStatisticsType,
|
||||
|
|
@ -20,7 +26,13 @@ from vbv_lernwelt.dashboard.graphql.types.feedback import (
|
|||
FeedbackStatisticsResponsesType,
|
||||
feedback_responses,
|
||||
)
|
||||
from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation
|
||||
from vbv_lernwelt.learnpath.models import Circle
|
||||
from vbv_lernwelt.shop.models import CheckoutInformation, CheckoutState
|
||||
from vbv_lernwelt.shop.views import (
|
||||
COURSE_SESSION_ID_TO_PRODUCT_SKU,
|
||||
PRODUCT_SKU_TO_COURSE_SESSION_ID,
|
||||
)
|
||||
|
||||
|
||||
class StatisticsCourseSessionDataType(graphene.ObjectType):
|
||||
|
|
@ -242,3 +254,75 @@ class CourseStatisticsType(BaseStatisticsType):
|
|||
participant_count=participant_count, # noqa
|
||||
expert_count=expert_count, # noqa
|
||||
)
|
||||
|
||||
|
||||
class CostForYear(graphene.ObjectType):
|
||||
_id = graphene.ID(required=True)
|
||||
year = graphene.Int(required=True)
|
||||
# In centimes CHF
|
||||
total_cost = graphene.Int(required=True)
|
||||
|
||||
|
||||
class ParticipantsForYear(graphene.ObjectType):
|
||||
_id = graphene.ID(required=True)
|
||||
year = graphene.Int(required=True)
|
||||
participants = graphene.List(CourseSessionUserType, required=True)
|
||||
|
||||
|
||||
class TrainingResponsibleStatisticsType(graphene.ObjectType):
|
||||
_id = graphene.ID(required=True)
|
||||
course_session_id = graphene.ID(required=True)
|
||||
|
||||
cost_per_year = graphene.List(CostForYear, required=True)
|
||||
participants_per_year = graphene.List(ParticipantsForYear, required=True)
|
||||
|
||||
@staticmethod
|
||||
def resolve_cost_per_year(root, info): # noqa
|
||||
user = info.context.user
|
||||
relations_qs = AgentParticipantRelation.objects.filter(
|
||||
agent=user,
|
||||
participant__course_session=root.course_session_id,
|
||||
)
|
||||
users = relations_qs.values_list("participant__user", flat=True)
|
||||
|
||||
sku = COURSE_SESSION_ID_TO_PRODUCT_SKU.get(int(root.course_session_id))
|
||||
checkout_information = CheckoutInformation.objects.filter(
|
||||
state=CheckoutState.PAID,
|
||||
product_sku=sku,
|
||||
user__in=users,
|
||||
)
|
||||
|
||||
grouped_checkouts = groupby(
|
||||
sorted(checkout_information, key=lambda x: x.created_at.year),
|
||||
key=lambda x: x.created_at.year,
|
||||
)
|
||||
return [
|
||||
CostForYear(
|
||||
_id=f"{root.course_session_id} {year}",
|
||||
year=year,
|
||||
total_cost=sum(c.product_price for c in checkouts),
|
||||
)
|
||||
for year, checkouts in grouped_checkouts
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def resolve_participants_per_year(root, info):
|
||||
user = info.context.user
|
||||
course_session_users = CourseSessionUser.objects.filter(
|
||||
agentparticipantrelation__agent=user,
|
||||
agentparticipantrelation__participant__course_session=root.course_session_id,
|
||||
)
|
||||
|
||||
grouped_course_session_users = groupby(
|
||||
sorted(course_session_users, key=lambda x: x.created_at.year),
|
||||
key=lambda x: x.created_at.year,
|
||||
)
|
||||
|
||||
return [
|
||||
ParticipantsForYear(
|
||||
_id=f"{root.course_session_id} {year}",
|
||||
year=year,
|
||||
participants=list(c),
|
||||
)
|
||||
for year, c in grouped_course_session_users
|
||||
]
|
||||
|
|
|
|||
|
|
@ -98,7 +98,8 @@ def create_course_session_dict(course_session_object, my_role, user_role):
|
|||
def create_person_list_with_roles(
|
||||
user, course_session_ids=None, include_private_data=False
|
||||
):
|
||||
def create_user_dict(user_object):
|
||||
def create_user_dict(csu: CourseSessionUser):
|
||||
user_object = csu.user
|
||||
user_data = {
|
||||
"user_id": user_object.id,
|
||||
"first_name": user_object.first_name,
|
||||
|
|
@ -107,6 +108,7 @@ def create_person_list_with_roles(
|
|||
"avatar_url_small": user_object.avatar_url_small,
|
||||
"avatar_url": user_object.avatar_url,
|
||||
"course_sessions": [],
|
||||
"chosen_profile": csu.chosen_profile.code if csu.chosen_profile else "all",
|
||||
}
|
||||
if include_private_data:
|
||||
user_data["phone_number"] = user_object.phone_number
|
||||
|
|
@ -130,9 +132,7 @@ def create_person_list_with_roles(
|
|||
).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 = result_persons.get(csu.user.id, create_user_dict(csu))
|
||||
person_data["course_sessions"].append(
|
||||
create_course_session_dict(cs, my_role, csu.role)
|
||||
)
|
||||
|
|
@ -146,7 +146,7 @@ def create_person_list_with_roles(
|
|||
participant_user = relation.participant.user
|
||||
|
||||
if participant_user.id not in result_persons:
|
||||
person_data = create_user_dict(participant_user)
|
||||
person_data = create_user_dict(relation.participant)
|
||||
person_data["course_sessions"] = [course_session_entry]
|
||||
result_persons[participant_user.id] = person_data
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ class WidgetType(Enum):
|
|||
COMPETENCE_CERTIFICATE_WIDGET = "CompetenceCertificateWidget"
|
||||
UK_STATISTICS_WIDGET = "UKStatisticsWidget"
|
||||
UK_BERUFSBILDNER_STATISTICS_WIDGET = "UKBerufsbildnerStatisticsWidget"
|
||||
TRAINING_RESPONSIBLE_STATISTICS_WIDGET = "TrainingResponsibleStatisticsWidget"
|
||||
|
||||
|
||||
class RoleKeyType(Enum):
|
||||
|
|
@ -71,6 +72,7 @@ class RoleKeyType(Enum):
|
|||
SUPERVISOR = "Supervisor"
|
||||
TRAINER = "Trainer"
|
||||
BERUFSBILDNER = "Berufsbildner"
|
||||
TRAINING_RESPONSIBLE = "Ausbildungsverantwortlicher"
|
||||
UNKNOWN_ROLE_KEY = "UnknownRoleKey"
|
||||
|
||||
|
||||
|
|
@ -210,6 +212,8 @@ def get_widgets_for_course(
|
|||
if "BERUFSBILDNER" in relation_roles:
|
||||
if is_uk:
|
||||
widgets.append(WidgetType.UK_BERUFSBILDNER_STATISTICS_WIDGET.value)
|
||||
if is_vv:
|
||||
widgets.append(WidgetType.TRAINING_RESPONSIBLE_STATISTICS_WIDGET.value)
|
||||
|
||||
return widgets
|
||||
|
||||
|
|
@ -237,6 +241,8 @@ def get_relevant_role_key(
|
|||
elif "BERUFSBILDNER" in relation_roles:
|
||||
if is_uk:
|
||||
return RoleKeyType.BERUFSBILDNER
|
||||
elif is_vv:
|
||||
return RoleKeyType.TRAINING_RESPONSIBLE
|
||||
|
||||
return RoleKeyType.UNKNOWN_ROLE_KEY
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ PRODUCT_SKU_TO_COURSE_SESSION_ID = {
|
|||
VV_IT_PRODUCT_SKU: 3, # vv-it
|
||||
}
|
||||
|
||||
COURSE_SESSION_ID_TO_PRODUCT_SKU = {
|
||||
id: sku for sku, id in PRODUCT_SKU_TO_COURSE_SESSION_ID.items()
|
||||
}
|
||||
|
||||
|
||||
@api_view(["POST"])
|
||||
def transaction_webhook(request):
|
||||
|
|
|
|||
Loading…
Reference in New Issue