Merge branch 'develop' into feature/vbv-676-berufsbildner-2
# Conflicts: # client/src/composables.ts # client/src/gql/gql.ts # client/src/gql/graphql.ts # client/src/graphql/queries.ts # client/src/pages/competence/CompetenceCertificateDetailPage.vue # client/src/pages/competence/CompetenceCertificateListPage.vue # client/src/pages/competence/CompetenceIndexPage.vue # client/src/types.ts # cypress/support/commands.js # server/vbv_lernwelt/shop/migrations/0016_alter_checkoutinformation_refno2.py
This commit is contained in:
commit
aca066a376
|
|
@ -10,6 +10,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/tailwindcss": "^0.2.1",
|
"@headlessui/tailwindcss": "^0.2.1",
|
||||||
"@headlessui/vue": "^1.7.22",
|
"@headlessui/vue": "^1.7.22",
|
||||||
|
"@parcel/watcher": "^2.4.1",
|
||||||
"@sentry/tracing": "^7.114.0",
|
"@sentry/tracing": "^7.114.0",
|
||||||
"@sentry/vue": "^8.17.0",
|
"@sentry/vue": "^8.17.0",
|
||||||
"@urql/exchange-graphcache": "^7.1.2",
|
"@urql/exchange-graphcache": "^7.1.2",
|
||||||
|
|
@ -5722,7 +5723,6 @@
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fill-range": "^7.0.1"
|
"fill-range": "^7.0.1"
|
||||||
},
|
},
|
||||||
|
|
@ -8258,7 +8258,6 @@
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
},
|
},
|
||||||
|
|
@ -9317,7 +9316,6 @@
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
|
@ -9334,7 +9332,6 @@
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
},
|
},
|
||||||
|
|
@ -9388,7 +9385,6 @@
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
}
|
}
|
||||||
|
|
@ -10334,7 +10330,6 @@
|
||||||
"version": "4.0.5",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||||
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"braces": "^3.0.2",
|
"braces": "^3.0.2",
|
||||||
"picomatch": "^2.3.1"
|
"picomatch": "^2.3.1"
|
||||||
|
|
@ -11072,7 +11067,6 @@
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
},
|
},
|
||||||
|
|
@ -12672,7 +12666,6 @@
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -18806,7 +18799,6 @@
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"fill-range": "^7.0.1"
|
"fill-range": "^7.0.1"
|
||||||
}
|
}
|
||||||
|
|
@ -20667,7 +20659,6 @@
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
}
|
}
|
||||||
|
|
@ -21425,8 +21416,7 @@
|
||||||
"is-extglob": {
|
"is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"is-fullwidth-code-point": {
|
"is-fullwidth-code-point": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
|
|
@ -21437,7 +21427,6 @@
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
}
|
}
|
||||||
|
|
@ -21475,8 +21464,7 @@
|
||||||
"is-number": {
|
"is-number": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"is-number-object": {
|
"is-number-object": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
|
|
@ -22174,7 +22162,6 @@
|
||||||
"version": "4.0.5",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||||
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"braces": "^3.0.2",
|
"braces": "^3.0.2",
|
||||||
"picomatch": "^2.3.1"
|
"picomatch": "^2.3.1"
|
||||||
|
|
@ -22727,8 +22714,7 @@
|
||||||
"picomatch": {
|
"picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"pify": {
|
"pify": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
|
|
@ -23824,7 +23810,6 @@
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@
|
||||||
"cypress:open": "cypress open",
|
"cypress:open": "cypress open",
|
||||||
"dev": "concurrently \"vite\" \"npm run codegen:watch\"",
|
"dev": "concurrently \"vite\" \"npm run codegen:watch\"",
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
|
"lint:errors": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --quiet --ignore-path .gitignore",
|
||||||
"prettier": "prettier . --write",
|
"prettier": "prettier . --write",
|
||||||
"prettier:check": "prettier . --check",
|
"prettier:check": "prettier . --check --ignore-unknown",
|
||||||
"tailwind": "tailwindcss -i tailwind.css -o ../server/vbv_lernwelt/static/css/tailwind.css --watch",
|
"tailwind": "tailwindcss -i tailwind.css -o ../server/vbv_lernwelt/static/css/tailwind.css --watch",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"typecheck": "npm run codegen && vue-tsc --noEmit -p tsconfig.app.json --composite false",
|
"typecheck": "npm run codegen && vue-tsc --noEmit -p tsconfig.app.json --composite false",
|
||||||
|
|
@ -20,6 +21,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/tailwindcss": "^0.2.1",
|
"@headlessui/tailwindcss": "^0.2.1",
|
||||||
"@headlessui/vue": "^1.7.22",
|
"@headlessui/vue": "^1.7.22",
|
||||||
|
"@parcel/watcher": "^2.4.1",
|
||||||
"@sentry/tracing": "^7.114.0",
|
"@sentry/tracing": "^7.114.0",
|
||||||
"@sentry/vue": "^8.17.0",
|
"@sentry/vue": "^8.17.0",
|
||||||
"@urql/exchange-graphcache": "^7.1.2",
|
"@urql/exchange-graphcache": "^7.1.2",
|
||||||
|
|
|
||||||
|
|
@ -342,7 +342,7 @@ const hasSessionTitle = computed(() => {
|
||||||
v-if="hasSessionTitle"
|
v-if="hasSessionTitle"
|
||||||
class="nav-item hidden items-center lg:inline-flex"
|
class="nav-item hidden items-center lg:inline-flex"
|
||||||
>
|
>
|
||||||
<div class="">
|
<div class="" data-cy="current-course-session-title">
|
||||||
{{ selectedCourseSessionTitle }}
|
{{ selectedCourseSessionTitle }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
|
import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
|
||||||
import { calculateCircleSectorData } from "@/pages/learningPath/learningPathPage/utils";
|
import {
|
||||||
|
calculateCircleSectorData,
|
||||||
|
filterCircles,
|
||||||
|
useCourseFilter,
|
||||||
|
} 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";
|
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||||
|
|
@ -48,9 +52,13 @@ const wrapperClasses = computed(() => {
|
||||||
return classes;
|
return classes;
|
||||||
});
|
});
|
||||||
|
|
||||||
const { inProgressCirclesCount, circlesCount } = useCourseCircleProgress(
|
const { filter } = useCourseFilter(props.courseSlug, props.courseSessionId);
|
||||||
lpQueryResult.circles
|
|
||||||
);
|
const filteredCircles = computed(() => {
|
||||||
|
return filterCircles(filter.value, circles.value);
|
||||||
|
});
|
||||||
|
const { inProgressCirclesCount, circlesCount } =
|
||||||
|
useCourseCircleProgress(filteredCircles);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -66,7 +74,7 @@ const { inProgressCirclesCount, circlesCount } = useCourseCircleProgress(
|
||||||
</h4>
|
</h4>
|
||||||
<div :class="wrapperClasses">
|
<div :class="wrapperClasses">
|
||||||
<LearningPathCircle
|
<LearningPathCircle
|
||||||
v-for="circle in circles"
|
v-for="circle in filteredCircles"
|
||||||
:key="circle.id"
|
:key="circle.id"
|
||||||
:sectors="calculateCircleSectorData(circle)"
|
:sectors="calculateCircleSectorData(circle)"
|
||||||
></LearningPathCircle>
|
></LearningPathCircle>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,27 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ItNavigationProgress from "@/components/ui/ItNavigationProgress.vue";
|
import ItNavigationProgress from "@/components/ui/ItNavigationProgress.vue";
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { isString, startsWith } from "lodash";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
step: number;
|
step: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const steps = computed(() => {
|
||||||
|
const courseType = route.params.courseType;
|
||||||
|
if (isString(courseType) && startsWith(courseType, "vv-")) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
return 3;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex h-screen flex-col">
|
<div class="flex h-screen flex-col">
|
||||||
<div class="flex-grow scroll-smooth p-16 lg:overflow-auto">
|
<div class="flex-grow scroll-smooth p-16 lg:overflow-auto">
|
||||||
<ItNavigationProgress :steps="3" :current-step="props.step" />
|
<ItNavigationProgress :steps="steps" :current-step="props.step" />
|
||||||
<slot name="content"></slot>
|
<slot name="content"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,14 @@ import { computed } from "vue"; // https://stackoverflow.com/questions/64775876/
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/64775876/vue-3-pass-reactive-object-to-component-with-two-way-binding
|
// https://stackoverflow.com/questions/64775876/vue-3-pass-reactive-object-to-component-with-two-way-binding
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue?: {
|
modelValue?: DropdownSelectable;
|
||||||
id: string | number;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
items?: DropdownSelectable[];
|
items?: DropdownSelectable[];
|
||||||
borderless?: boolean;
|
borderless?: boolean;
|
||||||
placeholderText?: string | null;
|
placeholderText?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:modelValue", data: object): void;
|
(e: "update:modelValue", data: DropdownSelectable): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,30 @@
|
||||||
import { useCSRFFetch } from "@/fetchHelpers";
|
import {useCSRFFetch} from "@/fetchHelpers";
|
||||||
import type { CourseStatisticsType } from "@/gql/graphql";
|
import type {CourseStatisticsType} from "@/gql/graphql";
|
||||||
import { graphqlClient } from "@/graphql/client";
|
import {graphqlClient} from "@/graphql/client";
|
||||||
import { COURSE_QUERY, COURSE_SESSION_DETAIL_QUERY } from "@/graphql/queries";
|
import {COURSE_QUERY, COURSE_SESSION_DETAIL_QUERY} from "@/graphql/queries";
|
||||||
import {
|
import {
|
||||||
circleFlatChildren,
|
circleFlatChildren,
|
||||||
circleFlatLearningContents,
|
circleFlatLearningContents,
|
||||||
circleFlatLearningUnits,
|
circleFlatLearningUnits,
|
||||||
someFinishedInLearningSequence,
|
someFinishedInLearningSequence,
|
||||||
} from "@/services/circle";
|
} from "@/services/circle";
|
||||||
import type {
|
import type {DashboardDueDate, DashboardPersonRoleType, DashboardPersonType,} from "@/services/dashboard";
|
||||||
DashboardDueDate,
|
|
||||||
DashboardPersonRoleType,
|
|
||||||
DashboardPersonType,
|
|
||||||
} from "@/services/dashboard";
|
|
||||||
import {
|
import {
|
||||||
courseIdForCourseSlug,
|
courseIdForCourseSlug,
|
||||||
fetchDashboardDueDates,
|
fetchDashboardDueDates,
|
||||||
fetchDashboardPersons,
|
fetchDashboardPersons,
|
||||||
fetchStatisticData,
|
fetchStatisticData,
|
||||||
} from "@/services/dashboard";
|
} from "@/services/dashboard";
|
||||||
import { presignUpload, uploadFile } from "@/services/files";
|
import {presignUpload, uploadFile} from "@/services/files";
|
||||||
import { useCompletionStore } from "@/stores/completion";
|
import {useCompletionStore} from "@/stores/completion";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import {useCourseSessionsStore} from "@/stores/courseSessions";
|
||||||
import { useDashboardStore } from "@/stores/dashboard";
|
import {useDashboardStore} from "@/stores/dashboard";
|
||||||
import { useUserStore } from "@/stores/user";
|
import {useUserStore} from "@/stores/user";
|
||||||
import type {
|
import type {
|
||||||
ActionCompetence,
|
ActionCompetence,
|
||||||
AgentParticipantRelation,
|
AgentParticipantRelation,
|
||||||
CircleType,
|
CircleType,
|
||||||
|
CompetenceCertificate,
|
||||||
Course,
|
Course,
|
||||||
CourseCompletion,
|
CourseCompletion,
|
||||||
CourseCompletionStatus,
|
CourseCompletionStatus,
|
||||||
|
|
@ -39,14 +36,16 @@ import type {
|
||||||
LearningUnitPerformanceCriteria,
|
LearningUnitPerformanceCriteria,
|
||||||
PerformanceCriteria,
|
PerformanceCriteria,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import { useQuery } from "@urql/vue";
|
import {useQuery} from "@urql/vue";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { t } from "i18next";
|
import {t} from "i18next";
|
||||||
import orderBy from "lodash/orderBy";
|
import orderBy from "lodash/orderBy";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import type { ComputedRef, Ref } from "vue";
|
import type {ComputedRef, Ref} from "vue";
|
||||||
import { computed, onMounted, ref, watchEffect } from "vue";
|
import {computed, onMounted, ref, watchEffect} from "vue";
|
||||||
import { type RouteLocationRaw, useRouter } from "vue-router";
|
import {type RouteLocationRaw, useRouter} from "vue-router";
|
||||||
|
import {getCertificates} from "./services/competence";
|
||||||
|
import {mergeCompetenceCertificates} from "./pages/competence/utils";
|
||||||
|
|
||||||
export function useCurrentCourseSession() {
|
export function useCurrentCourseSession() {
|
||||||
/**
|
/**
|
||||||
|
|
@ -178,7 +177,7 @@ export function useCourseData(courseSlug: string) {
|
||||||
|
|
||||||
// urql.useQuery is not meant to be used programmatically, so we use graphqlClient.query instead
|
// urql.useQuery is not meant to be used programmatically, so we use graphqlClient.query instead
|
||||||
const resultPromise = graphqlClient
|
const resultPromise = graphqlClient
|
||||||
.query(COURSE_QUERY, { slug: `${courseSlug}` })
|
.query(COURSE_QUERY, {slug: `${courseSlug}`})
|
||||||
.toPromise();
|
.toPromise();
|
||||||
|
|
||||||
resultPromise.then((result) => {
|
resultPromise.then((result) => {
|
||||||
|
|
@ -443,7 +442,7 @@ export function useFileUpload() {
|
||||||
const fileInfo = ref({} as { id: string; name: string; url: string });
|
const fileInfo = ref({} as { id: string; name: string; url: string });
|
||||||
|
|
||||||
async function upload(e: Event) {
|
async function upload(e: Event) {
|
||||||
const { files } = e.target as HTMLInputElement;
|
const {files} = e.target as HTMLInputElement;
|
||||||
if (!files?.length) return;
|
if (!files?.length) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -461,7 +460,7 @@ export function useFileUpload() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { upload, error, loading, fileInfo };
|
return {upload, error, loading, fileInfo};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMyLearningMentors() {
|
export function useMyLearningMentors() {
|
||||||
|
|
@ -471,7 +470,7 @@ export function useMyLearningMentors() {
|
||||||
|
|
||||||
const fetchMentors = async () => {
|
const fetchMentors = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { data } = await useCSRFFetch(
|
const {data} = await useCSRFFetch(
|
||||||
`/api/mentor/${currentCourseSessionId}/mentors`
|
`/api/mentor/${currentCourseSessionId}/mentors`
|
||||||
).json();
|
).json();
|
||||||
learningMentors.value = data.value;
|
learningMentors.value = data.value;
|
||||||
|
|
@ -623,7 +622,7 @@ export function useCourseCircleProgress(circles: Ref<CircleType[] | undefined>)
|
||||||
return circles.value?.length ?? 0;
|
return circles.value?.length ?? 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { inProgressCirclesCount, circlesCount };
|
return {inProgressCirclesCount, circlesCount};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCourseStatisticsv2(courseSlug: string) {
|
export function useCourseStatisticsv2(courseSlug: string) {
|
||||||
|
|
@ -669,6 +668,25 @@ export function useCourseStatisticsv2(courseSlug: string) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useCertificateQuery(
|
||||||
|
userIds: string[] | undefined,
|
||||||
|
courseSlug: string,
|
||||||
|
courseSession: CourseSession
|
||||||
|
) {
|
||||||
|
const certificatesQuery = (() => {
|
||||||
|
return useQuery({
|
||||||
|
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
||||||
|
variables: {
|
||||||
|
courseSlug: courseSlug,
|
||||||
|
courseSessionId: courseSession.id,
|
||||||
|
userIds,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
return {certificatesQuery};
|
||||||
|
}
|
||||||
|
|
||||||
export function useEvaluationWithFeedback() {
|
export function useEvaluationWithFeedback() {
|
||||||
const currentCourseSession = useCurrentCourseSession();
|
const currentCourseSession = useCurrentCourseSession();
|
||||||
const hasFeedback = computed(
|
const hasFeedback = computed(
|
||||||
|
|
@ -677,7 +695,7 @@ export function useEvaluationWithFeedback() {
|
||||||
currentCourseSession.value.course.configuration.is_vv
|
currentCourseSession.value.course.configuration.is_vv
|
||||||
);
|
);
|
||||||
|
|
||||||
return { hasFeedback };
|
return {hasFeedback};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useVVByLink() {
|
export function useVVByLink() {
|
||||||
|
|
@ -687,9 +705,36 @@ export function useVVByLink() {
|
||||||
() =>
|
() =>
|
||||||
router.resolve({
|
router.resolve({
|
||||||
name: "accountConfirm",
|
name: "accountConfirm",
|
||||||
params: { courseType: `vv-${userStore.language}` },
|
params: {courseType: `vv-${userStore.language}`},
|
||||||
}).href as RouteLocationRaw
|
}).href as RouteLocationRaw
|
||||||
);
|
);
|
||||||
|
|
||||||
return { href };
|
return {href};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAllCompetenceCertificates(
|
||||||
|
userId: string | undefined,
|
||||||
|
courseSlug: string
|
||||||
|
) {
|
||||||
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
|
const certificateQueries = courseSessionsStore.allCourseSessions.map(
|
||||||
|
(courseSession) => {
|
||||||
|
return useCertificateQuery([userId], courseSlug, courseSession).certificatesQuery;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const competenceCertificatesPerCs = computed(() =>
|
||||||
|
certificateQueries.map((query) => {
|
||||||
|
return getCertificates(query.data.value, userId ?? null)
|
||||||
|
?.competence_certificates as unknown as CompetenceCertificate[];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const isLoaded = computed(() => !certificateQueries.some((q) => q.fetching.value));
|
||||||
|
const competenceCertificates = computed(() =>
|
||||||
|
mergeCompetenceCertificates(competenceCertificatesPerCs.value.flat())
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
competenceCertificates,
|
||||||
|
isLoaded,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,5 @@ export const itCheckboxDefaultIconCheckedTailwindClass =
|
||||||
|
|
||||||
export const itCheckboxDefaultIconUncheckedTailwindClass =
|
export const itCheckboxDefaultIconUncheckedTailwindClass =
|
||||||
"bg-[url(/static/icons/icon-checkbox-unchecked.svg)] hover:bg-[url(/static/icons/icon-checkbox-unchecked-hover.svg)]";
|
"bg-[url(/static/icons/icon-checkbox-unchecked.svg)] hover:bg-[url(/static/icons/icon-checkbox-unchecked-hover.svg)]";
|
||||||
|
|
||||||
|
export const COURSE_PROFILE_ALL_FILTER = "all";
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -278,6 +278,8 @@ type CourseObjectType {
|
||||||
configuration: CourseConfigurationObjectType!
|
configuration: CourseConfigurationObjectType!
|
||||||
learning_path: LearningPathObjectType!
|
learning_path: LearningPathObjectType!
|
||||||
action_competences: [ActionCompetenceObjectType!]!
|
action_competences: [ActionCompetenceObjectType!]!
|
||||||
|
profiles: [String]
|
||||||
|
course_session_users(id: String): [CourseSessionUserType]!
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActionCompetenceObjectType implements CoursePageInterface {
|
type ActionCompetenceObjectType implements CoursePageInterface {
|
||||||
|
|
@ -343,45 +345,38 @@ type CircleLightObjectType {
|
||||||
slug: String!
|
slug: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type TopicObjectType implements CoursePageInterface {
|
type CourseSessionUserType {
|
||||||
is_visible: Boolean!
|
id: UUID!
|
||||||
id: ID!
|
chosen_profile: String!
|
||||||
title: String!
|
course_session: CourseSessionObjectType!
|
||||||
slug: String!
|
|
||||||
content_type: String!
|
|
||||||
live: Boolean!
|
|
||||||
translation_key: String!
|
|
||||||
frontend_url: String!
|
|
||||||
course: CourseObjectType
|
|
||||||
circles: [CircleObjectType!]!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CircleObjectType implements CoursePageInterface {
|
"""
|
||||||
description: String!
|
Leverages the internal Python implementation of UUID (uuid.UUID) to provide native UUID objects
|
||||||
goals: String!
|
in fields, resolvers and input.
|
||||||
|
"""
|
||||||
|
scalar UUID
|
||||||
|
|
||||||
|
type CourseSessionObjectType {
|
||||||
id: ID!
|
id: ID!
|
||||||
|
created_at: DateTime!
|
||||||
|
updated_at: DateTime!
|
||||||
|
course: CourseObjectType!
|
||||||
title: String!
|
title: String!
|
||||||
slug: String!
|
start_date: Date
|
||||||
content_type: String!
|
end_date: Date
|
||||||
live: Boolean!
|
attendance_courses: [CourseSessionAttendanceCourseObjectType!]!
|
||||||
translation_key: String!
|
assignments: [CourseSessionAssignmentObjectType!]!
|
||||||
frontend_url: String!
|
edoniq_tests: [CourseSessionEdoniqTestObjectType!]!
|
||||||
course: CourseObjectType
|
users: [CourseSessionUserObjectsType!]!
|
||||||
learning_sequences: [LearningSequenceObjectType!]!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LearningSequenceObjectType implements CoursePageInterface {
|
"""
|
||||||
icon: String!
|
The `Date` scalar type represents a Date
|
||||||
id: ID!
|
value as specified by
|
||||||
title: String!
|
[iso8601](https://en.wikipedia.org/wiki/ISO_8601).
|
||||||
slug: String!
|
"""
|
||||||
content_type: String!
|
scalar Date
|
||||||
live: Boolean!
|
|
||||||
translation_key: String!
|
|
||||||
frontend_url: String!
|
|
||||||
course: CourseObjectType
|
|
||||||
learning_units: [LearningUnitObjectType!]!
|
|
||||||
}
|
|
||||||
|
|
||||||
type CourseSessionAttendanceCourseObjectType {
|
type CourseSessionAttendanceCourseObjectType {
|
||||||
id: ID!
|
id: ID!
|
||||||
|
|
@ -443,26 +438,19 @@ type DueDateObjectType {
|
||||||
course_session: CourseSessionObjectType!
|
course_session: CourseSessionObjectType!
|
||||||
}
|
}
|
||||||
|
|
||||||
type CourseSessionObjectType {
|
type AttendanceUserObjectType {
|
||||||
id: ID!
|
user_id: UUID!
|
||||||
created_at: DateTime!
|
status: AttendanceUserStatus!
|
||||||
updated_at: DateTime!
|
first_name: String
|
||||||
course: CourseObjectType!
|
last_name: String
|
||||||
title: String!
|
email: String
|
||||||
start_date: Date
|
|
||||||
end_date: Date
|
|
||||||
attendance_courses: [CourseSessionAttendanceCourseObjectType!]!
|
|
||||||
assignments: [CourseSessionAssignmentObjectType!]!
|
|
||||||
edoniq_tests: [CourseSessionEdoniqTestObjectType!]!
|
|
||||||
users: [CourseSessionUserObjectsType!]!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""An enumeration."""
|
||||||
The `Date` scalar type represents a Date
|
enum AttendanceUserStatus {
|
||||||
value as specified by
|
PRESENT
|
||||||
[iso8601](https://en.wikipedia.org/wiki/ISO_8601).
|
ABSENT
|
||||||
"""
|
}
|
||||||
scalar Date
|
|
||||||
|
|
||||||
type CourseSessionAssignmentObjectType {
|
type CourseSessionAssignmentObjectType {
|
||||||
id: ID!
|
id: ID!
|
||||||
|
|
@ -591,12 +579,6 @@ type AssignmentCompletionObjectType {
|
||||||
evaluation_percent: Float
|
evaluation_percent: Float
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
|
||||||
Leverages the internal Python implementation of UUID (uuid.UUID) to provide native UUID objects
|
|
||||||
in fields, resolvers and input.
|
|
||||||
"""
|
|
||||||
scalar UUID
|
|
||||||
|
|
||||||
type UserObjectType {
|
type UserObjectType {
|
||||||
"""
|
"""
|
||||||
Erforderlich. 150 Zeichen oder weniger. Nur Buchstaben, Ziffern und @/./+/-/_.
|
Erforderlich. 150 Zeichen oder weniger. Nur Buchstaben, Ziffern und @/./+/-/_.
|
||||||
|
|
@ -735,18 +717,46 @@ type CourseSessionUserExpertCircleType {
|
||||||
slug: String!
|
slug: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type AttendanceUserObjectType {
|
type TopicObjectType implements CoursePageInterface {
|
||||||
user_id: UUID!
|
is_visible: Boolean!
|
||||||
status: AttendanceUserStatus!
|
id: ID!
|
||||||
first_name: String
|
title: String!
|
||||||
last_name: String
|
slug: String!
|
||||||
email: String
|
content_type: String!
|
||||||
|
live: Boolean!
|
||||||
|
translation_key: String!
|
||||||
|
frontend_url: String!
|
||||||
|
course: CourseObjectType
|
||||||
|
circles: [CircleObjectType!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
"""An enumeration."""
|
type CircleObjectType implements CoursePageInterface {
|
||||||
enum AttendanceUserStatus {
|
description: String!
|
||||||
PRESENT
|
goals: String!
|
||||||
ABSENT
|
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 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 LearningContentMediaLibraryObjectType implements CoursePageInterface & LearningContentInterface {
|
type LearningContentMediaLibraryObjectType implements CoursePageInterface & LearningContentInterface {
|
||||||
|
|
@ -909,6 +919,7 @@ type CompetenceCertificateListObjectType implements CoursePageInterface {
|
||||||
type Mutation {
|
type Mutation {
|
||||||
send_feedback(course_session_id: ID!, data: GenericScalar, learning_content_page_id: ID!, learning_content_type: String!, submitted: Boolean = false): SendFeedbackMutation
|
send_feedback(course_session_id: ID!, data: GenericScalar, learning_content_page_id: ID!, learning_content_type: String!, submitted: Boolean = false): SendFeedbackMutation
|
||||||
update_course_session_attendance_course_users(attendance_user_list: [AttendanceUserInputType]!, id: ID!): AttendanceCourseUserMutation
|
update_course_session_attendance_course_users(attendance_user_list: [AttendanceUserInputType]!, id: ID!): AttendanceCourseUserMutation
|
||||||
|
update_course_session_profile(input: CourseSessionProfileMutationInput!): CourseSessionProfileMutationPayload
|
||||||
upsert_assignment_completion(assignment_id: ID!, assignment_user_id: UUID, completion_data_string: String, completion_status: AssignmentCompletionStatus, course_session_id: ID!, evaluation_passed: Boolean, evaluation_points: Float, evaluation_user_id: ID, initialize_completion: Boolean, learning_content_page_id: ID): AssignmentCompletionMutation
|
upsert_assignment_completion(assignment_id: ID!, assignment_user_id: UUID, completion_data_string: String, completion_status: AssignmentCompletionStatus, course_session_id: ID!, evaluation_passed: Boolean, evaluation_points: Float, evaluation_user_id: ID, initialize_completion: Boolean, learning_content_page_id: ID): AssignmentCompletionMutation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -939,6 +950,27 @@ input AttendanceUserInputType {
|
||||||
status: AttendanceUserStatus!
|
status: AttendanceUserStatus!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CourseSessionProfileMutationPayload {
|
||||||
|
result: UpdateCourseProfileResult
|
||||||
|
clientMutationId: String
|
||||||
|
}
|
||||||
|
|
||||||
|
union UpdateCourseProfileResult = UpdateCourseProfileError | UpdateCourseProfileSuccess
|
||||||
|
|
||||||
|
type UpdateCourseProfileError {
|
||||||
|
message: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateCourseProfileSuccess {
|
||||||
|
user: CourseSessionUserType!
|
||||||
|
}
|
||||||
|
|
||||||
|
input CourseSessionProfileMutationInput {
|
||||||
|
course_profile: String!
|
||||||
|
course_slug: String!
|
||||||
|
clientMutationId: String
|
||||||
|
}
|
||||||
|
|
||||||
type AssignmentCompletionMutation {
|
type AssignmentCompletionMutation {
|
||||||
assignment_completion: AssignmentCompletionObjectType
|
assignment_completion: AssignmentCompletionObjectType
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,11 @@ export const CourseSessionAssignmentObjectType = "CourseSessionAssignmentObjectT
|
||||||
export const CourseSessionAttendanceCourseObjectType = "CourseSessionAttendanceCourseObjectType";
|
export const CourseSessionAttendanceCourseObjectType = "CourseSessionAttendanceCourseObjectType";
|
||||||
export const CourseSessionEdoniqTestObjectType = "CourseSessionEdoniqTestObjectType";
|
export const CourseSessionEdoniqTestObjectType = "CourseSessionEdoniqTestObjectType";
|
||||||
export const CourseSessionObjectType = "CourseSessionObjectType";
|
export const CourseSessionObjectType = "CourseSessionObjectType";
|
||||||
|
export const CourseSessionProfileMutationInput = "CourseSessionProfileMutationInput";
|
||||||
|
export const CourseSessionProfileMutationPayload = "CourseSessionProfileMutationPayload";
|
||||||
export const CourseSessionUserExpertCircleType = "CourseSessionUserExpertCircleType";
|
export const CourseSessionUserExpertCircleType = "CourseSessionUserExpertCircleType";
|
||||||
export const CourseSessionUserObjectsType = "CourseSessionUserObjectsType";
|
export const CourseSessionUserObjectsType = "CourseSessionUserObjectsType";
|
||||||
|
export const CourseSessionUserType = "CourseSessionUserType";
|
||||||
export const CourseStatisticsType = "CourseStatisticsType";
|
export const CourseStatisticsType = "CourseStatisticsType";
|
||||||
export const DashboardConfigType = "DashboardConfigType";
|
export const DashboardConfigType = "DashboardConfigType";
|
||||||
export const DashboardType = "DashboardType";
|
export const DashboardType = "DashboardType";
|
||||||
|
|
@ -84,4 +87,7 @@ export const StatisticsCourseSessionsSelectionMetricType = "StatisticsCourseSess
|
||||||
export const String = "String";
|
export const String = "String";
|
||||||
export const TopicObjectType = "TopicObjectType";
|
export const TopicObjectType = "TopicObjectType";
|
||||||
export const UUID = "UUID";
|
export const UUID = "UUID";
|
||||||
|
export const UpdateCourseProfileError = "UpdateCourseProfileError";
|
||||||
|
export const UpdateCourseProfileResult = "UpdateCourseProfileResult";
|
||||||
|
export const UpdateCourseProfileSuccess = "UpdateCourseProfileSuccess";
|
||||||
export const UserObjectType = "UserObjectType";
|
export const UserObjectType = "UserObjectType";
|
||||||
|
|
|
||||||
|
|
@ -58,3 +58,23 @@ export const UPSERT_ASSIGNMENT_COMPLETION_MUTATION = graphql(`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
export const UPDATE_COURSE_PROFILE_MUTATION = graphql(`
|
||||||
|
mutation UpdateCourseSessionProfile($input: CourseSessionProfileMutationInput!) {
|
||||||
|
update_course_session_profile(input: $input) {
|
||||||
|
clientMutationId
|
||||||
|
result {
|
||||||
|
__typename
|
||||||
|
... on UpdateCourseProfileSuccess {
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
chosen_profile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
... on UpdateCourseProfileError {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { graphql } from "@/gql";
|
import {graphql} from "@/gql";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const CoursePageFragment = graphql(`
|
const CoursePageFragment = graphql(`
|
||||||
|
|
@ -90,6 +90,45 @@ export const ASSIGNMENT_COMPLETION_QUERY = graphql(`
|
||||||
`);
|
`);
|
||||||
|
|
||||||
export const COMPETENCE_NAVI_CERTIFICATE_QUERY = graphql(`
|
export const COMPETENCE_NAVI_CERTIFICATE_QUERY = graphql(`
|
||||||
|
query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {
|
||||||
|
competence_certificate_list(course_slug: $courseSlug) {
|
||||||
|
...CoursePageFields
|
||||||
|
competence_certificates {
|
||||||
|
...CoursePageFields
|
||||||
|
assignments {
|
||||||
|
...CoursePageFields
|
||||||
|
assignment_type
|
||||||
|
max_points
|
||||||
|
competence_certificate_weight
|
||||||
|
completions(course_session_id: $courseSessionId) {
|
||||||
|
id
|
||||||
|
completion_status
|
||||||
|
submitted_at
|
||||||
|
evaluation_points
|
||||||
|
evaluation_points_deducted
|
||||||
|
evaluation_points_final
|
||||||
|
evaluation_max_points
|
||||||
|
evaluation_passed
|
||||||
|
course_session {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
learning_content {
|
||||||
|
...CoursePageFields
|
||||||
|
circle {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
export const COMPETENCE_NAVI_CERTIFICATE_FOR_USER_QUERY = graphql(`
|
||||||
query competenceCertificateForUserQuery(
|
query competenceCertificateForUserQuery(
|
||||||
$courseSlug: String!
|
$courseSlug: String!
|
||||||
$courseSessionId: ID!
|
$courseSessionId: ID!
|
||||||
|
|
@ -117,6 +156,10 @@ export const COMPETENCE_NAVI_CERTIFICATE_QUERY = graphql(`
|
||||||
assignment_user {
|
assignment_user {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
course_session {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
}
|
||||||
}
|
}
|
||||||
learning_content {
|
learning_content {
|
||||||
...CoursePageFields
|
...CoursePageFields
|
||||||
|
|
@ -225,18 +268,28 @@ export const COURSE_SESSION_DETAIL_QUERY = graphql(`
|
||||||
`);
|
`);
|
||||||
|
|
||||||
export const COURSE_QUERY = graphql(`
|
export const COURSE_QUERY = graphql(`
|
||||||
query courseQuery($slug: String!) {
|
query courseQuery($slug: String!, $user: String) {
|
||||||
course(slug: $slug) {
|
course(slug: $slug) {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
slug
|
slug
|
||||||
category_name
|
category_name
|
||||||
|
profiles
|
||||||
|
course_session_users(id: $user) {
|
||||||
|
id
|
||||||
|
__typename
|
||||||
|
chosen_profile
|
||||||
|
course_session {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
configuration {
|
configuration {
|
||||||
id
|
id
|
||||||
enable_circle_documents
|
enable_circle_documents
|
||||||
enable_learning_mentor
|
enable_learning_mentor
|
||||||
enable_competence_certificates
|
enable_competence_certificates
|
||||||
is_uk
|
is_uk
|
||||||
|
is_vv
|
||||||
}
|
}
|
||||||
action_competences {
|
action_competences {
|
||||||
competence_id
|
competence_id
|
||||||
|
|
@ -259,6 +312,8 @@ export const COURSE_QUERY = graphql(`
|
||||||
circles {
|
circles {
|
||||||
description
|
description
|
||||||
goals
|
goals
|
||||||
|
profiles
|
||||||
|
is_base_circle
|
||||||
...CoursePageFields
|
...CoursePageFields
|
||||||
learning_sequences {
|
learning_sequences {
|
||||||
icon
|
icon
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,13 @@ function log(data: any) {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h2 class="mb-8 mt-8">Tags</h2>
|
||||||
|
|
||||||
|
<div class="mb-16 flex flex-col flex-wrap content-center gap-4 lg:flex-row">
|
||||||
|
<button class="tag-active">Active</button>
|
||||||
|
<button class="tag-inactive">Inactive</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2 class="mb-8 mt-8">Dropdown (Work-in-progress)</h2>
|
<h2 class="mb-8 mt-8">Dropdown (Work-in-progress)</h2>
|
||||||
|
|
||||||
<ItDropdownSelect
|
<ItDropdownSelect
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,18 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
|
import router from "@/router";
|
||||||
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import type { CompetenceCertificateAssignment } from "@/types";
|
import type { CompetenceCertificateAssignment } from "@/types";
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
|
|
||||||
log.debug("CompetenceAssignmentRow setup");
|
log.debug("CompetenceAssignmentRow setup");
|
||||||
|
|
||||||
|
const currentCourseSession = useCurrentCourseSession();
|
||||||
|
const { switchCourseSessionById } = useCourseSessionsStore();
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
assignment: CompetenceCertificateAssignment;
|
assignment: CompetenceCertificateAssignment;
|
||||||
|
showCourseSession: boolean;
|
||||||
addBorderBottom?: boolean;
|
addBorderBottom?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,6 +26,13 @@ const getIconName = () => {
|
||||||
}
|
}
|
||||||
return "it-icon-assignment-large";
|
return "it-icon-assignment-large";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openInCircle = (assignment: CompetenceCertificateAssignment) => {
|
||||||
|
if (assignment.completion?.course_session !== currentCourseSession.value) {
|
||||||
|
switchCourseSessionById(assignment.completion!.course_session.id);
|
||||||
|
}
|
||||||
|
router.push(assignment.frontend_url);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -30,19 +44,26 @@ const getIconName = () => {
|
||||||
<component :is="getIconName()" class="mr-4 hidden h-9 w-9 lg:block"></component>
|
<component :is="getIconName()" class="mr-4 hidden h-9 w-9 lg:block"></component>
|
||||||
<div class="flex flex-col lg:w-[420px]">
|
<div class="flex flex-col lg:w-[420px]">
|
||||||
<h3 class="text-bold flex items-center gap-2">{{ assignment.title }}</h3>
|
<h3 class="text-bold flex items-center gap-2">{{ assignment.title }}</h3>
|
||||||
|
<p
|
||||||
|
v-if="showCourseSession"
|
||||||
|
:data-cy="`assignment-${assignment.slug}-course-session`"
|
||||||
|
>
|
||||||
|
{{ assignment?.completion?.course_session.title }}
|
||||||
|
</p>
|
||||||
<p class="text-gray-800">
|
<p class="text-gray-800">
|
||||||
<a
|
<button
|
||||||
v-if="assignment.learning_content"
|
v-if="assignment.learning_content"
|
||||||
:href="assignment.frontend_url"
|
:href="assignment.frontend_url"
|
||||||
class="link"
|
class="link"
|
||||||
data-cy="open-learning-content"
|
data-cy="open-learning-content"
|
||||||
|
@click="() => openInCircle(assignment)"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
$t("general.im circle x anschauen", {
|
$t("general.im circle x anschauen", {
|
||||||
x: assignment.learning_content.circle.title,
|
x: assignment.learning_content.circle.title,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</a>
|
</button>
|
||||||
<span v-else>Fehler, Lerninhalt nicht korrekt verknüpft</span>
|
<span v-else>Fehler, Lerninhalt nicht korrekt verknüpft</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
calcCompetenceCertificateGrade,
|
calcCompetenceCertificateGrade,
|
||||||
competenceCertificateProgressStatusCount,
|
competenceCertificateProgressStatusCount,
|
||||||
} from "@/pages/competence/utils";
|
} from "@/pages/competence/utils";
|
||||||
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
|
|
||||||
log.debug("CompetenceCertificateComponent setup");
|
log.debug("CompetenceCertificateComponent setup");
|
||||||
|
|
||||||
|
|
@ -51,6 +52,15 @@ const frontendUrl = computed(() => {
|
||||||
? props.frontendUrl
|
? props.frontendUrl
|
||||||
: props.competenceCertificate.frontend_url;
|
: props.competenceCertificate.frontend_url;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const showCourseSession = computed(() => {
|
||||||
|
const currentCourseSession = useCurrentCourseSession();
|
||||||
|
return props.competenceCertificate.assignments.some((assignment) => {
|
||||||
|
return (
|
||||||
|
assignment.completion?.course_session.title !== currentCourseSession.value.title
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -134,6 +144,7 @@ const frontendUrl = computed(() => {
|
||||||
<CompetenceAssignmentRow
|
<CompetenceAssignmentRow
|
||||||
:assignment="assignment"
|
:assignment="assignment"
|
||||||
:add-border-bottom="index < competenceCertificate.assignments.length - 1"
|
:add-border-bottom="index < competenceCertificate.assignments.length - 1"
|
||||||
|
:show-course-session="showCourseSession"
|
||||||
></CompetenceAssignmentRow>
|
></CompetenceAssignmentRow>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { computed } from "vue";
|
import {computed} from "vue";
|
||||||
import type { CompetenceCertificate } from "@/types";
|
import type {CompetenceCertificate} from "@/types";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import {useAllCompetenceCertificates} from "@/composables";
|
||||||
|
import {getPreviousRoute} from "@/router/history";
|
||||||
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
|
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
|
||||||
import { getPreviousRoute } from "@/router/history";
|
|
||||||
import { useQuery } from "@urql/vue";
|
|
||||||
import { COMPETENCE_NAVI_CERTIFICATE_QUERY } from "@/graphql/queries";
|
|
||||||
import { useUserStore } from "@/stores/user";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
|
|
@ -17,17 +14,6 @@ const props = defineProps<{
|
||||||
|
|
||||||
log.debug("CompetenceCertificateDetailPage setup", props);
|
log.debug("CompetenceCertificateDetailPage setup", props);
|
||||||
|
|
||||||
const user = useUserStore();
|
|
||||||
const courseSession = useCurrentCourseSession();
|
|
||||||
|
|
||||||
const certificatesQuery = useQuery({
|
|
||||||
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
|
||||||
variables: {
|
|
||||||
courseSlug: props.courseSlug,
|
|
||||||
courseSessionId: courseSession.value.id,
|
|
||||||
userIds: [props.userId ?? user.id],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const competenceCertificates = computed(() => {
|
const competenceCertificates = computed(() => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -36,7 +22,16 @@ const competenceCertificates = computed(() => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {competenceCertificates} = useAllCompetenceCertificates(
|
||||||
|
props.userId,
|
||||||
|
props.courseSlug
|
||||||
|
);
|
||||||
|
|
||||||
const certificate = computed(() => {
|
const certificate = computed(() => {
|
||||||
|
if (!competenceCertificates) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return competenceCertificates.value.find((cc) =>
|
return competenceCertificates.value.find((cc) =>
|
||||||
cc.slug.endsWith(props.certificateSlug)
|
cc.slug.endsWith(props.certificateSlug)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { computed, onMounted } from "vue";
|
import {computed, onMounted} from "vue";
|
||||||
import type { CompetenceCertificate } from "@/types";
|
import {useAllCompetenceCertificates} from "@/composables";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
|
||||||
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
|
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
|
||||||
import {
|
import {assignmentsUserPoints, calcCompetencesTotalGrade,} from "@/pages/competence/utils";
|
||||||
assignmentsUserPoints,
|
import {useRoute} from "vue-router";
|
||||||
calcCompetencesTotalGrade,
|
|
||||||
} from "@/pages/competence/utils";
|
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
import { useQuery } from "@urql/vue";
|
|
||||||
import { COMPETENCE_NAVI_CERTIFICATE_QUERY } from "@/graphql/queries";
|
|
||||||
import { useUserStore } from "@/stores/user";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
|
|
@ -22,27 +15,14 @@ log.debug("CompetenceCertificateListPage setup", props);
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const user = useUserStore();
|
|
||||||
const courseSession = useCurrentCourseSession();
|
|
||||||
|
|
||||||
const certificatesQuery = useQuery({
|
const {competenceCertificates} = useAllCompetenceCertificates(
|
||||||
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
|
props.userId,
|
||||||
variables: {
|
props.courseSlug
|
||||||
courseSlug: props.courseSlug,
|
);
|
||||||
courseSessionId: courseSession.value.id,
|
|
||||||
userIds: [props.userId ?? user.id],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const competenceCertificates = computed(() => {
|
|
||||||
return (
|
|
||||||
(certificatesQuery.data.value?.competence_certificate_list
|
|
||||||
?.competence_certificates as unknown as CompetenceCertificate[]) ?? []
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const assignments = computed(() => {
|
const assignments = computed(() => {
|
||||||
return competenceCertificates?.value?.flatMap((cc) => cc.assignments);
|
return competenceCertificates?.value.flatMap((cc) => cc.assignments);
|
||||||
});
|
});
|
||||||
|
|
||||||
const totalGrade = computed(() => {
|
const totalGrade = computed(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { COMPETENCE_NAVI_CERTIFICATE_QUERY } from "@/graphql/queries";
|
import {computed} from "vue";
|
||||||
import { useQuery } from "@urql/vue";
|
import {useAllCompetenceCertificates, useCurrentCourseSession} from "@/composables";
|
||||||
import { computed } from "vue";
|
|
||||||
import type { CompetenceCertificate } from "@/types";
|
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
|
||||||
import {
|
import {
|
||||||
assignmentsUserPoints,
|
assignmentsUserPoints,
|
||||||
calcCompetenceCertificateGrade,
|
calcCompetenceCertificateGrade,
|
||||||
|
|
@ -12,9 +9,10 @@ import {
|
||||||
competenceCertificateProgressStatusCount,
|
competenceCertificateProgressStatusCount,
|
||||||
} from "@/pages/competence/utils";
|
} from "@/pages/competence/utils";
|
||||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
import ItProgress from "@/components/ui/ItProgress.vue";
|
||||||
import SelfEvaluationAndFeedbackOverview from "@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackOverview.vue";
|
import SelfEvaluationAndFeedbackOverview
|
||||||
import { useUserStore } from "@/stores/user";
|
from "@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackOverview.vue";
|
||||||
import { useRouter } from "vue-router";
|
import {useUserStore} from "@/stores/user";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
|
|
@ -22,8 +20,6 @@ const props = defineProps<{
|
||||||
|
|
||||||
log.debug("CompetenceIndexPage setup", props);
|
log.debug("CompetenceIndexPage setup", props);
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
|
||||||
|
|
||||||
const user = useUserStore();
|
const user = useUserStore();
|
||||||
|
|
||||||
const certificatesQuery = useQuery({
|
const certificatesQuery = useQuery({
|
||||||
|
|
@ -35,12 +31,10 @@ const certificatesQuery = useQuery({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const competenceCertificates = computed(() => {
|
const {competenceCertificates, isLoaded} = useAllCompetenceCertificates(
|
||||||
return (
|
user.id,
|
||||||
(certificatesQuery.data.value?.competence_certificate_list
|
props.courseSlug
|
||||||
?.competence_certificates as unknown as CompetenceCertificate[]) ?? []
|
);
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const allAssignments = computed(() => {
|
const allAssignments = computed(() => {
|
||||||
return competenceCertificates.value.flatMap((cc) => cc.assignments);
|
return competenceCertificates.value.flatMap((cc) => cc.assignments);
|
||||||
|
|
@ -52,8 +46,6 @@ const userPointsEvaluatedAssignments = computed(() => {
|
||||||
|
|
||||||
const currentCourseSession = useCurrentCourseSession();
|
const currentCourseSession = useCurrentCourseSession();
|
||||||
|
|
||||||
const isLoaded = computed(() => !certificatesQuery.fetching.value);
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { StatusCount } from "@/components/ui/ItProgress.vue";
|
import type { StatusCount } from "@/components/ui/ItProgress.vue";
|
||||||
import { percentToRoundedGrade } from "@/services/assignmentService";
|
import { percentToRoundedGrade } from "@/services/assignmentService";
|
||||||
import type { CompetenceCertificate, CompetenceCertificateAssignment } from "@/types";
|
import type { CompetenceCertificate, CompetenceCertificateAssignment } from "@/types";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
export function assignmentsMaxEvaluationPoints(
|
export function assignmentsMaxEvaluationPoints(
|
||||||
|
|
@ -84,3 +85,55 @@ export function competenceCertificateProgressStatusCount(
|
||||||
FAIL: 0,
|
FAIL: 0,
|
||||||
} as StatusCount;
|
} as StatusCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mergeCompetenceCertificates(
|
||||||
|
competenceCertificates: CompetenceCertificate[]
|
||||||
|
) {
|
||||||
|
const groupedCompetenceCertificates: Record<
|
||||||
|
string,
|
||||||
|
Array<CompetenceCertificate>
|
||||||
|
> = {};
|
||||||
|
competenceCertificates.forEach((certificate) => {
|
||||||
|
if (!certificate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!groupedCompetenceCertificates[certificate.id]) {
|
||||||
|
groupedCompetenceCertificates[certificate.id] = [];
|
||||||
|
}
|
||||||
|
groupedCompetenceCertificates[certificate.id].push(certificate);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Found ${Object.keys(groupedCompetenceCertificates).length} competence certificates over all course sessions`
|
||||||
|
);
|
||||||
|
|
||||||
|
return Object.values(groupedCompetenceCertificates).map((certificates) => {
|
||||||
|
const mergedCertificate: CompetenceCertificate = {
|
||||||
|
...certificates[0],
|
||||||
|
assignments: [],
|
||||||
|
};
|
||||||
|
certificates.forEach((certificate) => {
|
||||||
|
certificate.assignments.forEach((assignment) => {
|
||||||
|
const existingAssignment = mergedCertificate.assignments.find(
|
||||||
|
(a) => a.id === assignment.id
|
||||||
|
);
|
||||||
|
if (!existingAssignment) {
|
||||||
|
mergedCertificate.assignments.push(assignment);
|
||||||
|
} else if (
|
||||||
|
assignment.completion != null &&
|
||||||
|
(existingAssignment.completion == null ||
|
||||||
|
dayjs(existingAssignment.completion.evaluation_submitted_at).isBefore(
|
||||||
|
assignment.completion.evaluation_submitted_at
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
mergedCertificate.assignments.splice(
|
||||||
|
mergedCertificate.assignments.findIndex((a) => a.id === assignment.id),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
mergedCertificate.assignments.push(assignment);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return mergedCertificate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { COURSE_PROFILE_ALL_FILTER } from "@/constants";
|
||||||
|
import LearningPathCircleListTile from "@/pages/learningPath/learningPathPage/LearningPathCircleListTile.vue";
|
||||||
|
import type { LearningContentWithCompletion, TopicType } from "@/types";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
topic: TopicType;
|
||||||
|
nextLearningContent?: LearningContentWithCompletion;
|
||||||
|
filter?: string;
|
||||||
|
}
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const filteredCircles = computed(() => {
|
||||||
|
if (
|
||||||
|
props.filter === undefined ||
|
||||||
|
props.filter === "" ||
|
||||||
|
props.filter === COURSE_PROFILE_ALL_FILTER
|
||||||
|
) {
|
||||||
|
return props.topic.circles;
|
||||||
|
}
|
||||||
|
return props.topic.circles.filter(
|
||||||
|
(circle) =>
|
||||||
|
circle.profiles.indexOf(props.filter as string) > -1 || circle.is_base_circle
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="pb-2 font-bold text-gray-700">
|
||||||
|
{{ topic.title }}
|
||||||
|
</div>
|
||||||
|
<LearningPathCircleListTile
|
||||||
|
v-for="circle in filteredCircles"
|
||||||
|
:key="circle.id"
|
||||||
|
:circle="circle"
|
||||||
|
:next-learning-content="nextLearningContent"
|
||||||
|
></LearningPathCircleListTile>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -1,26 +1,23 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LearningPathCircleListTile from "@/pages/learningPath/learningPathPage/LearningPathCircleListTile.vue";
|
|
||||||
import { computed } from "vue";
|
|
||||||
import type { LearningContentWithCompletion, LearningPathType } from "@/types";
|
import type { LearningContentWithCompletion, LearningPathType } from "@/types";
|
||||||
|
import { computed } from "vue";
|
||||||
|
import LearningPathListTopic from "./LearningPathListTopic.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
learningPath: LearningPathType | undefined;
|
learningPath: LearningPathType | undefined;
|
||||||
nextLearningContent: LearningContentWithCompletion | undefined;
|
nextLearningContent: LearningContentWithCompletion | undefined;
|
||||||
|
filter?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const topics = computed(() => props.learningPath?.topics ?? []);
|
const topics = computed(() => props.learningPath?.topics ?? []);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-for="topic in topics" :key="topic.title">
|
<LearningPathListTopic
|
||||||
<div class="pb-2 font-bold text-gray-700">
|
v-for="topic in topics"
|
||||||
{{ topic.title }}
|
:key="topic.title"
|
||||||
</div>
|
:topic="topic"
|
||||||
<LearningPathCircleListTile
|
:next-learning-content="nextLearningContent"
|
||||||
v-for="circle in topic.circles"
|
:filter="filter"
|
||||||
:key="circle.id"
|
/>
|
||||||
:circle="circle"
|
|
||||||
:next-learning-content="props.nextLearningContent"
|
|
||||||
></LearningPathCircleListTile>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import LearningPathListView from "@/pages/learningPath/learningPathPage/Learning
|
||||||
import LearningPathPathView from "@/pages/learningPath/learningPathPage/LearningPathPathView.vue";
|
import LearningPathPathView from "@/pages/learningPath/learningPathPage/LearningPathPathView.vue";
|
||||||
import CircleProgress from "@/pages/learningPath/learningPathPage/LearningPathProgress.vue";
|
import CircleProgress from "@/pages/learningPath/learningPathPage/LearningPathProgress.vue";
|
||||||
import LearningPathTopics from "@/pages/learningPath/learningPathPage/LearningPathTopics.vue";
|
import LearningPathTopics from "@/pages/learningPath/learningPathPage/LearningPathTopics.vue";
|
||||||
|
import LearningPathProfileFilter from "@/pages/learningPath/learningPathPage/LearningPathProfileFilter.vue";
|
||||||
import type { ViewType } from "@/pages/learningPath/learningPathPage/LearningPathViewSwitch.vue";
|
import type { ViewType } from "@/pages/learningPath/learningPathPage/LearningPathViewSwitch.vue";
|
||||||
import LearningPathViewSwitch from "@/pages/learningPath/learningPathPage/LearningPathViewSwitch.vue";
|
import LearningPathViewSwitch from "@/pages/learningPath/learningPathPage/LearningPathViewSwitch.vue";
|
||||||
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
|
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
|
||||||
|
|
@ -13,6 +14,9 @@ import {
|
||||||
useCurrentCourseSession,
|
useCurrentCourseSession,
|
||||||
} from "@/composables";
|
} from "@/composables";
|
||||||
import CourseSessionDueDatesList from "@/components/dueDates/CourseSessionDueDatesList.vue";
|
import CourseSessionDueDatesList from "@/components/dueDates/CourseSessionDueDatesList.vue";
|
||||||
|
import { useMutation } from "@urql/vue";
|
||||||
|
import { UPDATE_COURSE_PROFILE_MUTATION } from "@/graphql/mutations";
|
||||||
|
import { filterCircles, useCourseFilter } from "./utils";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
|
|
@ -33,9 +37,28 @@ const course = computed(() => lpQueryResult.course.value);
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
|
|
||||||
const { inProgressCirclesCount, circlesCount } = useCourseCircleProgress(
|
const { filter } = useCourseFilter(props.courseSlug);
|
||||||
lpQueryResult.circles
|
|
||||||
);
|
const filteredCircles = computed(() => {
|
||||||
|
if (lpQueryResult.circles.value === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return filterCircles(filter.value, lpQueryResult.circles.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const { inProgressCirclesCount, circlesCount } =
|
||||||
|
useCourseCircleProgress(filteredCircles);
|
||||||
|
|
||||||
|
const updateCourseProfileMutation = useMutation(UPDATE_COURSE_PROFILE_MUTATION);
|
||||||
|
|
||||||
|
const updateCourseProfile = (profile: string) => {
|
||||||
|
updateCourseProfileMutation.executeMutation({
|
||||||
|
input: {
|
||||||
|
course_profile: profile,
|
||||||
|
course_slug: props.courseSlug,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const changeViewType = (viewType: ViewType) => {
|
const changeViewType = (viewType: ViewType) => {
|
||||||
selectedView.value = viewType;
|
selectedView.value = viewType;
|
||||||
|
|
@ -46,7 +69,9 @@ const changeViewType = (viewType: ViewType) => {
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<!-- Top -->
|
<!-- Top -->
|
||||||
<div class="flex flex-row justify-between space-x-8 bg-gray-200 p-6 sm:p-12">
|
<div
|
||||||
|
class="flex flex-col justify-between gap-8 bg-gray-200 p-6 sm:p-12 xl:flex-row"
|
||||||
|
>
|
||||||
<!-- Left -->
|
<!-- Left -->
|
||||||
<div class="flex flex-col justify-between lg:w-1/2">
|
<div class="flex flex-col justify-between lg:w-1/2">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -64,15 +89,21 @@ const changeViewType = (viewType: ViewType) => {
|
||||||
></CircleProgress>
|
></CircleProgress>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right -->
|
<!-- todo: find out when to display CourseSessionDueDatesList -->
|
||||||
<div v-if="!useMobileLayout" class="flex-grow">
|
<div v-if="!useMobileLayout && false" class="flex-grow">
|
||||||
<CourseSessionDueDatesList
|
<CourseSessionDueDatesList
|
||||||
:course-session-id="courseSession.id"
|
:course-session-id="courseSession.id"
|
||||||
:max-count="2"
|
:max-count="2"
|
||||||
></CourseSessionDueDatesList>
|
></CourseSessionDueDatesList>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Right -->
|
||||||
|
<LearningPathProfileFilter
|
||||||
|
v-if="course?.configuration.is_vv"
|
||||||
|
:profiles="course?.profiles"
|
||||||
|
:selected="filter"
|
||||||
|
@select="updateCourseProfile"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bottom -->
|
<!-- Bottom -->
|
||||||
<div class="bg-white">
|
<div class="bg-white">
|
||||||
<div v-if="lpQueryResult.learningPath">
|
<div v-if="lpQueryResult.learningPath">
|
||||||
|
|
@ -101,6 +132,7 @@ const changeViewType = (viewType: ViewType) => {
|
||||||
<LearningPathPathView
|
<LearningPathPathView
|
||||||
:learning-path="learningPath"
|
:learning-path="learningPath"
|
||||||
:use-mobile-layout="useMobileLayout"
|
:use-mobile-layout="useMobileLayout"
|
||||||
|
:filter="filter"
|
||||||
:next-learning-content="lpQueryResult.nextLearningContent.value"
|
:next-learning-content="lpQueryResult.nextLearningContent.value"
|
||||||
></LearningPathPathView>
|
></LearningPathPathView>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -114,6 +146,7 @@ const changeViewType = (viewType: ViewType) => {
|
||||||
<LearningPathListView
|
<LearningPathListView
|
||||||
:learning-path="learningPath"
|
:learning-path="learningPath"
|
||||||
:next-learning-content="lpQueryResult.nextLearningContent.value"
|
:next-learning-content="lpQueryResult.nextLearningContent.value"
|
||||||
|
:filter="filter"
|
||||||
></LearningPathListView>
|
></LearningPathListView>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
import type { LearningContentWithCompletion, TopicType } from "@/types";
|
||||||
|
import LearningPathCircleColumn from "./LearningPathCircleColumn.vue";
|
||||||
|
import { filterCircles } from "./utils";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
topic: TopicType;
|
||||||
|
topicIndex: number;
|
||||||
|
nextLearningContent?: LearningContentWithCompletion;
|
||||||
|
overrideCircleUrlBase?: string;
|
||||||
|
filter?: string;
|
||||||
|
isLastTopic: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const isFirstCircle = (circleIndex: number) =>
|
||||||
|
props.topicIndex === 0 && circleIndex === 0;
|
||||||
|
|
||||||
|
const isLastCircle = (circleIndex: number, numCircles: number) =>
|
||||||
|
props.isLastTopic && circleIndex === numCircles - 1;
|
||||||
|
|
||||||
|
const filteredCircles = computed(() => {
|
||||||
|
return filterCircles(props.filter, props.topic.circles);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="basis-40 border-l border-gray-500 first:ml-6 first:sm:ml-12">
|
||||||
|
<p
|
||||||
|
:id="`topic-${topic.slug}`"
|
||||||
|
class="inline-block h-12 self-start px-4 font-bold text-gray-800"
|
||||||
|
data-cy="lp-topic"
|
||||||
|
>
|
||||||
|
{{ topic.title }}
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-row pt-6">
|
||||||
|
<LearningPathCircleColumn
|
||||||
|
v-for="(circle, circleIndex) in filteredCircles"
|
||||||
|
:key="circle.id"
|
||||||
|
:circle="circle"
|
||||||
|
:next-learning-content="nextLearningContent"
|
||||||
|
:is-first-circle="isFirstCircle(circleIndex)"
|
||||||
|
:is-last-circle="isLastCircle(circleIndex, filteredCircles.length)"
|
||||||
|
:override-circle-url="
|
||||||
|
overrideCircleUrlBase ? `${overrideCircleUrlBase}/${circle.slug}` : undefined
|
||||||
|
"
|
||||||
|
></LearningPathCircleColumn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LearningPathCircleColumn from "@/pages/learningPath/learningPathPage/LearningPathCircleColumn.vue";
|
|
||||||
import LearningPathScrollButton from "@/pages/learningPath/learningPathPage/LearningPathScrollButton.vue";
|
import LearningPathScrollButton from "@/pages/learningPath/learningPathPage/LearningPathScrollButton.vue";
|
||||||
import { useScroll } from "@vueuse/core";
|
import { useScroll } from "@vueuse/core";
|
||||||
import { ref } from "vue";
|
import { computed, nextTick, ref, watch } from "vue";
|
||||||
import type { LearningContentWithCompletion, LearningPathType } from "@/types";
|
import type { LearningContentWithCompletion, LearningPathType } from "@/types";
|
||||||
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
||||||
|
import LearningPathPathTopic from "./LearningPathPathTopic.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
learningPath: LearningPathType | undefined;
|
learningPath: LearningPathType | undefined;
|
||||||
|
|
@ -12,6 +12,7 @@ const props = defineProps<{
|
||||||
useMobileLayout: boolean;
|
useMobileLayout: boolean;
|
||||||
hideButtons?: boolean;
|
hideButtons?: boolean;
|
||||||
overrideCircleUrlBase?: string;
|
overrideCircleUrlBase?: string;
|
||||||
|
filter?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const scrollIncrement = 600;
|
const scrollIncrement = 600;
|
||||||
|
|
@ -19,13 +20,6 @@ const scrollIncrement = 600;
|
||||||
const learnPathDiagram = ref<HTMLElement | null>(null);
|
const learnPathDiagram = ref<HTMLElement | null>(null);
|
||||||
const { x, arrivedState } = useScroll(learnPathDiagram, { behavior: "smooth" });
|
const { x, arrivedState } = useScroll(learnPathDiagram, { behavior: "smooth" });
|
||||||
|
|
||||||
const isFirstCircle = (topicIndex: number, circleIndex: number) =>
|
|
||||||
topicIndex === 0 && circleIndex === 0;
|
|
||||||
|
|
||||||
const isLastCircle = (topicIndex: number, circleIndex: number, numCircles: number) =>
|
|
||||||
topicIndex === (props.learningPath?.topics ?? []).length - 1 &&
|
|
||||||
circleIndex === numCircles - 1;
|
|
||||||
|
|
||||||
const scrollRight = () => scrollLearnPathDiagram(scrollIncrement);
|
const scrollRight = () => scrollLearnPathDiagram(scrollIncrement);
|
||||||
|
|
||||||
const scrollLeft = () => scrollLearnPathDiagram(-scrollIncrement);
|
const scrollLeft = () => scrollLearnPathDiagram(-scrollIncrement);
|
||||||
|
|
@ -33,6 +27,22 @@ const scrollLeft = () => scrollLearnPathDiagram(-scrollIncrement);
|
||||||
const scrollLearnPathDiagram = (offset: number) => {
|
const scrollLearnPathDiagram = (offset: number) => {
|
||||||
x.value += offset;
|
x.value += offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const topics = computed(() => props.learningPath?.topics ?? []);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.filter,
|
||||||
|
() => {
|
||||||
|
// we need to update the scroll state of the element, otherwise the arrows won't match the scroll state
|
||||||
|
// https://github.com/vueuse/vueuse/issues/2875
|
||||||
|
nextTick(() => {
|
||||||
|
if (learnPathDiagram.value) {
|
||||||
|
const scrollEvent = new Event("scroll");
|
||||||
|
learnPathDiagram.value.dispatchEvent(scrollEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -50,37 +60,16 @@ const scrollLearnPathDiagram = (offset: number) => {
|
||||||
ref="learnPathDiagram"
|
ref="learnPathDiagram"
|
||||||
class="no-scrollbar flex h-96 snap-x flex-row overflow-auto py-5 sm:py-10"
|
class="no-scrollbar flex h-96 snap-x flex-row overflow-auto py-5 sm:py-10"
|
||||||
>
|
>
|
||||||
<div
|
<LearningPathPathTopic
|
||||||
v-for="(topic, topicIndex) in props.learningPath?.topics ?? []"
|
v-for="(topic, topicIndex) in props.learningPath?.topics ?? []"
|
||||||
:key="topic.title"
|
:key="topic.title"
|
||||||
class="border-l border-gray-500"
|
:topic-index="topicIndex"
|
||||||
:class="topicIndex == 0 ? 'ml-6 sm:ml-12' : ''"
|
:topic="topic"
|
||||||
>
|
:next-learning-content="nextLearningContent"
|
||||||
<p
|
:override-circle-url-base="overrideCircleUrlBase"
|
||||||
:id="`topic-${topic.slug}`"
|
:filter="filter"
|
||||||
class="inline-block h-12 self-start px-4 font-bold text-gray-800"
|
:is-last-topic="topicIndex === topics.length - 1"
|
||||||
data-cy="lp-topic"
|
/>
|
||||||
>
|
|
||||||
{{ topic.title }}
|
|
||||||
</p>
|
|
||||||
<div class="flex flex-row pt-6">
|
|
||||||
<LearningPathCircleColumn
|
|
||||||
v-for="(circle, circleIndex) in topic.circles"
|
|
||||||
:key="circle.id"
|
|
||||||
:circle="circle"
|
|
||||||
:next-learning-content="props.nextLearningContent"
|
|
||||||
:is-first-circle="isFirstCircle(topicIndex, circleIndex)"
|
|
||||||
:is-last-circle="
|
|
||||||
isLastCircle(topicIndex, circleIndex, topic.circles.length)
|
|
||||||
"
|
|
||||||
:override-circle-url="
|
|
||||||
props.overrideCircleUrlBase
|
|
||||||
? `${props.overrideCircleUrlBase}/${circle.slug}`
|
|
||||||
: undefined
|
|
||||||
"
|
|
||||||
></LearningPathCircleColumn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<LearningPathScrollButton
|
<LearningPathScrollButton
|
||||||
v-show="!arrivedState.right"
|
v-show="!arrivedState.right"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
|
import { COURSE_PROFILE_ALL_FILTER } from "@/constants";
|
||||||
|
import type { DropdownSelectable } from "@/types";
|
||||||
|
import { useTranslation } from "i18next-vue";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
profiles?: string[];
|
||||||
|
selected?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
const emit = defineEmits(["select"]);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const items = computed(() => {
|
||||||
|
return props.profiles?.map((p) => ({ id: p, name: t(`profile.${p}`) })) || [];
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedItem = computed(() => {
|
||||||
|
if (props.selected) {
|
||||||
|
return { id: props.selected || "", name: t(`profile.${props.selected}`) };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: COURSE_PROFILE_ALL_FILTER,
|
||||||
|
name: t(`profile.${COURSE_PROFILE_ALL_FILTER}`),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateFilter = (e: DropdownSelectable) => {
|
||||||
|
emit("select", e.id);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex-grow">
|
||||||
|
<h5 class="mb-4">{{ $t("a.Zulassungsprofil") }}:</h5>
|
||||||
|
<div class="mb-4 flex gap-4">
|
||||||
|
<ItDropdownSelect
|
||||||
|
:items="items"
|
||||||
|
class="min-w-[18rem]"
|
||||||
|
:model-value="selectedItem"
|
||||||
|
@update:model-value="updateFilter"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
|
import { COURSE_PROFILE_ALL_FILTER } from "@/constants";
|
||||||
|
import { COURSE_QUERY } from "@/graphql/queries";
|
||||||
import type {
|
import type {
|
||||||
CircleSectorData,
|
CircleSectorData,
|
||||||
CircleSectorProgress,
|
CircleSectorProgress,
|
||||||
|
|
@ -7,6 +10,8 @@ import {
|
||||||
someFinishedInLearningSequence,
|
someFinishedInLearningSequence,
|
||||||
} from "@/services/circle";
|
} from "@/services/circle";
|
||||||
import type { CircleType } from "@/types";
|
import type { CircleType } from "@/types";
|
||||||
|
import { useQuery } from "@urql/vue";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
export function calculateCircleSectorData(circle: CircleType): CircleSectorData[] {
|
export function calculateCircleSectorData(circle: CircleType): CircleSectorData[] {
|
||||||
return circle.learning_sequences.map((ls) => {
|
return circle.learning_sequences.map((ls) => {
|
||||||
|
|
@ -21,3 +26,42 @@ export function calculateCircleSectorData(circle: CircleType): CircleSectorData[
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useCourseFilter(courseSlug: string, courseSessionId?: string) {
|
||||||
|
const csId = computed(() => {
|
||||||
|
if (courseSessionId) {
|
||||||
|
return courseSessionId;
|
||||||
|
}
|
||||||
|
// assume we're on a page with a current course session
|
||||||
|
const courseSession = useCurrentCourseSession();
|
||||||
|
return courseSession.value.id;
|
||||||
|
});
|
||||||
|
const courseReactiveResult = useQuery({
|
||||||
|
query: COURSE_QUERY,
|
||||||
|
variables: { slug: courseSlug },
|
||||||
|
});
|
||||||
|
const courseReactive = computed(() => courseReactiveResult.data.value?.course);
|
||||||
|
const courseSessionUser = computed(() => {
|
||||||
|
return courseReactive.value?.course_session_users.find(
|
||||||
|
(e) => e?.course_session.id === csId.value
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const filter = computed(() => {
|
||||||
|
return courseSessionUser.value?.chosen_profile || "";
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
filter,
|
||||||
|
courseReactive,
|
||||||
|
courseSessionUser,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterCircles(filter: string | undefined, circles: CircleType[]) {
|
||||||
|
if (filter === undefined || filter === "" || filter === COURSE_PROFILE_ALL_FILTER) {
|
||||||
|
return circles;
|
||||||
|
}
|
||||||
|
return circles.filter(
|
||||||
|
(circle) => circle.profiles.indexOf(filter as string) > -1 || circle.is_base_circle
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import WizardPage from "@/components/onboarding/WizardPage.vue";
|
||||||
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
|
import { useEntities } from "@/services/entities";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import type { DropdownSelectable } from "@/types";
|
||||||
|
import { useTranslation } from "i18next-vue";
|
||||||
|
import { computed, ref, watch } from "vue";
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const user = useUserStore();
|
||||||
|
|
||||||
|
const { courseProfiles } = useEntities();
|
||||||
|
|
||||||
|
const selectedCourseProfile = ref({
|
||||||
|
id: 0,
|
||||||
|
name: t("a.Auswählen"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const validCourseProfile = computed(() => {
|
||||||
|
return selectedCourseProfile.value.id !== 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(selectedCourseProfile, async (courseProfile: DropdownSelectable) => {
|
||||||
|
const courseProfileWithCode = courseProfiles.value.find(
|
||||||
|
(cp) => cp.id === courseProfile.id
|
||||||
|
);
|
||||||
|
if (courseProfileWithCode) {
|
||||||
|
user.updateChosenCourseProfile(courseProfileWithCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const courseProfilesToDropdown = computed(() => {
|
||||||
|
return courseProfiles.value.map((profile) => ({
|
||||||
|
...profile,
|
||||||
|
name: t(`profile.${profile.code}`),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<WizardPage :step="2">
|
||||||
|
<template #content>
|
||||||
|
<h2 class="my-10" data-cy="account-course-profile-title">
|
||||||
|
{{ $t("a.Zulassungsprofil auswählen") }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p class="mb-6 max-w-md hyphens-none">
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
"a.Wähle ein Zulassungsprofil, damit du deinen Lehrgang an der richtigen Stelle beginnen kannst. Du kannst ihn später jederzeit ändern."
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ItDropdownSelect
|
||||||
|
v-model="selectedCourseProfile"
|
||||||
|
:items="courseProfilesToDropdown"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<router-link v-slot="{ navigate }" :to="{ name: 'checkoutAddress' }" custom>
|
||||||
|
<button
|
||||||
|
:disabled="!validCourseProfile"
|
||||||
|
class="btn-blue flex items-center"
|
||||||
|
role="link"
|
||||||
|
data-cy="continue-button"
|
||||||
|
@click="navigate"
|
||||||
|
>
|
||||||
|
{{ $t("general.next") }}
|
||||||
|
<it-icon-arrow-right class="it-icon ml-2 h-6 w-6" />
|
||||||
|
</button>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
</WizardPage>
|
||||||
|
</template>
|
||||||
|
|
@ -229,6 +229,7 @@ const executePayment = async () => {
|
||||||
redirect_url: fullHost,
|
redirect_url: fullHost,
|
||||||
address: addressData,
|
address: addressData,
|
||||||
product: props.courseType,
|
product: props.courseType,
|
||||||
|
chosen_profile: user.chosen_profile?.id || "",
|
||||||
with_cembra_byjuno_invoice: address.value.payment_method === "cembra_byjuno",
|
with_cembra_byjuno_invoice: address.value.payment_method === "cembra_byjuno",
|
||||||
device_fingerprint_session_key: getLocalSessionKey(),
|
device_fingerprint_session_key: getLocalSessionKey(),
|
||||||
}).then((res: any) => {
|
}).then((res: any) => {
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,14 @@ const { t } = useTranslation();
|
||||||
$t("Füge dein Profilbild hinzu und ergänze die fehlenden Angaben.")
|
$t("Füge dein Profilbild hinzu und ergänze die fehlenden Angaben.")
|
||||||
}}
|
}}
|
||||||
</li>
|
</li>
|
||||||
|
<li class="relative pl-8">
|
||||||
|
<span class="font-bold">{{ $t("a.Zulassungsprofil auswählen") }}:</span>
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
"a.Wähle ein Zulassungsprofil, damit du deinen Lehrgang an der richtigen Stelle beginnen kannst."
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</li>
|
||||||
<li class="relative pl-8">
|
<li class="relative pl-8">
|
||||||
<span class="font-bold">{{ $t("a.Lehrgang kaufen") }}:</span>
|
<span class="font-bold">{{ $t("a.Lehrgang kaufen") }}:</span>
|
||||||
{{
|
{{
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { calculateCircleSectorData } from "@/pages/learningPath/learningPathPage/utils";
|
|
||||||
import { useCourseDataWithCompletion } from "@/composables";
|
import { useCourseDataWithCompletion } from "@/composables";
|
||||||
import UserProfileContent from "@/components/userProfile/UserProfileContent.vue";
|
import UserProfileContent from "@/components/userProfile/UserProfileContent.vue";
|
||||||
import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue";
|
|
||||||
import LearningSequence from "@/pages/learningPath/circlePage/LearningSequence.vue";
|
import LearningSequence from "@/pages/learningPath/circlePage/LearningSequence.vue";
|
||||||
import { ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import type { CircleType } from "@/types";
|
import type { CircleType } from "@/types";
|
||||||
|
import { COURSE_QUERY } from "@/graphql/queries";
|
||||||
|
import { useQuery } from "@urql/vue";
|
||||||
|
import UserProfileTopicList from "./UserProfileTopicList.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
@ -20,6 +21,23 @@ function selectCircle(circle: CircleType) {
|
||||||
selectedCircle.value = circle;
|
selectedCircle.value = circle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const courseReactiveResult = useQuery({
|
||||||
|
query: COURSE_QUERY,
|
||||||
|
variables: { slug: props.courseSlug, user: props.userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
const courseReactive = computed(() => courseReactiveResult.data.value?.course);
|
||||||
|
const courseSessionUsers = computed(() => {
|
||||||
|
return courseReactive.value?.course_session_users;
|
||||||
|
});
|
||||||
|
|
||||||
|
const chosenProfile = computed(() => {
|
||||||
|
if (courseSessionUsers.value && courseSessionUsers.value.length > 0) {
|
||||||
|
return courseSessionUsers.value[0]?.chosen_profile;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
watch(lpQueryResult.learningPath, () => {
|
watch(lpQueryResult.learningPath, () => {
|
||||||
if (lpQueryResult.learningPath?.value?.topics?.length) {
|
if (lpQueryResult.learningPath?.value?.topics?.length) {
|
||||||
selectCircle(lpQueryResult.learningPath.value.topics[0].circles[0]);
|
selectCircle(lpQueryResult.learningPath.value.topics[0].circles[0]);
|
||||||
|
|
@ -30,28 +48,21 @@ watch(lpQueryResult.learningPath, () => {
|
||||||
<template>
|
<template>
|
||||||
<UserProfileContent>
|
<UserProfileContent>
|
||||||
<template #side>
|
<template #side>
|
||||||
<div
|
<div v-if="chosenProfile">
|
||||||
|
<h3 class="mb-4 text-base font-bold">
|
||||||
|
Zulassungsprofil:
|
||||||
|
<br />
|
||||||
|
{{ $t(`profile.${chosenProfile}`) }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<UserProfileTopicList
|
||||||
v-for="topic in lpQueryResult.learningPath?.value?.topics ?? []"
|
v-for="topic in lpQueryResult.learningPath?.value?.topics ?? []"
|
||||||
:key="topic.id"
|
:key="topic.id"
|
||||||
class="mb-4"
|
:topic="topic"
|
||||||
>
|
:filter="chosenProfile"
|
||||||
<h4 class="mb-1 font-semibold text-gray-800">
|
:selected-circle="selectedCircle"
|
||||||
{{ topic.title }}
|
@select-circle="selectCircle($event)"
|
||||||
</h4>
|
/>
|
||||||
<button
|
|
||||||
v-for="circle in topic.circles"
|
|
||||||
:key="circle.id"
|
|
||||||
class="flex w-full items-center space-x-2 p-2 pr-4 hover:bg-gray-200 lg:pr-8"
|
|
||||||
:class="{ 'bg-gray-200': selectedCircle === circle }"
|
|
||||||
@click="selectCircle(circle)"
|
|
||||||
>
|
|
||||||
<LearningPathCircle
|
|
||||||
:sectors="calculateCircleSectorData(circle)"
|
|
||||||
class="h-10 w-10 snap-center rounded-full bg-white p-0.5"
|
|
||||||
></LearningPathCircle>
|
|
||||||
<span>{{ circle.title }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template #main>
|
<template #main>
|
||||||
<ol v-if="selectedCircle" class="flex-auto bg-gray-200 px-6 py-4 lg:px-16">
|
<ol v-if="selectedCircle" class="flex-auto bg-gray-200 px-6 py-4 lg:px-16">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { CircleType, TopicType } from "@/types";
|
||||||
|
import { calculateCircleSectorData } from "@/pages/learningPath/learningPathPage/utils";
|
||||||
|
import LearningPathCircle from "../learningPath/learningPathPage/LearningPathCircle.vue";
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { COURSE_PROFILE_ALL_FILTER } from "@/constants";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
topic: TopicType;
|
||||||
|
selectedCircle?: CircleType;
|
||||||
|
filter?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
defineEmits(["select-circle"]);
|
||||||
|
|
||||||
|
const filteredCircles = computed(() => {
|
||||||
|
const circles = props.topic.circles;
|
||||||
|
const filter = props.filter;
|
||||||
|
if (filter === undefined || filter === "" || filter === COURSE_PROFILE_ALL_FILTER) {
|
||||||
|
return circles;
|
||||||
|
}
|
||||||
|
return circles.filter(
|
||||||
|
(circle) => circle.profiles.indexOf(filter as string) > -1 || circle.is_base_circle
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mb-4">
|
||||||
|
<h4 class="mb-1 font-semibold text-gray-800">{{ topic.title }} {{ filter }}</h4>
|
||||||
|
<button
|
||||||
|
v-for="circle in filteredCircles"
|
||||||
|
:key="circle.id"
|
||||||
|
class="flex w-full items-center space-x-2 p-2 pr-4 hover:bg-gray-200 lg:pr-8"
|
||||||
|
:class="{ 'bg-gray-200': selectedCircle === circle }"
|
||||||
|
@click="$emit('select-circle', circle)"
|
||||||
|
>
|
||||||
|
<LearningPathCircle
|
||||||
|
:sectors="calculateCircleSectorData(circle)"
|
||||||
|
class="h-10 w-10 snap-center rounded-full bg-white p-0.5"
|
||||||
|
></LearningPathCircle>
|
||||||
|
<span>{{ circle.title }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -61,7 +61,7 @@ describe("Onboarding", () => {
|
||||||
mockNext
|
mockNext
|
||||||
);
|
);
|
||||||
expect(mockNext).toHaveBeenCalledWith({
|
expect(mockNext).toHaveBeenCalledWith({
|
||||||
name: "checkoutAddress",
|
name: "accountCourseProfile",
|
||||||
params: { courseType: testCase },
|
params: { courseType: testCase },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -417,6 +417,12 @@ const router = createRouter({
|
||||||
component: () => import("@/pages/onboarding/uk/SetupComplete.vue"),
|
component: () => import("@/pages/onboarding/uk/SetupComplete.vue"),
|
||||||
name: "setupComplete",
|
name: "setupComplete",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "account/course-profile",
|
||||||
|
component: () => import("@/pages/onboarding/vv/AccountCourseProfile.vue"),
|
||||||
|
name: "accountCourseProfile",
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "checkout/address",
|
path: "checkout/address",
|
||||||
component: () => import("@/pages/onboarding/vv/CheckoutAddress.vue"),
|
component: () => import("@/pages/onboarding/vv/CheckoutAddress.vue"),
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,21 @@ export type Country = {
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CourseProfile = {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
|
|
||||||
export function useEntities() {
|
export function useEntities() {
|
||||||
const countries: Ref<Country[]> = ref([]);
|
const countries: Ref<Country[]> = ref([]);
|
||||||
const organisations: Ref<Organisation[]> = ref([]);
|
const organisations: Ref<Organisation[]> = ref([]);
|
||||||
|
const courseProfiles: Ref<CourseProfile[]> = ref([]);
|
||||||
|
|
||||||
itGetCached("/api/core/entities/").then((res: any) => {
|
itGetCached("/api/core/entities/").then((res: any) => {
|
||||||
countries.value = res.countries;
|
countries.value = res.countries;
|
||||||
organisations.value = res.organisations;
|
organisations.value = res.organisations;
|
||||||
|
courseProfiles.value = res.courseProfiles;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { organisations, countries };
|
return { organisations, countries, courseProfiles };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export function profileNextRoute(courseType: string | string[]) {
|
||||||
}
|
}
|
||||||
// vv- -> vv-de, vv-fr or vv-it
|
// vv- -> vv-de, vv-fr or vv-it
|
||||||
if (isString(courseType) && startsWith(courseType, "vv-")) {
|
if (isString(courseType) && startsWith(courseType, "vv-")) {
|
||||||
return "checkoutAddress";
|
return "accountCourseProfile";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { bustItGetCache, itGetCached, itPost } from "@/fetchHelpers";
|
import { bustItGetCache, itGetCached, itPost } from "@/fetchHelpers";
|
||||||
import { setI18nLanguage } from "@/i18nextWrapper";
|
import { setI18nLanguage } from "@/i18nextWrapper";
|
||||||
import type { Country } from "@/services/entities";
|
import type { Country, CourseProfile } from "@/services/entities";
|
||||||
import { directUpload } from "@/services/files";
|
import { directUpload } from "@/services/files";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
@ -44,6 +44,7 @@ export interface User {
|
||||||
organisation_postal_code: string;
|
organisation_postal_code: string;
|
||||||
organisation_city: string;
|
organisation_city: string;
|
||||||
organisation_country: Country | null;
|
organisation_country: Country | null;
|
||||||
|
chosen_profile?: CourseProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
let defaultLanguage: AvailableLanguages = "de";
|
let defaultLanguage: AvailableLanguages = "de";
|
||||||
|
|
@ -89,6 +90,7 @@ const initialUserState: User = {
|
||||||
organisation_postal_code: "",
|
organisation_postal_code: "",
|
||||||
organisation_city: "",
|
organisation_city: "",
|
||||||
organisation_country: null,
|
organisation_country: null,
|
||||||
|
chosen_profile: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function setLocale(language: AvailableLanguages) {
|
async function setLocale(language: AvailableLanguages) {
|
||||||
|
|
@ -176,5 +178,8 @@ export const useUserStore = defineStore({
|
||||||
await itPost("/api/core/me/", profileData, { method: "PUT" });
|
await itPost("/api/core/me/", profileData, { method: "PUT" });
|
||||||
Object.assign(this.$state, profileData);
|
Object.assign(this.$state, profileData);
|
||||||
},
|
},
|
||||||
|
updateChosenCourseProfile(courseProfile: CourseProfile) {
|
||||||
|
Object.assign(this.$state, { chosen_profile: courseProfile });
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -202,6 +202,7 @@ export interface Course {
|
||||||
title: string;
|
title: string;
|
||||||
category_name: string;
|
category_name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
profiles: string[];
|
||||||
configuration: CourseConfiguration;
|
configuration: CourseConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -408,6 +409,10 @@ export interface CompetenceCertificateAssignment extends BaseCourseWagtailPage {
|
||||||
evaluation_points_reason: string;
|
evaluation_points_reason: string;
|
||||||
evaluation_max_points: number | null;
|
evaluation_max_points: number | null;
|
||||||
evaluation_passed: boolean | null;
|
evaluation_passed: boolean | null;
|
||||||
|
course_session: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
@apply underline underline-offset-2;
|
@apply cursor-pointer underline underline-offset-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-large {
|
.link-large {
|
||||||
|
|
@ -167,6 +167,14 @@ textarea {
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag-inactive {
|
||||||
|
@apply rounded-full border-2 border-blue-900 px-4 py-2 font-semibold text-blue-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-active {
|
||||||
|
@apply rounded-full bg-blue-900 px-4 py-2 font-semibold text-white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
|
@ -181,7 +189,9 @@ textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-scrollbar {
|
.no-scrollbar {
|
||||||
-ms-overflow-style: none; /* IE and Edge */
|
-ms-overflow-style: none;
|
||||||
scrollbar-width: none; /* Firefox */
|
/* IE and Edge */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* Firefox */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,22 @@
|
||||||
// ids for cypress test data
|
// ids for cypress test data
|
||||||
export const ADMIN_USER_ID = "872efd96-3bd7-4a1e-a239-2d72cad9f604"
|
export const ADMIN_USER_ID = "872efd96-3bd7-4a1e-a239-2d72cad9f604";
|
||||||
export const TEST_SUPERVISOR1_USER_ID = "a9a8b741-f115-4521-af2d-7dfef673b8c5"
|
export const TEST_SUPERVISOR1_USER_ID = "a9a8b741-f115-4521-af2d-7dfef673b8c5";
|
||||||
export const TEST_TRAINER1_USER_ID = "b9e71f59-c44f-4290-b93a-9b3151e9a2fc"
|
export const TEST_TRAINER1_USER_ID = "b9e71f59-c44f-4290-b93a-9b3151e9a2fc";
|
||||||
export const TEST_TRAINER2_USER_ID = "299941ae-1e4b-4f45-8180-876c3ad340b4"
|
export const TEST_TRAINER2_USER_ID = "299941ae-1e4b-4f45-8180-876c3ad340b4";
|
||||||
export const TEST_STUDENT1_USER_ID = "65c73ad0-6d53-43a9-a4a4-64143f27b03a"
|
export const TEST_STUDENT1_USER_ID = "65c73ad0-6d53-43a9-a4a4-64143f27b03a";
|
||||||
export const TEST_STUDENT2_USER_ID = "19c40d94-15cc-4198-aaad-ef707c4b0900"
|
export const TEST_STUDENT2_USER_ID = "19c40d94-15cc-4198-aaad-ef707c4b0900";
|
||||||
export const TEST_STUDENT3_USER_ID = "bcf94dba-53bc-474b-a22d-e4af39aa042b"
|
export const TEST_STUDENT3_USER_ID = "bcf94dba-53bc-474b-a22d-e4af39aa042b";
|
||||||
export const TEST_MENTOR1_USER_ID = "d1f5f5a9-5b0a-4e1a-9e1a-9e9b5b5e1b1b"
|
export const TEST_MENTOR1_USER_ID = "d1f5f5a9-5b0a-4e1a-9e1a-9e9b5b5e1b1b";
|
||||||
export const TEST_STUDENT1_VV_USER_ID = "5ff59857-8de5-415e-a387-4449f9a0337a"
|
export const TEST_STUDENT1_VV_USER_ID = "5ff59857-8de5-415e-a387-4449f9a0337a";
|
||||||
export const TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID = "7e8ebf0b-e6e2-4022-88f4-6e663ba0a9db"
|
export const TEST_STUDENT2_VV_AND_VV_MENTOR_USER_ID =
|
||||||
export const TEST_USER_EMPTY_ID = "daecbabe-4ab9-4edf-a71f-4119042ccb02"
|
"7e8ebf0b-e6e2-4022-88f4-6e663ba0a9db";
|
||||||
|
export const TEST_USER_EMPTY_ID = "daecbabe-4ab9-4edf-a71f-4119042ccb02";
|
||||||
|
|
||||||
export const TEST_COURSE_SESSION_BERN_ID = -1;
|
export const TEST_COURSE_SESSION_BERN_ID = -1;
|
||||||
export const TEST_COURSE_SESSION_ZURICH_ID = -2;
|
export const TEST_COURSE_SESSION_ZURICH_ID = -2;
|
||||||
|
export const TEST_COURSE_SESSION_VV_ID = 1;
|
||||||
|
|
||||||
|
export const COURSE_PROFILE_LEBEN_ID = -1;
|
||||||
|
export const COURSE_PROFILE_NICHTLEBEN_ID = -2;
|
||||||
|
export const COURSE_PROFILE_KRANKENZUSATZ_ID = -3;
|
||||||
|
export const COURSE_PROFILE_ALL_ID = -99;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
import { TEST_USER_EMPTY_ID } from "../../consts";
|
import {
|
||||||
|
COURSE_PROFILE_ALL_ID,
|
||||||
|
COURSE_PROFILE_NICHTLEBEN_ID,
|
||||||
|
TEST_COURSE_SESSION_VV_ID,
|
||||||
|
TEST_USER_EMPTY_ID,
|
||||||
|
} from "../../consts";
|
||||||
import { login } from "../helpers";
|
import { login } from "../helpers";
|
||||||
|
|
||||||
describe("checkout.cy.js", () => {
|
describe("checkout.cy.js", () => {
|
||||||
|
|
@ -32,6 +37,15 @@ describe("checkout.cy.js", () => {
|
||||||
cy.get("#organisationDetailName").type("FdH GmbH");
|
cy.get("#organisationDetailName").type("FdH GmbH");
|
||||||
cy.get('[data-cy="continue-button"]').click();
|
cy.get('[data-cy="continue-button"]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="account-course-profile-title"]').should(
|
||||||
|
"have.text",
|
||||||
|
"Zulassungsprofil auswählen",
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.get('[data-cy="dropdown-select"]').click();
|
||||||
|
cy.get('[data-cy="dropdown-select-option-Nichtleben"]').click();
|
||||||
|
cy.get('[data-cy="continue-button"]').click();
|
||||||
|
|
||||||
cy.loadUser("id", TEST_USER_EMPTY_ID).then((u) => {
|
cy.loadUser("id", TEST_USER_EMPTY_ID).then((u) => {
|
||||||
expect(u.organisation_detail_name).to.equal("FdH GmbH");
|
expect(u.organisation_detail_name).to.equal("FdH GmbH");
|
||||||
// 2 -> andere Krankenversicherer
|
// 2 -> andere Krankenversicherer
|
||||||
|
|
@ -121,6 +135,12 @@ describe("checkout.cy.js", () => {
|
||||||
cy.loadCheckoutInformation("user_id", TEST_USER_EMPTY_ID).then((ci) => {
|
cy.loadCheckoutInformation("user_id", TEST_USER_EMPTY_ID).then((ci) => {
|
||||||
expect(ci.state).to.equal("paid");
|
expect(ci.state).to.equal("paid");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cy.loadCourseSessionUser("user_id", TEST_USER_EMPTY_ID).then((csu) => {
|
||||||
|
expect(csu.role).to.equal("MEMBER");
|
||||||
|
expect(csu.course_session).to.equal(TEST_COURSE_SESSION_VV_ID);
|
||||||
|
expect(csu.chosen_profile).to.equal(COURSE_PROFILE_NICHTLEBEN_ID);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can checkout and pay Versicherungsvermittlerin with Cembra invoice", () => {
|
it("can checkout and pay Versicherungsvermittlerin with Cembra invoice", () => {
|
||||||
|
|
@ -143,6 +163,15 @@ describe("checkout.cy.js", () => {
|
||||||
cy.get('[data-cy="dropdown-select-option-Baloise"]').click();
|
cy.get('[data-cy="dropdown-select-option-Baloise"]').click();
|
||||||
cy.get('[data-cy="continue-button"]').click();
|
cy.get('[data-cy="continue-button"]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="account-course-profile-title"]').should(
|
||||||
|
"have.text",
|
||||||
|
"Zulassungsprofil auswählen",
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.get('[data-cy="dropdown-select"]').click();
|
||||||
|
cy.get('[data-cy="dropdown-select-option-Allbranche"]').click();
|
||||||
|
cy.get('[data-cy="continue-button"]').click();
|
||||||
|
|
||||||
// Adressdaten ausfüllen
|
// Adressdaten ausfüllen
|
||||||
cy.get('[data-cy="account-checkout-title"]').should(
|
cy.get('[data-cy="account-checkout-title"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
|
|
@ -236,5 +265,32 @@ describe("checkout.cy.js", () => {
|
||||||
// 7 -> Baloise
|
// 7 -> Baloise
|
||||||
expect(u.organisation).to.equal(7);
|
expect(u.organisation).to.equal(7);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// pay
|
||||||
|
cy.get('[data-cy="pay-button"]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="checkout-success-title"]').should(
|
||||||
|
"contain",
|
||||||
|
"Gratuliere",
|
||||||
|
);
|
||||||
|
// wait for payment callback
|
||||||
|
cy.wait(3000);
|
||||||
|
cy.get('[data-cy="start-vv-button"]').click();
|
||||||
|
|
||||||
|
// back on dashboard page
|
||||||
|
cy.get('[data-cy="db-course-title"]').should(
|
||||||
|
"contain",
|
||||||
|
"Versicherungsvermittler",
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.loadCheckoutInformation("user_id", TEST_USER_EMPTY_ID).then((ci) => {
|
||||||
|
expect(ci.state).to.equal("paid");
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.loadCourseSessionUser("user_id", TEST_USER_EMPTY_ID).then((csu) => {
|
||||||
|
expect(csu.role).to.equal("MEMBER");
|
||||||
|
expect(csu.course_session).to.equal(TEST_COURSE_SESSION_VV_ID);
|
||||||
|
expect(csu.chosen_profile).to.equal(COURSE_PROFILE_ALL_ID);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,26 @@
|
||||||
import { login } from "../helpers";
|
import { login } from "../helpers"
|
||||||
|
|
||||||
describe("competenceCertificate.cy.js", () => {
|
describe("competenceCertificate.cy.js", () => {
|
||||||
beforeEach(() => {});
|
beforeEach(() => { })
|
||||||
|
|
||||||
it("check without points", () => {
|
it("check without points", () => {
|
||||||
cy.manageCommand("cypress_reset");
|
cy.manageCommand("cypress_reset")
|
||||||
login("test-student1@example.com", "test");
|
login("test-student1@example.com", "test")
|
||||||
cy.visit("/course/test-lehrgang/competence");
|
cy.visit("/course/test-lehrgang/competence")
|
||||||
|
|
||||||
cy.get('[data-cy="certificate-total-points-text"]').contains(
|
cy.get('[data-cy="certificate-total-points-text"]').contains(
|
||||||
"Der Punktestand wird zu einem späteren Zeitpunkt berechnet."
|
"Der Punktestand wird zu einem späteren Zeitpunkt berechnet."
|
||||||
);
|
)
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||||
).and("contain", "0 von 2 Kompetenznachweis-Elementen");
|
).and("contain", "0 von 2 Kompetenznachweis-Elementen")
|
||||||
|
|
||||||
// check on certificates page
|
// check on certificates page
|
||||||
cy.get('[data-cy="certificates-show-all-button"]').click();
|
cy.get('[data-cy="certificates-show-all-button"]').click()
|
||||||
cy.get('[data-cy="certificate-total-points-text"]').contains(
|
cy.get('[data-cy="certificate-total-points-text"]').contains(
|
||||||
"Der Punktestand wird zu einem späteren Zeitpunkt berechnet."
|
"Der Punktestand wird zu einem späteren Zeitpunkt berechnet."
|
||||||
);
|
)
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||||
|
|
@ -29,100 +29,100 @@ describe("competenceCertificate.cy.js", () => {
|
||||||
"contain",
|
"contain",
|
||||||
"Der Punktestand wird zu einem späteren Zeitpunkt berechnet."
|
"Der Punktestand wird zu einem späteren Zeitpunkt berechnet."
|
||||||
)
|
)
|
||||||
.and("contain", "0 von 2 Kompetenznachweis-Elementen");
|
.and("contain", "0 von 2 Kompetenznachweis-Elementen")
|
||||||
|
|
||||||
// check certificate detail page
|
// check certificate detail page
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
|
||||||
).click();
|
).click()
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
|
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
|
||||||
).should("contain", "Höchstpunktzahl");
|
).should("contain", "Höchstpunktzahl")
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
|
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
|
||||||
).should("contain", "Höchstpunktzahl");
|
).should("contain", "Höchstpunktzahl")
|
||||||
});
|
})
|
||||||
|
|
||||||
it("check with finished passed edoniq test", () => {
|
it("check with finished passed edoniq test", () => {
|
||||||
cy.manageCommand(
|
cy.manageCommand(
|
||||||
"cypress_reset --create-assignment-completion --create-edoniq-test-results 19 24 0"
|
"cypress_reset --create-assignment-completion --create-edoniq-test-results 19 24 0"
|
||||||
);
|
)
|
||||||
login("test-student1@example.com", "test");
|
login("test-student1@example.com", "test")
|
||||||
cy.visit("/course/test-lehrgang/competence");
|
cy.visit("/course/test-lehrgang/competence")
|
||||||
|
|
||||||
cy.get('[data-cy="certificate-total-points-text"]').contains(
|
cy.get('[data-cy="certificate-total-points-text"]').contains(
|
||||||
"Erfahrungsnote üK: 5"
|
"Erfahrungsnote üK: 5"
|
||||||
);
|
)
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||||
)
|
)
|
||||||
.should("contain", "Note: 5")
|
.should("contain", "Note: 5")
|
||||||
.and("contain", "1 von 2 Kompetenznachweis-Elementen");
|
.and("contain", "1 von 2 Kompetenznachweis-Elementen")
|
||||||
|
|
||||||
// check on certificates page
|
// check on certificates page
|
||||||
cy.get('[data-cy="certificates-show-all-button"]').click();
|
cy.get('[data-cy="certificates-show-all-button"]').click()
|
||||||
cy.get('[data-cy="certificate-total-points-text"]')
|
cy.get('[data-cy="certificate-total-points-text"]')
|
||||||
.should("contain", "Erfahrungsnote üK")
|
.should("contain", "Erfahrungsnote üK")
|
||||||
.and("contain", "Zwischenstand");
|
.and("contain", "Zwischenstand")
|
||||||
cy.get('[data-cy="certificate-total-grade"]').should("contain", "Note: 5");
|
cy.get('[data-cy="certificate-total-grade"]').should("contain", "Note: 5")
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
||||||
).should("contain", "Note: 5");
|
).should("contain", "Note: 5")
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
||||||
).should("contain", "Ungerundete Note: 4.96");
|
).should("contain", "Ungerundete Note: 4.96")
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||||
)
|
)
|
||||||
.and("contain", "Zwischenstand")
|
.and("contain", "Zwischenstand")
|
||||||
.and("contain", "1 von 2 Kompetenznachweis-Elementen");
|
.and("contain", "1 von 2 Kompetenznachweis-Elementen")
|
||||||
|
|
||||||
// check certificate detail page
|
// check certificate detail page
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
|
||||||
).click();
|
).click()
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
|
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
|
||||||
)
|
)
|
||||||
.should("contain", "Höchstpunktzahl")
|
.should("contain", "Höchstpunktzahl")
|
||||||
.and("contain", "Ergebnisse abgegeben");
|
.and("contain", "Ergebnisse abgegeben")
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
|
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
|
||||||
)
|
)
|
||||||
.should("contain", "19")
|
.should("contain", "19")
|
||||||
.and("contain", "Bewertung freigegeben")
|
.and("contain", "Bewertung freigegeben")
|
||||||
.and("not.contain", "Nicht Bestanden");
|
.and("not.contain", "Nicht Bestanden")
|
||||||
|
|
||||||
// it can open learning content page directly
|
// it can open learning content page directly
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"] [data-cy="open-learning-content"]'
|
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"] [data-cy="open-learning-content"]'
|
||||||
).click();
|
).click()
|
||||||
cy.get('[data-cy="test-result"]')
|
cy.get('[data-cy="test-result"]')
|
||||||
.should("contain", "19 von 24 Punkten")
|
.should("contain", "19 von 24 Punkten")
|
||||||
.and("contain", "79%");
|
.and("contain", "79%")
|
||||||
});
|
})
|
||||||
|
|
||||||
it("check with finished failed edoniq test", () => {
|
it("check with finished failed edoniq test", () => {
|
||||||
cy.manageCommand(
|
cy.manageCommand(
|
||||||
"cypress_reset --create-assignment-completion --create-edoniq-test-results 10 24 0"
|
"cypress_reset --create-assignment-completion --create-edoniq-test-results 10 24 0"
|
||||||
);
|
)
|
||||||
login("test-student1@example.com", "test");
|
login("test-student1@example.com", "test")
|
||||||
|
|
||||||
// go to certificate detail page
|
// go to certificate detail page
|
||||||
cy.visit(
|
cy.visit(
|
||||||
"/course/test-lehrgang/competence/certificates/kompetenznachweis-1"
|
"/course/test-lehrgang/competence/certificates/kompetenznachweis-1"
|
||||||
);
|
)
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
||||||
).should("contain", "Note: 3");
|
).should("contain", "Note: 3")
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
||||||
).should("contain", "Ungerundete Note: 3.08");
|
).should("contain", "Ungerundete Note: 3.08")
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
|
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
|
||||||
|
|
@ -130,90 +130,170 @@ describe("competenceCertificate.cy.js", () => {
|
||||||
.should("contain", "10")
|
.should("contain", "10")
|
||||||
.and("contain", "Bewertung freigegeben")
|
.and("contain", "Bewertung freigegeben")
|
||||||
.and("contain", "42%")
|
.and("contain", "42%")
|
||||||
.and("contain", "Nicht bestanden");
|
.and("contain", "Nicht bestanden")
|
||||||
|
|
||||||
// it can open learning content page directly
|
// it can open learning content page directly
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"] [data-cy="open-learning-content"]'
|
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"] [data-cy="open-learning-content"]'
|
||||||
).click();
|
).click()
|
||||||
cy.get('[data-cy="test-result"]')
|
cy.get('[data-cy="test-result"]')
|
||||||
.should("contain", "10 von 24 Punkten")
|
.should("contain", "10 von 24 Punkten")
|
||||||
.and("contain", "42%")
|
.and("contain", "42%")
|
||||||
.and("contain", "Nicht bestanden");
|
.and("contain", "Nicht bestanden")
|
||||||
});
|
})
|
||||||
|
|
||||||
it("check with finished edoniq test and finished casework", () => {
|
it("check with finished edoniq test and finished casework", () => {
|
||||||
cy.manageCommand(
|
cy.manageCommand(
|
||||||
"cypress_reset --create-assignment-evaluation --create-edoniq-test-results 19 24 0"
|
"cypress_reset --create-assignment-evaluation --create-edoniq-test-results 19 24 0"
|
||||||
);
|
)
|
||||||
login("test-student1@example.com", "test");
|
login("test-student1@example.com", "test")
|
||||||
cy.visit("/course/test-lehrgang/competence");
|
cy.visit("/course/test-lehrgang/competence")
|
||||||
|
|
||||||
cy.get('[data-cy="certificate-total-points-text"]').contains(
|
cy.get('[data-cy="certificate-total-points-text"]').contains(
|
||||||
"Erfahrungsnote üK: 5.5"
|
"Erfahrungsnote üK: 5.5"
|
||||||
);
|
)
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||||
)
|
)
|
||||||
.should("contain", "Note: 5.5")
|
.should("contain", "Note: 5.5")
|
||||||
.and("contain", "2 von 2 Kompetenznachweis-Elementen");
|
.and("contain", "2 von 2 Kompetenznachweis-Elementen")
|
||||||
|
|
||||||
// check on certificates page
|
// check on certificates page
|
||||||
cy.get('[data-cy="certificates-show-all-button"]').click();
|
cy.get('[data-cy="certificates-show-all-button"]').click()
|
||||||
cy.get('[data-cy="certificate-total-points-text"]')
|
cy.get('[data-cy="certificate-total-points-text"]')
|
||||||
.should("contain", "Erfahrungsnote üK")
|
.should("contain", "Erfahrungsnote üK")
|
||||||
.and("contain", "Note: 5.5")
|
.and("contain", "Note: 5.5")
|
||||||
.and("not.contain", "Zwischenstand");
|
.and("not.contain", "Zwischenstand")
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||||
)
|
)
|
||||||
.and("not.contain", "Zwischenstand")
|
.and("not.contain", "Zwischenstand")
|
||||||
.and("contain", "2 von 2 Kompetenznachweis-Elementen");
|
.and("contain", "2 von 2 Kompetenznachweis-Elementen")
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
||||||
).should("contain", "Note: 5.5");
|
).should("contain", "Note: 5.5")
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
||||||
).should("contain", "Ungerundete Note: 5.48");
|
).should("contain", "Ungerundete Note: 5.48")
|
||||||
|
|
||||||
// check certificate detail page
|
// check certificate detail page
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
|
||||||
).click();
|
).click()
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
|
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
|
||||||
)
|
)
|
||||||
.should("contain", "24")
|
.should("contain", "24")
|
||||||
.and("contain", "von 24 Punkten")
|
.and("contain", "von 24 Punkten")
|
||||||
.and("contain", "Bewertung freigegeben");
|
.and("contain", "Bewertung freigegeben")
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
|
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
|
||||||
)
|
)
|
||||||
.should("contain", "19")
|
.should("contain", "19")
|
||||||
.and("contain", "von 24 Punkten")
|
.and("contain", "von 24 Punkten")
|
||||||
.and("contain", "Bewertung freigegeben");
|
.and("contain", "Bewertung freigegeben")
|
||||||
});
|
})
|
||||||
|
|
||||||
|
it("check with finished edoniq test and finished casework in different course sessions", () => {
|
||||||
|
const TEST_TRAINER2_USER_ID = "299941ae-1e4b-4f45-8180-876c3ad340b4"
|
||||||
|
const TEST_STUDENT2_USER_ID = "19c40d94-15cc-4198-aaad-ef707c4b0900"
|
||||||
|
const TEST_COURSE_SESSION_ZURICH_ID = -2
|
||||||
|
cy.manageCommand(
|
||||||
|
`cypress_reset --create-assignment-evaluation --assignment-evaluation-user-id ${TEST_TRAINER2_USER_ID} --assignment-completion-user-id ${TEST_STUDENT2_USER_ID} --edoniq-user-id ${TEST_STUDENT2_USER_ID} --edoniq-course-session-id '${TEST_COURSE_SESSION_ZURICH_ID}' --create-edoniq-test-results 19 24 0`
|
||||||
|
)
|
||||||
|
login("test-student2@example.com", "test")
|
||||||
|
cy.visit("/course/test-lehrgang/competence")
|
||||||
|
|
||||||
|
cy.get('[data-cy="certificate-total-points-text"]').contains(
|
||||||
|
"Erfahrungsnote üK: 5.5"
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||||
|
)
|
||||||
|
.should("contain", "Note: 5.5")
|
||||||
|
.and("contain", "2 von 2 Kompetenznachweis-Elementen")
|
||||||
|
|
||||||
|
// check on certificates page
|
||||||
|
cy.get('[data-cy="certificates-show-all-button"]').click()
|
||||||
|
cy.get('[data-cy="certificate-total-points-text"]')
|
||||||
|
.should("contain", "Erfahrungsnote üK")
|
||||||
|
.and("contain", "Note: 5.5")
|
||||||
|
.and("not.contain", "Zwischenstand")
|
||||||
|
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||||
|
)
|
||||||
|
.and("not.contain", "Zwischenstand")
|
||||||
|
.and("contain", "2 von 2 Kompetenznachweis-Elementen")
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
||||||
|
).should("contain", "Note: 5.5")
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
||||||
|
).should("contain", "Ungerundete Note: 5.48")
|
||||||
|
|
||||||
|
// check certificate detail page
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
|
||||||
|
).click()
|
||||||
|
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
|
||||||
|
)
|
||||||
|
.should("contain", "24")
|
||||||
|
.and("contain", "von 24 Punkten")
|
||||||
|
.and("contain", "Bewertung freigegeben")
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
|
||||||
|
)
|
||||||
|
.should("contain", "19")
|
||||||
|
.and("contain", "von 24 Punkten")
|
||||||
|
.and("contain", "Bewertung freigegeben")
|
||||||
|
|
||||||
|
cy.get('[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice-course-session"]').should("contain", "Test Bern 2022 a")
|
||||||
|
cy.get('[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo-course-session"]').should("contain", "Test Zürich 2022 a")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("check show assignment in different course session", () => {
|
||||||
|
const TEST_TRAINER2_USER_ID = "299941ae-1e4b-4f45-8180-876c3ad340b4"
|
||||||
|
const TEST_STUDENT2_USER_ID = "19c40d94-15cc-4198-aaad-ef707c4b0900"
|
||||||
|
const TEST_COURSE_SESSION_ZURICH_ID = -2
|
||||||
|
cy.manageCommand(
|
||||||
|
`cypress_reset --create-assignment-evaluation --assignment-evaluation-user-id ${TEST_TRAINER2_USER_ID} --assignment-completion-user-id ${TEST_STUDENT2_USER_ID} --edoniq-user-id ${TEST_STUDENT2_USER_ID} --edoniq-course-session-id '${TEST_COURSE_SESSION_ZURICH_ID}' --create-edoniq-test-results 19 24 0`
|
||||||
|
)
|
||||||
|
login("test-student2@example.com", "test")
|
||||||
|
cy.visit("course/test-lehrgang/competence/certificates/kompetenznachweis-1")
|
||||||
|
|
||||||
|
cy.get('[data-cy="current-course-session-title"]').should("contain", "Test Bern 2022 a")
|
||||||
|
|
||||||
|
cy.get(
|
||||||
|
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"] [data-cy="open-learning-content"]'
|
||||||
|
).click()
|
||||||
|
|
||||||
|
cy.get('[data-cy="current-course-session-title"]').should("contain", "Test Zürich 2022 a")
|
||||||
|
})
|
||||||
|
|
||||||
it("check with finished edoniq test with deducted points", () => {
|
it("check with finished edoniq test with deducted points", () => {
|
||||||
cy.manageCommand(
|
cy.manageCommand(
|
||||||
"cypress_reset --create-assignment-completion --create-edoniq-test-results 19 24 8"
|
"cypress_reset --create-assignment-completion --create-edoniq-test-results 19 24 8"
|
||||||
);
|
)
|
||||||
login("test-student1@example.com", "test");
|
login("test-student1@example.com", "test")
|
||||||
|
|
||||||
// go to certificate detail page
|
// go to certificate detail page
|
||||||
cy.visit(
|
cy.visit(
|
||||||
"/course/test-lehrgang/competence/certificates/kompetenznachweis-1"
|
"/course/test-lehrgang/competence/certificates/kompetenznachweis-1"
|
||||||
);
|
)
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
||||||
).should("contain", "Note: 3.5");
|
).should("contain", "Note: 3.5")
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
||||||
).should("contain", "Ungerundete Note: 3.29");
|
).should("contain", "Ungerundete Note: 3.29")
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
|
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
|
||||||
|
|
@ -222,61 +302,61 @@ describe("competenceCertificate.cy.js", () => {
|
||||||
.and("contain", "Bewertung freigegeben")
|
.and("contain", "Bewertung freigegeben")
|
||||||
.and("contain", "46%")
|
.and("contain", "46%")
|
||||||
.and("contain", "mit Abzug")
|
.and("contain", "mit Abzug")
|
||||||
.and("contain", "Nicht bestanden");
|
.and("contain", "Nicht bestanden")
|
||||||
|
|
||||||
// it can open learning content page directly
|
// it can open learning content page directly
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"] [data-cy="open-learning-content"]'
|
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"] [data-cy="open-learning-content"]'
|
||||||
).click();
|
).click()
|
||||||
cy.get('[data-cy="test-result"]')
|
cy.get('[data-cy="test-result"]')
|
||||||
.should("contain", "11 von 24 Punkten")
|
.should("contain", "11 von 24 Punkten")
|
||||||
.and("contain", "46%")
|
.and("contain", "46%")
|
||||||
.and("contain", "Punkte aus Bewertung: 19")
|
.and("contain", "Punkte aus Bewertung: 19")
|
||||||
.and("contain", "Abgezogene Punkte: 8")
|
.and("contain", "Abgezogene Punkte: 8")
|
||||||
.and("contain", "Grund: Edoniq Punkteabzug Test")
|
.and("contain", "Grund: Edoniq Punkteabzug Test")
|
||||||
.and("contain", "Nicht bestanden");
|
.and("contain", "Nicht bestanden")
|
||||||
});
|
})
|
||||||
|
|
||||||
it("check with finished casework and points deducted", () => {
|
it("check with finished casework and points deducted", () => {
|
||||||
cy.manageCommand(
|
cy.manageCommand(
|
||||||
"cypress_reset --create-assignment-evaluation --assignment-evaluation-scores 4,6,4,3,2 --assignment-points-deducted 5"
|
"cypress_reset --create-assignment-evaluation --assignment-evaluation-scores 4,6,4,3,2 --assignment-points-deducted 5"
|
||||||
);
|
)
|
||||||
login("test-student1@example.com", "test");
|
login("test-student1@example.com", "test")
|
||||||
cy.visit("/course/test-lehrgang/competence");
|
cy.visit("/course/test-lehrgang/competence")
|
||||||
|
|
||||||
cy.get('[data-cy="certificate-total-points-text"]').contains(
|
cy.get('[data-cy="certificate-total-points-text"]').contains(
|
||||||
"Erfahrungsnote üK: 4"
|
"Erfahrungsnote üK: 4"
|
||||||
);
|
)
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||||
)
|
)
|
||||||
.should("contain", "Note: 4")
|
.should("contain", "Note: 4")
|
||||||
.and("contain", "1 von 2 Kompetenznachweis-Elementen");
|
.and("contain", "1 von 2 Kompetenznachweis-Elementen")
|
||||||
|
|
||||||
// check on certificates page
|
// check on certificates page
|
||||||
cy.get('[data-cy="certificates-show-all-button"]').click();
|
cy.get('[data-cy="certificates-show-all-button"]').click()
|
||||||
cy.get('[data-cy="certificate-total-points-text"]')
|
cy.get('[data-cy="certificate-total-points-text"]')
|
||||||
.should("contain", "Erfahrungsnote üK")
|
.should("contain", "Erfahrungsnote üK")
|
||||||
.and("contain", "Note: 4")
|
.and("contain", "Note: 4")
|
||||||
.and("contain", "Zwischenstand");
|
.and("contain", "Zwischenstand")
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
|
||||||
)
|
)
|
||||||
.and("contain", "Zwischenstand")
|
.and("contain", "Zwischenstand")
|
||||||
.and("contain", "1 von 2 Kompetenznachweis-Elementen");
|
.and("contain", "1 von 2 Kompetenznachweis-Elementen")
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
|
||||||
).should("contain", "Note: 4");
|
).should("contain", "Note: 4")
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
|
||||||
).should("contain", "Ungerundete Note: 3.92");
|
).should("contain", "Ungerundete Note: 3.92")
|
||||||
|
|
||||||
// check certificate detail page
|
// check certificate detail page
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
|
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
|
||||||
).click();
|
).click()
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
|
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
|
||||||
|
|
@ -285,28 +365,28 @@ describe("competenceCertificate.cy.js", () => {
|
||||||
.and("contain", "von 24 Punkten")
|
.and("contain", "von 24 Punkten")
|
||||||
.and("contain", "58%")
|
.and("contain", "58%")
|
||||||
.and("contain", "mit Abzug")
|
.and("contain", "mit Abzug")
|
||||||
.and("contain", "Bewertung freigegeben");
|
.and("contain", "Bewertung freigegeben")
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"] [data-cy="open-learning-content"]'
|
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"] [data-cy="open-learning-content"]'
|
||||||
).click();
|
).click()
|
||||||
cy.get('[data-cy="user-points"]').should("contain", "14");
|
cy.get('[data-cy="user-points"]').should("contain", "14")
|
||||||
cy.get('[data-cy="total-points"]').should(
|
cy.get('[data-cy="total-points"]').should(
|
||||||
"contain",
|
"contain",
|
||||||
"von 24 Punkten (58%)"
|
"von 24 Punkten (58%)"
|
||||||
);
|
)
|
||||||
cy.get('[data-cy="points-deducted"]')
|
cy.get('[data-cy="points-deducted"]')
|
||||||
.should("contain", "Punkte aus Bewertung: 19")
|
.should("contain", "Punkte aus Bewertung: 19")
|
||||||
.and("contain", "Abgezogene Punkte: 5")
|
.and("contain", "Abgezogene Punkte: 5")
|
||||||
.and("contain", "Grund: Assignment Punkteabzug Test");
|
.and("contain", "Grund: Assignment Punkteabzug Test")
|
||||||
});
|
})
|
||||||
|
|
||||||
it("should display link to details", () => {
|
it("should display link to details", () => {
|
||||||
cy.manageCommand("cypress_reset");
|
cy.manageCommand("cypress_reset")
|
||||||
login("test-student1@example.com", "test");
|
login("test-student1@example.com", "test")
|
||||||
cy.visit("/course/test-lehrgang/competence/self-evaluation-and-feedback");
|
cy.visit("/course/test-lehrgang/competence/self-evaluation-and-feedback")
|
||||||
cy.get('[data-cy^="self-eval-"][data-cy$="-detail-url"]:first').contains(
|
cy.get('[data-cy^="self-eval-"][data-cy$="-detail-url"]:first').contains(
|
||||||
"Selbsteinschätzung anschauen"
|
"Selbsteinschätzung anschauen"
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
|
||||||
|
|
@ -50,20 +50,20 @@
|
||||||
// -- This is will overwrite an existing command --
|
// -- This is will overwrite an existing command --
|
||||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||||
|
|
||||||
const _ = Cypress._;
|
const _ = Cypress._
|
||||||
|
|
||||||
Cypress.Commands.add("manageCommand", (command, preCommand = "") => {
|
Cypress.Commands.add("manageCommand", (command, preCommand = "") => {
|
||||||
const execCommand = `${preCommand} python server/manage.py ${command} --settings=config.settings.test_cypress`;
|
const execCommand = `${preCommand} python server/manage.py ${command} --settings=config.settings.test_cypress`
|
||||||
console.log(execCommand);
|
console.log(execCommand)
|
||||||
// hack to add my asdf python instance to the path
|
// hack to add my asdf python instance to the path
|
||||||
// so I can run the test directly from within IntelliJ
|
// so I can run the test directly from within IntelliJ
|
||||||
let pythonPaths = [
|
let pythonPaths = [
|
||||||
"/Users/daniel/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin",
|
"/Users/daniel/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin",
|
||||||
"/Users/eliabieri/iterativ/vbv_lernwelt/.direnv/python-3.10.6/bin",
|
"/Users/eliabieri/iterativ/vbv_lernwelt/.direnv/python-3.10/bin",
|
||||||
"/Users/christiancueni/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin",
|
"/Users/christiancueni/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin",
|
||||||
"/Users/renzo/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin",
|
"/Users/renzo/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin",
|
||||||
];
|
]
|
||||||
let bashCommand = `PATH=${pythonPaths.join(":")}:$PATH && ${execCommand}`;
|
let bashCommand = `PATH=${pythonPaths.join(":")}:$PATH && ${execCommand}`
|
||||||
return cy
|
return cy
|
||||||
.exec(`bash -c "${bashCommand}"`, {
|
.exec(`bash -c "${bashCommand}"`, {
|
||||||
failOnNonZeroExit: true,
|
failOnNonZeroExit: true,
|
||||||
|
|
@ -73,14 +73,14 @@ Cypress.Commands.add("manageCommand", (command, preCommand = "") => {
|
||||||
throw new Error(`Execution of "${command}" failed
|
throw new Error(`Execution of "${command}" failed
|
||||||
Exit code: ${result.code}
|
Exit code: ${result.code}
|
||||||
Stdout:\n${result.stdout}
|
Stdout:\n${result.stdout}
|
||||||
Stderr:\n${result.stderr}`);
|
Stderr:\n${result.stderr}`)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("manageShellCommand", (command) => {
|
Cypress.Commands.add("manageShellCommand", (command) => {
|
||||||
return cy.manageCommand(`shell -c '${command}'`);
|
return cy.manageCommand(`shell -c '${command}'`)
|
||||||
});
|
})
|
||||||
|
|
||||||
function loadObjectJson(
|
function loadObjectJson(
|
||||||
key,
|
key,
|
||||||
|
|
@ -89,28 +89,28 @@ function loadObjectJson(
|
||||||
serializerModelPath,
|
serializerModelPath,
|
||||||
valueAsString = false,
|
valueAsString = false,
|
||||||
) {
|
) {
|
||||||
const djangoModel = _.last(djangoModelPath.split("."));
|
const djangoModel = _.last(djangoModelPath.split("."))
|
||||||
const djangoModelImportPath = _.initial(djangoModelPath.split(".")).join(".");
|
const djangoModelImportPath = _.initial(djangoModelPath.split(".")).join(".")
|
||||||
const serializerModel = _.last(serializerModelPath.split("."));
|
const serializerModel = _.last(serializerModelPath.split("."))
|
||||||
const serializerModelImportPath = _.initial(
|
const serializerModelImportPath = _.initial(
|
||||||
serializerModelPath.split("."),
|
serializerModelPath.split("."),
|
||||||
).join(".");
|
).join(".");
|
||||||
|
|
||||||
let filterPart = `${key}=${value}`;
|
let filterPart = `${key}=${value}`
|
||||||
if (valueAsString) {
|
if (valueAsString) {
|
||||||
filterPart = `${key}=\\"${value}\\"`;
|
filterPart = `${key}=\\"${value}\\"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.isArray(key)) {
|
if (_.isArray(key)) {
|
||||||
filterPart = _.zip(key, value)
|
filterPart = _.zip(key, value)
|
||||||
.map(([k, v]) => {
|
.map(([k, v]) => {
|
||||||
if (valueAsString) {
|
if (valueAsString) {
|
||||||
return `${k}=\\"${v}\\"`;
|
return `${k}=\\"${v}\\"`
|
||||||
} else {
|
} else {
|
||||||
return `${k}=${v}`;
|
return `${k}=${v}`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.join(",");
|
.join(",")
|
||||||
}
|
}
|
||||||
|
|
||||||
const command = `from ${djangoModelImportPath} import ${djangoModel};
|
const command = `from ${djangoModelImportPath} import ${djangoModel};
|
||||||
|
|
@ -119,13 +119,13 @@ function loadObjectJson(
|
||||||
object = ${djangoModel}.objects.filter(${filterPart}).first();
|
object = ${djangoModel}.objects.filter(${filterPart}).first();
|
||||||
print(create_json_from_objects(object, ${serializerModel}, many=False));
|
print(create_json_from_objects(object, ${serializerModel}, many=False));
|
||||||
exit();
|
exit();
|
||||||
`.replace(/(?:\r\n|\r|\n)/g, "");
|
`.replace(/(?:\r\n|\r|\n)/g, "")
|
||||||
return cy.manageShellCommand(command).then((result) => {
|
return cy.manageShellCommand(command).then((result) => {
|
||||||
const objectJson = JSON.parse(result.stdout);
|
const objectJson = JSON.parse(result.stdout)
|
||||||
// console.log(command);
|
// console.log(command);
|
||||||
console.log(objectJson);
|
console.log(objectJson)
|
||||||
return objectJson;
|
return objectJson
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add("loadAssignmentCompletion", (key, value) => {
|
Cypress.Commands.add("loadAssignmentCompletion", (key, value) => {
|
||||||
|
|
@ -138,6 +138,7 @@ Cypress.Commands.add("loadAssignmentCompletion", (key, value) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add("loadSecurityRequestResponseLog", (key, value) => {
|
Cypress.Commands.add("loadSecurityRequestResponseLog", (key, value) => {
|
||||||
return loadObjectJson(
|
return loadObjectJson(
|
||||||
key,
|
key,
|
||||||
|
|
@ -148,6 +149,7 @@ Cypress.Commands.add("loadSecurityRequestResponseLog", (key, value) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add("loadExternalApiRequestLog", (key, value) => {
|
Cypress.Commands.add("loadExternalApiRequestLog", (key, value) => {
|
||||||
return loadObjectJson(
|
return loadObjectJson(
|
||||||
key,
|
key,
|
||||||
|
|
@ -158,6 +160,7 @@ Cypress.Commands.add("loadExternalApiRequestLog", (key, value) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add("loadFeedbackResponse", (key, value) => {
|
Cypress.Commands.add("loadFeedbackResponse", (key, value) => {
|
||||||
return loadObjectJson(
|
return loadObjectJson(
|
||||||
key,
|
key,
|
||||||
|
|
@ -168,6 +171,7 @@ Cypress.Commands.add("loadFeedbackResponse", (key, value) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add("loadCheckoutInformation", (key, value) => {
|
Cypress.Commands.add("loadCheckoutInformation", (key, value) => {
|
||||||
return loadObjectJson(
|
return loadObjectJson(
|
||||||
key,
|
key,
|
||||||
|
|
@ -178,6 +182,7 @@ Cypress.Commands.add("loadCheckoutInformation", (key, value) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add("loadUser", (key, value) => {
|
Cypress.Commands.add("loadUser", (key, value) => {
|
||||||
return loadObjectJson(
|
return loadObjectJson(
|
||||||
key,
|
key,
|
||||||
|
|
@ -188,6 +193,15 @@ Cypress.Commands.add("loadUser", (key, value) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add("loadCourseSessionUser", (key, value) => {
|
||||||
|
return loadObjectJson(
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
"vbv_lernwelt.course.models.CourseSessionUser",
|
||||||
|
"vbv_lernwelt.course.serializers.CypressCourseSessionUserSerializer",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("makeSelfEvaluation", (answers, withCompletion = true) => {
|
Cypress.Commands.add("makeSelfEvaluation", (answers, withCompletion = true) => {
|
||||||
for (let i = 0; i < answers.length; i++) {
|
for (let i = 0; i < answers.length; i++) {
|
||||||
|
|
@ -200,28 +214,28 @@ Cypress.Commands.add("makeSelfEvaluation", (answers, withCompletion = true) => {
|
||||||
|
|
||||||
if (withCompletion) {
|
if (withCompletion) {
|
||||||
if (i < answers.length - 1) {
|
if (i < answers.length - 1) {
|
||||||
cy.get('[data-cy="next-step"]').click({ force: true });
|
cy.get('[data-cy="next-step"]').click({force: true});
|
||||||
} else {
|
} else {
|
||||||
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
cy.get('[data-cy="complete-and-continue"]').click({force: true});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cy.get('[data-cy="next-step"]').click({ force: true });
|
cy.get('[data-cy="next-step"]').click({force: true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("learningContentMultiLayoutNextStep", () => {
|
Cypress.Commands.add("learningContentMultiLayoutNextStep", () => {
|
||||||
return cy.get('[data-cy="next-step"]').click({ force: true });
|
return cy.get('[data-cy="next-step"]').click({force: true})
|
||||||
});
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("learningContentMultiLayoutPreviousStep", () => {
|
Cypress.Commands.add("learningContentMultiLayoutPreviousStep", () => {
|
||||||
return cy.get('[data-cy="previous-step"]').click({ force: true });
|
return cy.get('[data-cy="previous-step"]').click({force: true})
|
||||||
});
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("testLearningContentTitle", (title) => {
|
Cypress.Commands.add("testLearningContentTitle", (title) => {
|
||||||
return cy.get('[data-cy="lc-title"]').should("contain", title);
|
return cy.get('[data-cy="lc-title"]').should("contain", title)
|
||||||
});
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("testLearningContentSubtitle", (subtitle) => {
|
Cypress.Commands.add("testLearningContentSubtitle", (subtitle) => {
|
||||||
return cy.get('[data-cy="lc-subtitle"]').should("contain", subtitle);
|
return cy.get('[data-cy="lc-subtitle"]').should("contain", subtitle)
|
||||||
});
|
})
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -7,7 +7,7 @@ echo 'prettier:check'
|
||||||
(cd client && npm run prettier:check)
|
(cd client && npm run prettier:check)
|
||||||
|
|
||||||
echo 'lint and typecheck'
|
echo 'lint and typecheck'
|
||||||
(cd client && npm run lint && npm run typecheck)
|
(cd client && npm run lint:errors && npm run typecheck)
|
||||||
|
|
||||||
echo 'python ufmt check'
|
echo 'python ufmt check'
|
||||||
ufmt check server
|
ufmt check server
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"cypress:open": "cypress open",
|
"cypress:open": "cypress open",
|
||||||
"cypress:ci": "cypress-cloud run --parallel --record",
|
"cypress:ci": "cypress-cloud run --parallel --record",
|
||||||
|
"cypress:install": "cypress install",
|
||||||
"prettier": "npm run prettier --prefix client"
|
"prettier": "npm run prettier --prefix client"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ from rest_framework.response import Response
|
||||||
|
|
||||||
from vbv_lernwelt.core.models import Country, Organisation
|
from vbv_lernwelt.core.models import Country, Organisation
|
||||||
from vbv_lernwelt.core.serializers import CountrySerializer, OrganisationSerializer
|
from vbv_lernwelt.core.serializers import CountrySerializer, OrganisationSerializer
|
||||||
|
from vbv_lernwelt.learnpath.models import CourseProfile
|
||||||
|
from vbv_lernwelt.learnpath.serializers import CourseProfileSerializer
|
||||||
|
|
||||||
|
|
||||||
@api_view(["GET"])
|
@api_view(["GET"])
|
||||||
|
|
@ -26,4 +28,13 @@ def list_entities(request):
|
||||||
countries = CountrySerializer(
|
countries = CountrySerializer(
|
||||||
Country.objects.all(), many=True, context=context
|
Country.objects.all(), many=True, context=context
|
||||||
).data
|
).data
|
||||||
return Response({"organisations": organisations, "countries": countries})
|
course_profiles = CourseProfileSerializer(
|
||||||
|
CourseProfile.objects.all(), many=True, context=context
|
||||||
|
).data
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"organisations": organisations,
|
||||||
|
"countries": countries,
|
||||||
|
"courseProfiles": course_profiles,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from rest_framework.decorators import api_view, permission_classes
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
from rest_framework.generics import get_object_or_404
|
from rest_framework.generics import get_object_or_404
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
|
||||||
|
|
@ -64,12 +64,32 @@ from vbv_lernwelt.shop.models import CheckoutInformation
|
||||||
@click.option(
|
@click.option(
|
||||||
"--create-assignment-completion/--no-create-assignment-completion",
|
"--create-assignment-completion/--no-create-assignment-completion",
|
||||||
default=False,
|
default=False,
|
||||||
help="will create assignment completion data for test-student1@example.com",
|
help="will create assignment completion data for test-student1@example.com by default. Other user can be specified with --assignment-completion-user",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--assignment-completion-user-id",
|
||||||
|
default=TEST_STUDENT1_USER_ID,
|
||||||
|
help="user to create assignment completion for. Defaults to test-student1@example.com. Hint: Is only evaluated if --create-assignment-completion is set.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--assignment-completion-course-session-id",
|
||||||
|
default=TEST_COURSE_SESSION_BERN_ID,
|
||||||
|
help="course session to create assignment completion in. Defaults to 'Test Bern 2022 a'. Hint: Is only evaluated if --create-assignment-completion is set.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--create-assignment-evaluation/--no-create-assignment-evaluation",
|
"--create-assignment-evaluation/--no-create-assignment-evaluation",
|
||||||
default=False,
|
default=False,
|
||||||
help="will create assignment evaluation data for test-student1@example.com",
|
help="will create assignment evaluation data for test-student1@example.com by default. Other user can be specified with --assignment-evaluation-user",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--assignment-evaluation-user-id",
|
||||||
|
default=TEST_TRAINER1_USER_ID,
|
||||||
|
help="user to create assignment evaluation for. Defaults to test-trainer1@example.com. Hint: Is only evaluated if --create-assignment-completion is set.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--assignment-evaluation-course-session-id",
|
||||||
|
default=TEST_COURSE_SESSION_BERN_ID,
|
||||||
|
help="course session to create assignment evaluation in. Defaults to 'Test Bern 2022 a'. Hint: Is only evaluated if --create-assignment-completion is set.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--assignment-evaluation-scores",
|
"--assignment-evaluation-scores",
|
||||||
|
|
@ -86,7 +106,17 @@ from vbv_lernwelt.shop.models import CheckoutInformation
|
||||||
type=(int, int, float),
|
type=(int, int, float),
|
||||||
default=(None, None, 0.0),
|
default=(None, None, 0.0),
|
||||||
metavar="USER_POINTS MAX_POINTS POINTS_DEDUCTED",
|
metavar="USER_POINTS MAX_POINTS POINTS_DEDUCTED",
|
||||||
help="Create edoniq result data for test-student1@example.com with user points and max points",
|
help="Create edoniq result data for test-student1@example.com by default with user points and max points. Use --edoniq-user-id to specify a different user.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--edoniq-user-id",
|
||||||
|
default=TEST_STUDENT1_USER_ID,
|
||||||
|
help="User to create edoniq test results for. Defaults to test-student1@example.com. Hint: Is only evaluated if --create-edoniq-test-results is set.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--edoniq-course-session-id",
|
||||||
|
default=TEST_COURSE_SESSION_BERN_ID,
|
||||||
|
help="course session to create edoniq test results in. Defaults to 'Test Bern 2022 a'. Hint: Is only evaluated if --create-edoniq-test-results is set.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--create-feedback-responses/--no-create-feedback-responses",
|
"--create-feedback-responses/--no-create-feedback-responses",
|
||||||
|
|
@ -130,10 +160,16 @@ from vbv_lernwelt.shop.models import CheckoutInformation
|
||||||
)
|
)
|
||||||
def command(
|
def command(
|
||||||
create_assignment_completion,
|
create_assignment_completion,
|
||||||
|
assignment_completion_user_id,
|
||||||
|
assignment_completion_course_session_id,
|
||||||
create_assignment_evaluation,
|
create_assignment_evaluation,
|
||||||
|
assignment_evaluation_user_id,
|
||||||
|
assignment_evaluation_course_session_id,
|
||||||
assignment_evaluation_scores,
|
assignment_evaluation_scores,
|
||||||
assignment_points_deducted,
|
assignment_points_deducted,
|
||||||
create_edoniq_test_results,
|
create_edoniq_test_results,
|
||||||
|
edoniq_user_id,
|
||||||
|
edoniq_course_session_id,
|
||||||
create_feedback_responses,
|
create_feedback_responses,
|
||||||
create_course_completion_performance_criteria,
|
create_course_completion_performance_criteria,
|
||||||
create_attendance_days,
|
create_attendance_days,
|
||||||
|
|
@ -186,15 +222,19 @@ def command(
|
||||||
assignment=Assignment.objects.get(
|
assignment=Assignment.objects.get(
|
||||||
slug="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"
|
slug="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"
|
||||||
),
|
),
|
||||||
course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID),
|
course_session=CourseSession.objects.get(
|
||||||
user=User.objects.get(id=TEST_STUDENT1_USER_ID),
|
id=assignment_completion_course_session_id
|
||||||
|
),
|
||||||
|
user=User.objects.get(id=assignment_completion_user_id),
|
||||||
)
|
)
|
||||||
create_test_assignment_submitted_data(
|
create_test_assignment_submitted_data(
|
||||||
assignment=Assignment.objects.get(
|
assignment=Assignment.objects.get(
|
||||||
slug="test-lehrgang-assignment-mein-kundenstamm"
|
slug="test-lehrgang-assignment-mein-kundenstamm"
|
||||||
),
|
),
|
||||||
course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID),
|
course_session=CourseSession.objects.get(
|
||||||
user=User.objects.get(id=TEST_STUDENT1_USER_ID),
|
id=assignment_completion_course_session_id
|
||||||
|
),
|
||||||
|
user=User.objects.get(id=assignment_completion_user_id),
|
||||||
)
|
)
|
||||||
if create_assignment_evaluation:
|
if create_assignment_evaluation:
|
||||||
if not assignment_evaluation_scores:
|
if not assignment_evaluation_scores:
|
||||||
|
|
@ -211,9 +251,11 @@ def command(
|
||||||
assignment=Assignment.objects.get(
|
assignment=Assignment.objects.get(
|
||||||
slug="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"
|
slug="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"
|
||||||
),
|
),
|
||||||
course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID),
|
course_session=CourseSession.objects.get(
|
||||||
assignment_user=User.objects.get(id=TEST_STUDENT1_USER_ID),
|
id=assignment_evaluation_course_session_id
|
||||||
evaluation_user=User.objects.get(id=TEST_TRAINER1_USER_ID),
|
),
|
||||||
|
assignment_user=User.objects.get(id=assignment_completion_user_id),
|
||||||
|
evaluation_user=User.objects.get(id=assignment_evaluation_user_id),
|
||||||
input_scores=assignment_evaluation_scores,
|
input_scores=assignment_evaluation_scores,
|
||||||
points_deducted=assignment_points_deducted,
|
points_deducted=assignment_points_deducted,
|
||||||
)
|
)
|
||||||
|
|
@ -227,8 +269,8 @@ def command(
|
||||||
assignment=Assignment.objects.get(
|
assignment=Assignment.objects.get(
|
||||||
slug="test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"
|
slug="test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"
|
||||||
),
|
),
|
||||||
course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID),
|
course_session=CourseSession.objects.get(id=edoniq_course_session_id),
|
||||||
assignment_user=User.objects.get(id=TEST_STUDENT1_USER_ID),
|
assignment_user=User.objects.get(id=edoniq_user_id),
|
||||||
user_points=user_points,
|
user_points=user_points,
|
||||||
max_points=max_points,
|
max_points=max_points,
|
||||||
evaluation_points_deducted=points_deducted,
|
evaluation_points_deducted=points_deducted,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ from graphql import GraphQLError
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
|
||||||
from vbv_lernwelt.competence.graphql.types import ActionCompetenceObjectType
|
from vbv_lernwelt.competence.graphql.types import ActionCompetenceObjectType
|
||||||
|
from vbv_lernwelt.core.models import User
|
||||||
from vbv_lernwelt.course.models import (
|
from vbv_lernwelt.course.models import (
|
||||||
CircleDocument,
|
CircleDocument,
|
||||||
Course,
|
Course,
|
||||||
|
|
@ -29,8 +30,9 @@ from vbv_lernwelt.course_session.models import (
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||||
from vbv_lernwelt.iam.permissions import has_course_access
|
from vbv_lernwelt.iam.permissions import has_course_access
|
||||||
|
from vbv_lernwelt.learnpath.consts import COURSE_PROFILE_ALL_ID
|
||||||
from vbv_lernwelt.learnpath.graphql.types import LearningPathObjectType
|
from vbv_lernwelt.learnpath.graphql.types import LearningPathObjectType
|
||||||
from vbv_lernwelt.learnpath.models import Circle
|
from vbv_lernwelt.learnpath.models import Circle, CourseProfile
|
||||||
|
|
||||||
logger = structlog.get_logger(__name__)
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
@ -106,6 +108,12 @@ class CourseObjectType(DjangoObjectType):
|
||||||
graphene.NonNull(ActionCompetenceObjectType), required=True
|
graphene.NonNull(ActionCompetenceObjectType), required=True
|
||||||
)
|
)
|
||||||
configuration = graphene.Field(CourseConfigurationObjectType, required=True)
|
configuration = graphene.Field(CourseConfigurationObjectType, required=True)
|
||||||
|
profiles = graphene.List(graphene.String)
|
||||||
|
course_session_users = graphene.List(
|
||||||
|
"vbv_lernwelt.course.graphql.types.CourseSessionUserType",
|
||||||
|
required=True,
|
||||||
|
id=graphene.String(),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Course
|
model = Course
|
||||||
|
|
@ -125,6 +133,22 @@ class CourseObjectType(DjangoObjectType):
|
||||||
def resolve_action_competences(root: Course, info):
|
def resolve_action_competences(root: Course, info):
|
||||||
return root.get_action_competences()
|
return root.get_action_competences()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_profiles(root: Course, info, **kwargs):
|
||||||
|
if root.configuration.is_vv:
|
||||||
|
return CourseProfile.objects.values_list("code", flat=True)
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_course_session_users(root: Course, info, id=None, **kwargs):
|
||||||
|
# todo: restrict users that can be queried
|
||||||
|
if id is not None:
|
||||||
|
user = User.objects.get(id=id)
|
||||||
|
else:
|
||||||
|
user = info.context.user
|
||||||
|
users = CourseSessionUser.objects.filter(user=user, course_session__course=root)
|
||||||
|
return users
|
||||||
|
|
||||||
|
|
||||||
class CourseSessionUserExpertCircleType(ObjectType):
|
class CourseSessionUserExpertCircleType(ObjectType):
|
||||||
id = graphene.ID(required=True)
|
id = graphene.ID(required=True)
|
||||||
|
|
@ -132,6 +156,21 @@ class CourseSessionUserExpertCircleType(ObjectType):
|
||||||
slug = graphene.String(required=True)
|
slug = graphene.String(required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionUserType(DjangoObjectType):
|
||||||
|
chosen_profile = graphene.String(required=True)
|
||||||
|
course_session = graphene.Field(
|
||||||
|
"vbv_lernwelt.course.graphql.types.CourseSessionObjectType", required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CourseSessionUser
|
||||||
|
fields = ["chosen_profile", "id"]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_chosen_profile(root: CourseSessionUser, info, **kwargs):
|
||||||
|
return getattr(root.chosen_profile, "code", "")
|
||||||
|
|
||||||
|
|
||||||
class CourseSessionUserObjectsType(ObjectType):
|
class CourseSessionUserObjectsType(ObjectType):
|
||||||
"""
|
"""
|
||||||
WORKAROUND:
|
WORKAROUND:
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ from vbv_lernwelt.learnpath.create_vv_new_learning_path import (
|
||||||
create_vv_new_learning_path,
|
create_vv_new_learning_path,
|
||||||
create_vv_pruefung_learning_path,
|
create_vv_pruefung_learning_path,
|
||||||
)
|
)
|
||||||
|
from vbv_lernwelt.learnpath.creators import assign_circles_to_profiles
|
||||||
from vbv_lernwelt.learnpath.models import (
|
from vbv_lernwelt.learnpath.models import (
|
||||||
Circle,
|
Circle,
|
||||||
LearningContent,
|
LearningContent,
|
||||||
|
|
@ -218,6 +219,7 @@ def create_versicherungsvermittlerin_course(
|
||||||
create_vv_gewinnen_casework(course_id=course_id)
|
create_vv_gewinnen_casework(course_id=course_id)
|
||||||
create_vv_reflection(course_id=course_id)
|
create_vv_reflection(course_id=course_id)
|
||||||
create_vv_new_learning_path(course_id=course_id)
|
create_vv_new_learning_path(course_id=course_id)
|
||||||
|
assign_circles_to_profiles()
|
||||||
|
|
||||||
cs = CourseSession.objects.create(course_id=course_id, title=names[language])
|
cs = CourseSession.objects.create(course_id=course_id, title=names[language])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 3.2.20 on 2024-07-11 09:00
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("learnpath", "0017_auto_20240711_1100"),
|
||||||
|
("course", "0008_auto_20240403_1132"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="coursesessionuser",
|
||||||
|
name="chosen_profile",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="learnpath.courseprofile",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 4.2.13 on 2024-07-22 19:45
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import vbv_lernwelt.course.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("course", "0009_coursesessionuser_chosen_profile"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Generated by Django 4.2.13 on 2024-08-07 11:17
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("course", "0009_coursesessionuser_required_attendance_and_more"),
|
||||||
|
("course", "0010_alter_coursecompletion_completion_status"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = []
|
||||||
|
|
@ -285,6 +285,10 @@ class CourseSessionUser(models.Model):
|
||||||
)
|
)
|
||||||
optional_attendance = models.BooleanField(default=False)
|
optional_attendance = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
chosen_profile = models.ForeignKey(
|
||||||
|
"learnpath.CourseProfile", on_delete=models.SET_NULL, blank=True, null=True
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
constraints = [
|
constraints = [
|
||||||
UniqueConstraint(
|
UniqueConstraint(
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@ from vbv_lernwelt.course.models import (
|
||||||
CourseCompletion,
|
CourseCompletion,
|
||||||
CourseConfiguration,
|
CourseConfiguration,
|
||||||
CourseSession,
|
CourseSession,
|
||||||
|
CourseSessionUser,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.iam.permissions import course_session_permissions
|
from vbv_lernwelt.iam.permissions import course_session_permissions
|
||||||
|
from vbv_lernwelt.learnpath.models import CourseProfile
|
||||||
|
|
||||||
|
|
||||||
class CourseConfigurationSerializer(serializers.ModelSerializer):
|
class CourseConfigurationSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -31,10 +33,23 @@ class CourseSerializer(serializers.ModelSerializer):
|
||||||
configuration = CourseConfigurationSerializer(
|
configuration = CourseConfigurationSerializer(
|
||||||
read_only=True,
|
read_only=True,
|
||||||
)
|
)
|
||||||
|
course_profiles = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_course_profiles(self, obj):
|
||||||
|
if obj.configuration.is_vv:
|
||||||
|
return CourseProfile.objects.all().values_list("code", flat=True)
|
||||||
|
return []
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Course
|
model = Course
|
||||||
fields = ["id", "title", "category_name", "slug", "configuration"]
|
fields = [
|
||||||
|
"id",
|
||||||
|
"title",
|
||||||
|
"category_name",
|
||||||
|
"slug",
|
||||||
|
"configuration",
|
||||||
|
"course_profiles",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class CourseCategorySerializer(serializers.ModelSerializer):
|
class CourseCategorySerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -103,6 +118,12 @@ class CourseSessionSerializer(serializers.ModelSerializer):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class CypressCourseSessionUserSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = CourseSessionUser
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class CircleDocumentSerializer(serializers.ModelSerializer):
|
class CircleDocumentSerializer(serializers.ModelSerializer):
|
||||||
learning_sequence = serializers.SerializerMethodField()
|
learning_sequence = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
from django.test import RequestFactory, TestCase
|
||||||
|
from graphene.test import Client
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.admin import User
|
||||||
|
from vbv_lernwelt.core.create_default_users import create_default_users
|
||||||
|
from vbv_lernwelt.core.schema import schema
|
||||||
|
from vbv_lernwelt.course.management.commands.create_default_courses import (
|
||||||
|
create_versicherungsvermittlerin_course,
|
||||||
|
)
|
||||||
|
from vbv_lernwelt.course.models import CourseSessionUser
|
||||||
|
from vbv_lernwelt.learnpath.creators import create_course_profiles
|
||||||
|
from vbv_lernwelt.learnpath.models import CourseProfile
|
||||||
|
|
||||||
|
|
||||||
|
class CourseGraphQLTestCase(TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
create_default_users()
|
||||||
|
create_course_profiles()
|
||||||
|
create_versicherungsvermittlerin_course()
|
||||||
|
|
||||||
|
def test_update_course_profile(self):
|
||||||
|
user = User.objects.get(username="student-vv@eiger-versicherungen.ch")
|
||||||
|
request = RequestFactory().get("/")
|
||||||
|
request.user = user
|
||||||
|
client = Client(schema=schema, context_value=request)
|
||||||
|
query = """
|
||||||
|
query CourseQuery($slug: String!) {
|
||||||
|
course(slug: $slug){
|
||||||
|
id
|
||||||
|
profiles
|
||||||
|
course_session_users {
|
||||||
|
id
|
||||||
|
__typename
|
||||||
|
chosen_profile
|
||||||
|
course_session {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
slug = "versicherungsvermittler-in"
|
||||||
|
variables = {"slug": slug}
|
||||||
|
result = client.execute(query, variables=variables)
|
||||||
|
|
||||||
|
self.assertIsNone(result.get("errors"))
|
||||||
|
data = result.get("data")
|
||||||
|
course = data.get("course")
|
||||||
|
profiles = course.get("profiles")
|
||||||
|
self.assertEqual(
|
||||||
|
set(profiles),
|
||||||
|
set(["all", "nichtleben", "leben", "krankenzusatzversicherung"]),
|
||||||
|
)
|
||||||
|
course_session_user = course.get("course_session_users")[0]
|
||||||
|
chosen_profile = course_session_user.get("chosen_profile")
|
||||||
|
|
||||||
|
self.assertEqual(chosen_profile, "")
|
||||||
|
|
||||||
|
mutation = """
|
||||||
|
mutation UpdateCourseSessionProfile($input: CourseSessionProfileMutationInput!) {
|
||||||
|
update_course_session_profile(input: $input) {
|
||||||
|
clientMutationId
|
||||||
|
result {
|
||||||
|
__typename
|
||||||
|
... on UpdateCourseProfileSuccess {
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
chosen_profile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
... on UpdateCourseProfileError {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
profile = "nichtleben"
|
||||||
|
input = {"course_profile": profile, "course_slug": slug}
|
||||||
|
|
||||||
|
mutation_result = client.execute(mutation, variables={"input": input})
|
||||||
|
|
||||||
|
self.assertIsNone(mutation_result.get("errors"))
|
||||||
|
|
||||||
|
second_query_result = client.execute(query, variables=variables)
|
||||||
|
|
||||||
|
self.assertIsNone(second_query_result.get("errors"))
|
||||||
|
data = second_query_result.get("data")
|
||||||
|
course = data.get("course")
|
||||||
|
profiles = course.get("profiles")
|
||||||
|
self.assertEqual(
|
||||||
|
set(profiles),
|
||||||
|
set(["all", "nichtleben", "leben", "krankenzusatzversicherung"]),
|
||||||
|
)
|
||||||
|
course_session_user = course.get("course_session_users")[0]
|
||||||
|
chosen_profile = course_session_user.get("chosen_profile")
|
||||||
|
self.assertEqual(chosen_profile, profile)
|
||||||
|
|
||||||
|
def test_mentor_profile_view(self):
|
||||||
|
user = User.objects.get(username="test-mentor1@example.com")
|
||||||
|
request = RequestFactory().get("/")
|
||||||
|
request.user = user
|
||||||
|
client = Client(schema=schema, context_value=request)
|
||||||
|
|
||||||
|
query = """
|
||||||
|
query courseQuery($slug: String!, $user: String) {
|
||||||
|
course(slug: $slug) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
slug
|
||||||
|
category_name
|
||||||
|
profiles
|
||||||
|
course_session_users(id: $user) {
|
||||||
|
id
|
||||||
|
__typename
|
||||||
|
chosen_profile
|
||||||
|
course_session {
|
||||||
|
id
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
student = User.objects.get(username="student-vv@eiger-versicherungen.ch")
|
||||||
|
|
||||||
|
slug = "versicherungsvermittler-in"
|
||||||
|
student_id = str(student.id)
|
||||||
|
variables = {"slug": slug, "user": student_id}
|
||||||
|
print(variables)
|
||||||
|
result = client.execute(query, variables=variables)
|
||||||
|
self.assertIsNone(result.get("errors"))
|
||||||
|
data = result.get("data")
|
||||||
|
course = data.get("course")
|
||||||
|
profiles = course.get("profiles")
|
||||||
|
self.assertEqual(
|
||||||
|
set(profiles),
|
||||||
|
set(["all", "nichtleben", "leben", "krankenzusatzversicherung"]),
|
||||||
|
)
|
||||||
|
course_session_user = course.get("course_session_users")[0]
|
||||||
|
chosen_profile = course_session_user.get("chosen_profile")
|
||||||
|
self.assertEqual(chosen_profile, "")
|
||||||
|
|
||||||
|
csu = CourseSessionUser.objects.get(
|
||||||
|
course_session__course__slug=slug, user=student
|
||||||
|
)
|
||||||
|
course_profile = CourseProfile.objects.get(code="nichtleben")
|
||||||
|
csu.chosen_profile = course_profile
|
||||||
|
csu.save()
|
||||||
|
|
||||||
|
second_result = client.execute(query, variables=variables)
|
||||||
|
self.assertIsNone(second_result.get("errors"))
|
||||||
|
data = second_result.get("data")
|
||||||
|
course = data.get("course")
|
||||||
|
profiles = course.get("profiles")
|
||||||
|
self.assertEqual(
|
||||||
|
set(profiles),
|
||||||
|
set(["all", "nichtleben", "leben", "krankenzusatzversicherung"]),
|
||||||
|
)
|
||||||
|
course_session_user = course.get("course_session_users")[0]
|
||||||
|
chosen_profile = course_session_user.get("chosen_profile")
|
||||||
|
self.assertEqual(chosen_profile, "nichtleben")
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import graphene
|
import graphene
|
||||||
import structlog
|
import structlog
|
||||||
|
from graphene import relay
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
|
||||||
|
from vbv_lernwelt.course.graphql.types import CourseSessionUserType
|
||||||
|
from vbv_lernwelt.course.models import CourseSessionUser
|
||||||
from vbv_lernwelt.course_session.graphql.types import (
|
from vbv_lernwelt.course_session.graphql.types import (
|
||||||
CourseSessionAttendanceCourseObjectType,
|
CourseSessionAttendanceCourseObjectType,
|
||||||
)
|
)
|
||||||
|
|
@ -11,10 +14,33 @@ from vbv_lernwelt.course_session.services.attendance import (
|
||||||
update_attendance_list,
|
update_attendance_list,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.iam.permissions import has_course_access
|
from vbv_lernwelt.iam.permissions import has_course_access
|
||||||
|
from vbv_lernwelt.learnpath.models import CourseProfile
|
||||||
|
|
||||||
logger = structlog.get_logger(__name__)
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateCourseProfileSuccess(graphene.ObjectType):
|
||||||
|
user = graphene.Field(CourseSessionUserType(), required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateCourseProfileError(graphene.ObjectType):
|
||||||
|
message = graphene.String()
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateCourseProfileResult(graphene.Union):
|
||||||
|
class Meta:
|
||||||
|
types = (
|
||||||
|
UpdateCourseProfileError,
|
||||||
|
UpdateCourseProfileSuccess,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resolve_type(cls, instance, info):
|
||||||
|
if type(instance).__name__ == "UpdateCourseProfileSuccess":
|
||||||
|
return UpdateCourseProfileSuccess
|
||||||
|
return UpdateCourseProfileError
|
||||||
|
|
||||||
|
|
||||||
class AttendanceUserInputType(graphene.InputObjectType):
|
class AttendanceUserInputType(graphene.InputObjectType):
|
||||||
user_id = graphene.UUID(required=True)
|
user_id = graphene.UUID(required=True)
|
||||||
status = graphene.Field(
|
status = graphene.Field(
|
||||||
|
|
@ -57,5 +83,40 @@ class AttendanceCourseUserMutation(graphene.Mutation):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionProfileMutation(relay.ClientIDMutation):
|
||||||
|
class Input:
|
||||||
|
course_profile = graphene.String(required=True)
|
||||||
|
course_slug = graphene.String(required=True)
|
||||||
|
|
||||||
|
result = UpdateCourseProfileResult()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, root, info, **input):
|
||||||
|
course_profile = input.get("course_profile")
|
||||||
|
course_slug = input.get("course_slug")
|
||||||
|
user = info.context.user
|
||||||
|
|
||||||
|
try:
|
||||||
|
if course_profile == "":
|
||||||
|
profile = None
|
||||||
|
else:
|
||||||
|
profile = CourseProfile.objects.get(code=course_profile)
|
||||||
|
|
||||||
|
# csu = user.coursesessionuser_set.first()
|
||||||
|
csu = CourseSessionUser.objects.get(
|
||||||
|
course_session__course__slug=course_slug, user=user
|
||||||
|
)
|
||||||
|
csu.chosen_profile = profile
|
||||||
|
csu.save()
|
||||||
|
return cls(result=UpdateCourseProfileSuccess(user=csu))
|
||||||
|
except CourseProfile.DoesNotExist:
|
||||||
|
return cls(result=UpdateCourseProfileError("Course Profile does not exist"))
|
||||||
|
except CourseSessionUser.DoesNotExist:
|
||||||
|
return cls(
|
||||||
|
result=UpdateCourseProfileError("Course Session User does not exist")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CourseSessionMutation:
|
class CourseSessionMutation:
|
||||||
update_course_session_attendance_course_users = AttendanceCourseUserMutation.Field()
|
update_course_session_attendance_course_users = AttendanceCourseUserMutation.Field()
|
||||||
|
update_course_session_profile = CourseSessionProfileMutation.Field()
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
# Register your models here.
|
from vbv_lernwelt.learnpath.models import CourseProfile
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(CourseProfile)
|
||||||
|
class CourseProfileAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
COURSE_PROFILE_LEBEN_ID = -1
|
||||||
|
COURSE_PROFILE_NICHTLEBEN_ID = -2
|
||||||
|
COURSE_PROFILE_KRANKENZUSATZ_ID = -3
|
||||||
|
COURSE_PROFILE_ALL_ID = -99
|
||||||
|
|
||||||
|
COURSE_PROFILE_LEBEN_CODE = "leben"
|
||||||
|
COURSE_PROFILE_NICHTLEBEN_CODE = "nichtleben"
|
||||||
|
COURSE_PROFILE_KRANKENZUSATZ_CODE = "krankenzusatzversicherung"
|
||||||
|
COURSE_PROFILE_ALL_CODE = "all"
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
from vbv_lernwelt.course.consts import (
|
||||||
|
COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID,
|
||||||
|
COURSE_VERSICHERUNGSVERMITTLERIN_ID,
|
||||||
|
COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID,
|
||||||
|
)
|
||||||
|
from vbv_lernwelt.learnpath.consts import (
|
||||||
|
COURSE_PROFILE_ALL_CODE,
|
||||||
|
COURSE_PROFILE_ALL_ID,
|
||||||
|
COURSE_PROFILE_KRANKENZUSATZ_CODE,
|
||||||
|
COURSE_PROFILE_KRANKENZUSATZ_ID,
|
||||||
|
COURSE_PROFILE_LEBEN_CODE,
|
||||||
|
COURSE_PROFILE_LEBEN_ID,
|
||||||
|
COURSE_PROFILE_NICHTLEBEN_CODE,
|
||||||
|
COURSE_PROFILE_NICHTLEBEN_ID,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def create_course_profiles():
|
||||||
|
from vbv_lernwelt.learnpath.models import CourseProfile
|
||||||
|
|
||||||
|
# Allbranche, Krankenzusatzversicherung, nicht Leben, Leben
|
||||||
|
CourseProfile.objects.get_or_create(
|
||||||
|
id=COURSE_PROFILE_ALL_ID, code=COURSE_PROFILE_ALL_CODE, order=1
|
||||||
|
)
|
||||||
|
CourseProfile.objects.get_or_create(
|
||||||
|
id=COURSE_PROFILE_KRANKENZUSATZ_ID,
|
||||||
|
code=COURSE_PROFILE_KRANKENZUSATZ_CODE,
|
||||||
|
order=2,
|
||||||
|
)
|
||||||
|
CourseProfile.objects.get_or_create(
|
||||||
|
id=COURSE_PROFILE_NICHTLEBEN_ID, code=COURSE_PROFILE_NICHTLEBEN_CODE, order=3
|
||||||
|
)
|
||||||
|
CourseProfile.objects.get_or_create(
|
||||||
|
id=COURSE_PROFILE_LEBEN_ID, code=COURSE_PROFILE_LEBEN_CODE, order=4
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def assign_circle_to_profile_curry(course_page):
|
||||||
|
from vbv_lernwelt.learnpath.models import Circle, CourseProfile
|
||||||
|
|
||||||
|
def assign_circle_to_profile(title, code):
|
||||||
|
try:
|
||||||
|
circle = Circle.objects.descendant_of(course_page).get(title=title)
|
||||||
|
course_profile = CourseProfile.objects.get(code=code)
|
||||||
|
circle.profiles.add(course_profile)
|
||||||
|
circle.save()
|
||||||
|
except Circle.DoesNotExist:
|
||||||
|
logger.warning("assign_circle_to_profile: circle not found", title=title)
|
||||||
|
|
||||||
|
return assign_circle_to_profile
|
||||||
|
|
||||||
|
|
||||||
|
def make_base_circle_curry(course_page):
|
||||||
|
from vbv_lernwelt.learnpath.models import Circle
|
||||||
|
|
||||||
|
def make_base_circle(title):
|
||||||
|
try:
|
||||||
|
circle = Circle.objects.descendant_of(course_page).get(title=title)
|
||||||
|
circle.is_base_circle = True
|
||||||
|
circle.save()
|
||||||
|
except Circle.DoesNotExist:
|
||||||
|
logger.warning("assign_circle_to_profile: circle not found", title=title)
|
||||||
|
|
||||||
|
return make_base_circle
|
||||||
|
|
||||||
|
|
||||||
|
def assign_de_circles_to_profiles():
|
||||||
|
from vbv_lernwelt.course.models import CoursePage
|
||||||
|
|
||||||
|
try:
|
||||||
|
course_page = CoursePage.objects.get(
|
||||||
|
course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID
|
||||||
|
)
|
||||||
|
except CoursePage.DoesNotExist:
|
||||||
|
logger.warning("Course does not exist yet")
|
||||||
|
return
|
||||||
|
|
||||||
|
assign_circle_to_profile = assign_circle_to_profile_curry(course_page)
|
||||||
|
make_base_circle = make_base_circle_curry(course_page)
|
||||||
|
|
||||||
|
assign_circle_to_profile("Fahrzeug", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Haushalt", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Rechtsstreitigkeiten", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Reisen", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Wohneigentum", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile(
|
||||||
|
"Selbstständigkeit", COURSE_PROFILE_NICHTLEBEN_CODE
|
||||||
|
) # typo, but that's how it is in prod data
|
||||||
|
assign_circle_to_profile("KMU", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
|
||||||
|
assign_circle_to_profile("Einkommenssicherung", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Pensionierung", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Erben/Vererben", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Sparen", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
assign_circle_to_profile(
|
||||||
|
"Selbstständigkeit", COURSE_PROFILE_LEBEN_CODE
|
||||||
|
) # typo, but that's how it is in prod data
|
||||||
|
assign_circle_to_profile("KMU", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
|
||||||
|
assign_circle_to_profile("Gesundheit", COURSE_PROFILE_KRANKENZUSATZ_CODE)
|
||||||
|
|
||||||
|
make_base_circle("Kickoff")
|
||||||
|
make_base_circle("Basis")
|
||||||
|
make_base_circle("Gewinnen")
|
||||||
|
make_base_circle("Prüfungsvorbereitung")
|
||||||
|
make_base_circle("Prüfung")
|
||||||
|
|
||||||
|
|
||||||
|
def assign_fr_circles_to_profiles():
|
||||||
|
from vbv_lernwelt.course.models import CoursePage
|
||||||
|
|
||||||
|
try:
|
||||||
|
course_page = CoursePage.objects.get(
|
||||||
|
course_id=COURSE_VERSICHERUNGSVERMITTLERIN_FR_ID
|
||||||
|
)
|
||||||
|
except CoursePage.DoesNotExist:
|
||||||
|
logger.warning("Course does not exist yet")
|
||||||
|
return
|
||||||
|
|
||||||
|
assign_circle_to_profile = assign_circle_to_profile_curry(course_page)
|
||||||
|
make_base_circle = make_base_circle_curry(course_page)
|
||||||
|
|
||||||
|
assign_circle_to_profile("Véhicule", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Ménage", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Litiges juridiques", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Voyages", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Propriété du logement", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Activité indépendante", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("PME", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
|
||||||
|
assign_circle_to_profile("Garantie des revenus", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Retraite", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Hériter\xa0/\xa0léguer", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Épargne", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Activité indépendante", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
assign_circle_to_profile("PME", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
|
||||||
|
assign_circle_to_profile("Santé", COURSE_PROFILE_KRANKENZUSATZ_CODE)
|
||||||
|
|
||||||
|
make_base_circle("Lancement")
|
||||||
|
make_base_circle("Base")
|
||||||
|
make_base_circle("Acquisition")
|
||||||
|
make_base_circle("Préparation à l’examen")
|
||||||
|
make_base_circle("L’examen")
|
||||||
|
|
||||||
|
|
||||||
|
def assign_it_circles_to_profiles():
|
||||||
|
from vbv_lernwelt.course.models import CoursePage
|
||||||
|
|
||||||
|
try:
|
||||||
|
course_page = CoursePage.objects.get(
|
||||||
|
course_id=COURSE_VERSICHERUNGSVERMITTLERIN_IT_ID
|
||||||
|
)
|
||||||
|
except CoursePage.DoesNotExist:
|
||||||
|
logger.warning("Course does not exist yet")
|
||||||
|
return
|
||||||
|
|
||||||
|
assign_circle_to_profile = assign_circle_to_profile_curry(course_page)
|
||||||
|
make_base_circle = make_base_circle_curry(course_page)
|
||||||
|
|
||||||
|
assign_circle_to_profile("Veicolo", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Economia domestica", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Controversie giuridiche", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Viaggi", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Casa di proprietà", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Attività indipendente", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
assign_circle_to_profile("PMI", COURSE_PROFILE_NICHTLEBEN_CODE)
|
||||||
|
|
||||||
|
assign_circle_to_profile("Protezione del reddito", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Pensionamento", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Ereditare/lasciare in eredità", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Risparmio", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
assign_circle_to_profile("Attività indipendente", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
assign_circle_to_profile("PMI", COURSE_PROFILE_LEBEN_CODE)
|
||||||
|
|
||||||
|
assign_circle_to_profile("Salute", COURSE_PROFILE_KRANKENZUSATZ_CODE)
|
||||||
|
|
||||||
|
make_base_circle("Kickoff")
|
||||||
|
make_base_circle("Base")
|
||||||
|
make_base_circle("Acquisizione")
|
||||||
|
make_base_circle("Preparazione all'esame")
|
||||||
|
make_base_circle("Esame")
|
||||||
|
|
||||||
|
|
||||||
|
def assign_circles_to_profiles():
|
||||||
|
assign_de_circles_to_profiles()
|
||||||
|
assign_fr_circles_to_profiles()
|
||||||
|
assign_it_circles_to_profiles()
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import random
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
import structlog
|
import structlog
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
|
|
@ -6,6 +8,7 @@ from vbv_lernwelt.core.utils import find_first_index
|
||||||
from vbv_lernwelt.course.graphql.interfaces import CoursePageInterface
|
from vbv_lernwelt.course.graphql.interfaces import CoursePageInterface
|
||||||
from vbv_lernwelt.learnpath.models import (
|
from vbv_lernwelt.learnpath.models import (
|
||||||
Circle,
|
Circle,
|
||||||
|
CourseProfile,
|
||||||
LearningContentAssignment,
|
LearningContentAssignment,
|
||||||
LearningContentAttendanceCourse,
|
LearningContentAttendanceCourse,
|
||||||
LearningContentDocumentList,
|
LearningContentDocumentList,
|
||||||
|
|
@ -299,14 +302,12 @@ class CircleObjectType(DjangoObjectType):
|
||||||
learning_sequences = graphene.List(
|
learning_sequences = graphene.List(
|
||||||
graphene.NonNull(LearningSequenceObjectType), required=True
|
graphene.NonNull(LearningSequenceObjectType), required=True
|
||||||
)
|
)
|
||||||
|
profiles = graphene.List(graphene.String, required=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Circle
|
model = Circle
|
||||||
interfaces = (CoursePageInterface,)
|
interfaces = (CoursePageInterface,)
|
||||||
fields = [
|
fields = ["description", "goals", "is_base_circle"]
|
||||||
"description",
|
|
||||||
"goals",
|
|
||||||
]
|
|
||||||
|
|
||||||
def resolve_learning_sequences(self: Circle, info, **kwargs):
|
def resolve_learning_sequences(self: Circle, info, **kwargs):
|
||||||
circle_descendants = None
|
circle_descendants = None
|
||||||
|
|
@ -335,6 +336,10 @@ class CircleObjectType(DjangoObjectType):
|
||||||
if descendant.specific_class == LearningSequence
|
if descendant.specific_class == LearningSequence
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_profiles(root: Circle, info, **kwargs):
|
||||||
|
return root.profiles.all()
|
||||||
|
|
||||||
|
|
||||||
class TopicObjectType(DjangoObjectType):
|
class TopicObjectType(DjangoObjectType):
|
||||||
circles = graphene.List(graphene.NonNull(CircleObjectType), required=True)
|
circles = graphene.List(graphene.NonNull(CircleObjectType), required=True)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Generated by Django 3.2.20 on 2024-07-11 09:00
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("learnpath", "0016_remove_learningunit_feedback_user"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="CourseProfile",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("code", models.CharField(max_length=255)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="CourseProfileToCircle",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="circle",
|
||||||
|
name="profiles",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="circles", to="learnpath.CourseProfile"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Generated by Django 4.2.13 on 2024-07-30 07:03
|
||||||
|
|
||||||
|
import modelcluster.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("learnpath", "0017_auto_20240711_1100"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="courseprofile",
|
||||||
|
options={"ordering": ["order"]},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="circle",
|
||||||
|
name="is_base_circle",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="courseprofile",
|
||||||
|
name="order",
|
||||||
|
field=models.IntegerField(default=999),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="circle",
|
||||||
|
name="profiles",
|
||||||
|
field=modelcluster.fields.ParentalManyToManyField(
|
||||||
|
related_name="circles", to="learnpath.courseprofile"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 4.2.13 on 2024-07-30 07:04
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
from vbv_lernwelt.learnpath.creators import create_course_profiles
|
||||||
|
|
||||||
|
|
||||||
|
def migrate(apps, schema_editor):
|
||||||
|
create_course_profiles()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
(
|
||||||
|
"learnpath",
|
||||||
|
"0018_alter_courseprofile_options_circle_is_base_circle_and_more",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [migrations.RunPython(migrate)]
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 4.2.13 on 2024-07-30 07:05
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
from vbv_lernwelt.learnpath.creators import assign_circles_to_profiles
|
||||||
|
|
||||||
|
|
||||||
|
def migrate(apps, schema_editor):
|
||||||
|
assign_circles_to_profiles()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("learnpath", "0019_auto_20240730_0904"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [migrations.RunPython(migrate)]
|
||||||
|
|
@ -3,6 +3,7 @@ from typing import Tuple
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
|
from modelcluster.models import ParentalManyToManyField
|
||||||
from wagtail.admin.panels import FieldPanel, PageChooserPanel
|
from wagtail.admin.panels import FieldPanel, PageChooserPanel
|
||||||
from wagtail.fields import RichTextField, StreamField
|
from wagtail.fields import RichTextField, StreamField
|
||||||
from wagtail.models import Page
|
from wagtail.models import Page
|
||||||
|
|
@ -66,6 +67,25 @@ class Topic(CourseBasePage):
|
||||||
return f"{self.title}"
|
return f"{self.title}"
|
||||||
|
|
||||||
|
|
||||||
|
class CourseProfile(models.Model):
|
||||||
|
code = models.CharField(max_length=255)
|
||||||
|
order = models.IntegerField(default=999)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.code
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = [
|
||||||
|
"order",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CourseProfileToCircle(models.Model):
|
||||||
|
# this connects the course profile to a circle, because a circle can be in multiple profiles
|
||||||
|
# todo: to we even need a through model?
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Circle(CourseBasePage):
|
class Circle(CourseBasePage):
|
||||||
parent_page_types = ["learnpath.LearningPath"]
|
parent_page_types = ["learnpath.LearningPath"]
|
||||||
subpage_types = [
|
subpage_types = [
|
||||||
|
|
@ -95,9 +115,25 @@ class Circle(CourseBasePage):
|
||||||
|
|
||||||
goals = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES_WITH_HEADER)
|
goals = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES_WITH_HEADER)
|
||||||
|
|
||||||
|
profiles = ParentalManyToManyField(CourseProfile, related_name="circles")
|
||||||
|
|
||||||
|
# base circles do never belong to a course profile and should also get displayed no matter what profile is chosen
|
||||||
|
is_base_circle = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
# profile = models.ForeignKey(
|
||||||
|
# ApprovalProfile,
|
||||||
|
# null=True,
|
||||||
|
# blank=True,
|
||||||
|
# on_delete=models.SET_NULL,
|
||||||
|
# related_name="circles",
|
||||||
|
# help_text="Zulassungsprofil",
|
||||||
|
# )
|
||||||
|
|
||||||
content_panels = Page.content_panels + [
|
content_panels = Page.content_panels + [
|
||||||
FieldPanel("description"),
|
FieldPanel("description"),
|
||||||
FieldPanel("goals"),
|
FieldPanel("goals"),
|
||||||
|
FieldPanel("is_base_circle"),
|
||||||
|
FieldPanel("profiles"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_frontend_url(self):
|
def get_frontend_url(self):
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from rest_framework import serializers
|
||||||
from rest_framework.fields import SerializerMethodField
|
from rest_framework.fields import SerializerMethodField
|
||||||
|
|
||||||
from vbv_lernwelt.competence.serializers import (
|
from vbv_lernwelt.competence.serializers import (
|
||||||
|
|
@ -6,6 +7,7 @@ from vbv_lernwelt.competence.serializers import (
|
||||||
from vbv_lernwelt.core.utils import get_django_content_type
|
from vbv_lernwelt.core.utils import get_django_content_type
|
||||||
from vbv_lernwelt.course.serializer_helpers import get_course_serializer_class
|
from vbv_lernwelt.course.serializer_helpers import get_course_serializer_class
|
||||||
from vbv_lernwelt.learnpath.models import (
|
from vbv_lernwelt.learnpath.models import (
|
||||||
|
CourseProfile,
|
||||||
LearningContentAssignment,
|
LearningContentAssignment,
|
||||||
LearningContentEdoniqTest,
|
LearningContentEdoniqTest,
|
||||||
LearningUnit,
|
LearningUnit,
|
||||||
|
|
@ -98,3 +100,9 @@ class LearningContentAssignmentSerializer(
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class CourseProfileSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = CourseProfile
|
||||||
|
fields = ["id", "code"]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Generated by Django 4.2.13 on 2024-07-22 19:45
|
||||||
|
|
||||||
|
import wagtail.images.models
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("media_files", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="contentimagerendition",
|
||||||
|
name="file",
|
||||||
|
field=wagtail.images.models.WagtailImageField(
|
||||||
|
height_field="height",
|
||||||
|
storage=wagtail.images.models.get_rendition_storage,
|
||||||
|
upload_to=wagtail.images.models.get_rendition_upload_to,
|
||||||
|
width_field="width",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="userimagerendition",
|
||||||
|
name="file",
|
||||||
|
field=wagtail.images.models.WagtailImageField(
|
||||||
|
height_field="height",
|
||||||
|
storage=wagtail.images.models.get_rendition_storage,
|
||||||
|
upload_to=wagtail.images.models.get_rendition_upload_to,
|
||||||
|
width_field="width",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 4.2.13 on 2024-07-30 07:03
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
(
|
||||||
|
"learnpath",
|
||||||
|
"0018_alter_courseprofile_options_circle_is_base_circle_and_more",
|
||||||
|
),
|
||||||
|
("shop", "0016_alter_checkoutinformation_refno2"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="checkoutinformation",
|
||||||
|
name="chosen_profile",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="learnpath.courseprofile",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -106,7 +106,9 @@ class CheckoutInformation(models.Model):
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
chosen_profile = models.ForeignKey(
|
||||||
|
"learnpath.CourseProfile", on_delete=models.SET_NULL, null=True, blank=True
|
||||||
|
)
|
||||||
# webhook metadata
|
# webhook metadata
|
||||||
webhook_history = models.JSONField(default=list)
|
webhook_history = models.JSONField(default=list)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from vbv_lernwelt.core.admin import User
|
from vbv_lernwelt.core.admin import User
|
||||||
from vbv_lernwelt.core.model_utils import add_countries
|
from vbv_lernwelt.core.model_utils import add_countries
|
||||||
|
from vbv_lernwelt.learnpath.consts import COURSE_PROFILE_ALL_CODE, COURSE_PROFILE_ALL_ID
|
||||||
|
from vbv_lernwelt.learnpath.models import CourseProfile
|
||||||
from vbv_lernwelt.shop.const import VV_DE_PRODUCT_SKU
|
from vbv_lernwelt.shop.const import VV_DE_PRODUCT_SKU
|
||||||
from vbv_lernwelt.shop.models import CheckoutInformation, CheckoutState, Product
|
from vbv_lernwelt.shop.models import CheckoutInformation, CheckoutState, Product
|
||||||
from vbv_lernwelt.shop.services import InitTransactionException
|
from vbv_lernwelt.shop.services import InitTransactionException
|
||||||
|
|
@ -50,6 +52,10 @@ class CheckoutAPITestCase(APITestCase):
|
||||||
is_active=True,
|
is_active=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CourseProfile.objects.get_or_create(
|
||||||
|
id=COURSE_PROFILE_ALL_ID, code=COURSE_PROFILE_ALL_CODE
|
||||||
|
)
|
||||||
|
|
||||||
self.client.login(username=USER_USERNAME, password=USER_PASSWORD)
|
self.client.login(username=USER_USERNAME, password=USER_PASSWORD)
|
||||||
add_countries(small_set=True)
|
add_countries(small_set=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ from rest_framework.permissions import IsAuthenticated
|
||||||
from sentry_sdk import capture_exception
|
from sentry_sdk import capture_exception
|
||||||
|
|
||||||
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
||||||
|
from vbv_lernwelt.learnpath.consts import COURSE_PROFILE_ALL_ID
|
||||||
|
from vbv_lernwelt.learnpath.models import CourseProfile
|
||||||
from vbv_lernwelt.notify.email.email_services import EmailTemplate, send_email
|
from vbv_lernwelt.notify.email.email_services import EmailTemplate, send_email
|
||||||
from vbv_lernwelt.shop.const import (
|
from vbv_lernwelt.shop.const import (
|
||||||
VV_DE_PRODUCT_SKU,
|
VV_DE_PRODUCT_SKU,
|
||||||
|
|
@ -92,6 +94,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"]
|
||||||
|
chosen_profile_id = request.data.get("chosen_profile", COURSE_PROFILE_ALL_ID)
|
||||||
|
|
||||||
log.info("Checkout requested: sku", user_id=request.user.id, sku=sku)
|
log.info("Checkout requested: sku", user_id=request.user.id, sku=sku)
|
||||||
|
|
||||||
|
|
@ -106,6 +109,11 @@ def checkout_vv(request):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
chosen_profile = CourseProfile.objects.get(id=chosen_profile_id)
|
||||||
|
except CourseProfile.DoesNotExist:
|
||||||
|
chosen_profile = CourseProfile.objects.get(id=COURSE_PROFILE_ALL_ID)
|
||||||
|
|
||||||
checkouts = CheckoutInformation.objects.filter(
|
checkouts = CheckoutInformation.objects.filter(
|
||||||
user=request.user,
|
user=request.user,
|
||||||
product_sku=sku,
|
product_sku=sku,
|
||||||
|
|
@ -151,6 +159,7 @@ def checkout_vv(request):
|
||||||
"device_fingerprint_session_key", ""
|
"device_fingerprint_session_key", ""
|
||||||
),
|
),
|
||||||
# address
|
# address
|
||||||
|
chosen_profile=chosen_profile,
|
||||||
**request.data["address"],
|
**request.data["address"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -257,9 +266,11 @@ def create_vv_course_session_user(checkout_info: CheckoutInformation):
|
||||||
_, created = CourseSessionUser.objects.get_or_create(
|
_, created = CourseSessionUser.objects.get_or_create(
|
||||||
user=checkout_info.user,
|
user=checkout_info.user,
|
||||||
role=CourseSessionUser.Role.MEMBER,
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
chosen_profile=checkout_info.chosen_profile,
|
||||||
course_session=CourseSession.objects.get(
|
course_session=CourseSession.objects.get(
|
||||||
id=PRODUCT_SKU_TO_COURSE_SESSION_ID[checkout_info.product_sku]
|
id=PRODUCT_SKU_TO_COURSE_SESSION_ID[checkout_info.product_sku]
|
||||||
),
|
),
|
||||||
|
# chosen_profile=bla,
|
||||||
)
|
)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue