diff --git a/README.md b/README.md
index 71693b77..b43ca54b 100644
--- a/README.md
+++ b/README.md
@@ -94,6 +94,21 @@ Preferences -> Tools -> Actions on Save
* Run eslint --fix
* Run prettier
+## Translations
+
+We use (vue-i18n)[https://kazupon.github.io/vue-i18n/] for translations
+and (vue-i18n-extract)[https://github.com/Spittal/vue-i18n-extract] for helper
+scripts.
+
+```
+# will create a report on command line with missing translations
+npm run vue-i18n-extract
+
+# add missing translations to files, see docs for more options
+cd client
+npx vue-i18n-extract --add
+```
+
## Deployment to CapRover
### CapRover Dev (vbv-lernwelt.control.iterativ.ch)
@@ -131,3 +146,24 @@ Preferences -> Tools -> Actions on Save

#### Install the tailwind css Plugin from Jetbrains
+
+## currents.dev
+
+This project uses [currents.dev](https://currents.dev) to run the Cypress tests concurrently.
+The following steps were taken to set it up:
+
+- Create a new project on currents.dev
+- Add the generated `projectId` to the cypress configuration file
+- Install `@currents/cli` as a dev dependency
+- Add the following script to the `package.json` file:
+ ```json
+ "cypress:ci": "currents run --parallel --record --key $CURRENTS_KEY",
+ ```
+- Create a new Bitbucket pipelines job that exports the `CURRENTS_KEY` and runs the `cypress:ci` script
+- Refactor the pipeline steps so that the dependencies are installed first and then the other steps are run in
+ parallel.
+ You can then run multiple instances of the previously created job in parallel.
+ See `.bitbucket-pipelines.yml` for an example.
+
+> 💡 The number of cypress worker jobs depends on the number of cypress tests.
+> Too many workers for too few tests will result in a lot of idle workers.
diff --git a/client/.prettierignore b/client/.prettierignore
index de4d1f00..80cb10cb 100644
--- a/client/.prettierignore
+++ b/client/.prettierignore
@@ -1,2 +1,4 @@
dist
node_modules
+**/__tests__/*.json
+src/colors.json
diff --git a/client/.prettierrc b/client/.prettierrc
index 8258aa33..7542914d 100644
--- a/client/.prettierrc
+++ b/client/.prettierrc
@@ -1,8 +1,9 @@
{
+ "htmlWhitespaceSensitivity": "ignore",
+ "jsonRecursiveSort": true,
+ "organizeImportsSkipDestructiveCodeActions": true,
+ "printWidth": 88,
"semi": true,
"singleQuote": false,
- "tabWidth": 2,
- "printWidth": 88,
- "organizeImportsSkipDestructiveCodeActions": true,
- "htmlWhitespaceSensitivity": "ignore"
+ "tabWidth": 2
}
diff --git a/client/package-lock.json b/client/package-lock.json
index e2df688f..08d5fb5c 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -21,6 +21,7 @@
"pinia": "^2.0.21",
"vue": "^3.2.38",
"vue-i18n": "^9.2.2",
+ "vue-i18n-extract": "^2.0.7",
"vue-router": "^4.1.5"
},
"devDependencies": {
@@ -51,6 +52,7 @@
"postcss-import": "^14.1.0",
"prettier": "^2.7.1",
"prettier-plugin-organize-imports": "^3.1.1",
+ "prettier-plugin-sort-json": "^1.0.0",
"prettier-plugin-tailwindcss": "^0.2.1",
"replace-in-file": "^6.3.5",
"sass": "^1.54.6",
@@ -3345,6 +3347,12 @@
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true
},
+ "node_modules/@types/prettier": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
+ "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==",
+ "dev": true
+ },
"node_modules/@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
@@ -4225,8 +4233,7 @@
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/aria-query": {
"version": "5.1.3",
@@ -4409,8 +4416,7 @@
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/base64-js": {
"version": "1.5.1",
@@ -4467,7 +4473,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -4569,6 +4574,14 @@
"node": ">=10.16.0"
}
},
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -4942,8 +4955,7 @@
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/constant-case": {
"version": "3.0.4",
@@ -5725,6 +5737,26 @@
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==",
"dev": true
},
+ "node_modules/dot-object": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.4.tgz",
+ "integrity": "sha512-7FXnyyCLFawNYJ+NhkqyP9Wd2yzuo+7n9pGiYpkmXCTYa8Ci2U0eUNDVg5OuO5Pm6aFXI2SWN8/N/w7SJWu1WA==",
+ "dependencies": {
+ "commander": "^4.0.0",
+ "glob": "^7.1.5"
+ },
+ "bin": {
+ "dot-object": "bin/dot-object"
+ }
+ },
+ "node_modules/dot-object/node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/dotenv": {
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
@@ -6953,8 +6985,7 @@
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "dev": true
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/fsevents": {
"version": "2.3.2",
@@ -7046,7 +7077,6 @@
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -7084,7 +7114,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -7094,7 +7123,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -7552,7 +7580,6 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "dev": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@@ -7561,8 +7588,7 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/inquirer": {
"version": "8.2.5",
@@ -7946,6 +7972,14 @@
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==",
"dev": true
},
+ "node_modules/is-valid-glob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz",
+ "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-weakmap": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz",
@@ -8062,7 +8096,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
"dependencies": {
"argparse": "^2.0.1"
},
@@ -8675,7 +8708,6 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
- "dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
@@ -8965,7 +8997,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
"dependencies": {
"wrappy": "1"
}
@@ -9205,7 +9236,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -9522,6 +9552,21 @@
}
}
},
+ "node_modules/prettier-plugin-sort-json": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-sort-json/-/prettier-plugin-sort-json-1.0.0.tgz",
+ "integrity": "sha512-XgcaF/Sojax1vD6j53wNIByx0rp7ecang+A8W0eM+Ks3yBFu/qXjJNvUtC1lEWeYbNfmRs/d8FyYJCYozAVENw==",
+ "dev": true,
+ "dependencies": {
+ "@types/prettier": "^2.7.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "prettier": "^2.3.2"
+ }
+ },
"node_modules/prettier-plugin-tailwindcss": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.2.1.tgz",
@@ -11125,6 +11170,39 @@
"vue": "^3.0.0"
}
},
+ "node_modules/vue-i18n-extract": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/vue-i18n-extract/-/vue-i18n-extract-2.0.7.tgz",
+ "integrity": "sha512-i1NW5R58S720iQ1BEk+6ILo3hT6UA8mtYNNolSH4rt9345qvXdvA6GHy2+jHozdDAKHwlu9VvS/+vIMKs1UYQw==",
+ "dependencies": {
+ "cac": "^6.7.12",
+ "dot-object": "^2.1.4",
+ "glob": "^8.0.1",
+ "is-valid-glob": "^1.0.0",
+ "js-yaml": "^4.1.0"
+ },
+ "bin": {
+ "vue-i18n-extract": "bin/vue-i18n-extract.js"
+ }
+ },
+ "node_modules/vue-i18n-extract/node_modules/glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/vue-router": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz",
@@ -11460,8 +11538,7 @@
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.11.0",
@@ -14317,6 +14394,12 @@
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true
},
+ "@types/prettier": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
+ "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==",
+ "dev": true
+ },
"@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
@@ -15009,8 +15092,7 @@
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"aria-query": {
"version": "5.1.3",
@@ -15149,8 +15231,7 @@
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"base64-js": {
"version": "1.5.1",
@@ -15190,7 +15271,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
"requires": {
"balanced-match": "^1.0.0"
}
@@ -15256,6 +15336,11 @@
"streamsearch": "^1.1.0"
}
},
+ "cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="
+ },
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -15544,8 +15629,7 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"constant-case": {
"version": "3.0.4",
@@ -16143,6 +16227,22 @@
}
}
},
+ "dot-object": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.4.tgz",
+ "integrity": "sha512-7FXnyyCLFawNYJ+NhkqyP9Wd2yzuo+7n9pGiYpkmXCTYa8Ci2U0eUNDVg5OuO5Pm6aFXI2SWN8/N/w7SJWu1WA==",
+ "requires": {
+ "commander": "^4.0.0",
+ "glob": "^7.1.5"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="
+ }
+ }
+ },
"dotenv": {
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
@@ -16977,8 +17077,7 @@
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "dev": true
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"fsevents": {
"version": "2.3.2",
@@ -17042,7 +17141,6 @@
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -17056,7 +17154,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -17066,7 +17163,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -17416,7 +17512,6 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@@ -17425,8 +17520,7 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"inquirer": {
"version": "8.2.5",
@@ -17703,6 +17797,11 @@
}
}
},
+ "is-valid-glob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz",
+ "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA=="
+ },
"is-weakmap": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz",
@@ -17801,7 +17900,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
"requires": {
"argparse": "^2.0.1"
}
@@ -18274,7 +18372,6 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
- "dev": true,
"requires": {
"brace-expansion": "^2.0.1"
}
@@ -18490,7 +18587,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
"requires": {
"wrappy": "1"
}
@@ -18678,8 +18774,7 @@
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "dev": true
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
},
"path-key": {
"version": "3.1.1",
@@ -18847,6 +18942,15 @@
"dev": true,
"requires": {}
},
+ "prettier-plugin-sort-json": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-sort-json/-/prettier-plugin-sort-json-1.0.0.tgz",
+ "integrity": "sha512-XgcaF/Sojax1vD6j53wNIByx0rp7ecang+A8W0eM+Ks3yBFu/qXjJNvUtC1lEWeYbNfmRs/d8FyYJCYozAVENw==",
+ "dev": true,
+ "requires": {
+ "@types/prettier": "^2.7.2"
+ }
+ },
"prettier-plugin-tailwindcss": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.2.1.tgz",
@@ -19980,6 +20084,32 @@
"@vue/devtools-api": "^6.2.1"
}
},
+ "vue-i18n-extract": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/vue-i18n-extract/-/vue-i18n-extract-2.0.7.tgz",
+ "integrity": "sha512-i1NW5R58S720iQ1BEk+6ILo3hT6UA8mtYNNolSH4rt9345qvXdvA6GHy2+jHozdDAKHwlu9VvS/+vIMKs1UYQw==",
+ "requires": {
+ "cac": "^6.7.12",
+ "dot-object": "^2.1.4",
+ "glob": "^8.0.1",
+ "is-valid-glob": "^1.0.0",
+ "js-yaml": "^4.1.0"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ }
+ }
+ }
+ },
"vue-router": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz",
@@ -20233,8 +20363,7 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"ws": {
"version": "8.11.0",
diff --git a/client/package.json b/client/package.json
index 4d8a7a34..92c91e05 100644
--- a/client/package.json
+++ b/client/package.json
@@ -10,6 +10,7 @@
"coverage": "vitest run --coverage",
"typecheck": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
+ "vue-i18n-extract": "vue-i18n-extract report",
"prettier": "prettier . --write",
"prettier:check": "prettier . --check",
"tailwind": "tailwindcss -i tailwind.css -o ../server/vbv_lernwelt/static/css/tailwind.css --watch"
@@ -28,6 +29,7 @@
"pinia": "^2.0.21",
"vue": "^3.2.38",
"vue-i18n": "^9.2.2",
+ "vue-i18n-extract": "^2.0.7",
"vue-router": "^4.1.5"
},
"devDependencies": {
@@ -58,6 +60,7 @@
"postcss-import": "^14.1.0",
"prettier": "^2.7.1",
"prettier-plugin-organize-imports": "^3.1.1",
+ "prettier-plugin-sort-json": "^1.0.0",
"prettier-plugin-tailwindcss": "^0.2.1",
"replace-in-file": "^6.3.5",
"sass": "^1.54.6",
diff --git a/client/src/components/AppFooter.vue b/client/src/components/AppFooter.vue
index 19f67a71..4174bd72 100644
--- a/client/src/components/AppFooter.vue
+++ b/client/src/components/AppFooter.vue
@@ -1,15 +1,16 @@
@@ -21,19 +22,27 @@ async function changeLocale(event: Event) {
{{ $t("footer.imprint") }}
VBV_VERSION_BUILD_NUMBER_VBV
- Deutsch
-
+
+
+
+
{{ $t("footer.contact") }}
diff --git a/client/src/components/learningPath/page/LearningPathAppointmentsMock.vue b/client/src/components/learningPath/page/LearningPathAppointmentsMock.vue
new file mode 100644
index 00000000..f151385c
--- /dev/null
+++ b/client/src/components/learningPath/page/LearningPathAppointmentsMock.vue
@@ -0,0 +1,12 @@
+
+ Nächste Termine
+
+ -
+
24. November 2022, 11 Uhr - Austausch mit Trainer
+
+ -
+
4. Dezember 2022, 10.30 Uhr - Vernetzen - Live Session
+
+
+ Alle Termine anzeigen
+
diff --git a/client/src/components/learningPath/LearningPathCircle.vue b/client/src/components/learningPath/page/LearningPathCircle.vue
similarity index 88%
rename from client/src/components/learningPath/LearningPathCircle.vue
rename to client/src/components/learningPath/page/LearningPathCircle.vue
index 55239b18..410976ae 100644
--- a/client/src/components/learningPath/LearningPathCircle.vue
+++ b/client/src/components/learningPath/page/LearningPathCircle.vue
@@ -51,9 +51,17 @@ const pieData = computed(() => {
.reverse() as CircleSector[];
});
-const width = 450;
-const height = 450;
-const radius = Math.min(width, height) / 2.4;
+const width = 100;
+const height = 100;
+const radius = 50;
+
+const svgId = computed(() => {
+ // Generate a random id for the svg element like 'circle-visualization-ziurxxmp'
+ return `circle-visualization-${Math.random()
+ .toString(36)
+ .replace(/[^a-z]+/g, "")
+ .substring(2, 10)}`;
+});
function getColor(sector: CircleSector) {
let color = colors.gray[300];
@@ -67,7 +75,7 @@ function getColor(sector: CircleSector) {
}
function render() {
- const svg = d3.select(".circle-visualization");
+ const svg = d3.select("#" + svgId.value);
// Clean svg before adding new stuff.
svg.selectAll("*").remove();
@@ -124,10 +132,10 @@ function render() {
-
+
{{ pieData }}
{{ render() }}
-
-
+
{{ $t("general.nextStep") }}
diff --git a/client/src/pages/StyleGuidePage.vue b/client/src/pages/StyleGuidePage.vue
index 858a55fc..d176e69a 100644
--- a/client/src/pages/StyleGuidePage.vue
+++ b/client/src/pages/StyleGuidePage.vue
@@ -1,7 +1,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {{ $t("learningPathPage.welcomeBack", { name: userStore.first_name }) }}
+
+
+ {{ learningPath?.title }}
+
-
-
- {{ learningPath.title }}
-
-
-
-
- {{ $t("learningPathPage.welcomeBack", { name: userStore.first_name }) }}
-
-
-
-
- {{ $t("learningPathPage.nextStep") }}
-
- {{ learningPath.nextLearningContent.parentCircle.title }}:
- {{ learningPath.nextLearningContent.parentLearningSequence?.title }}
-
-
-
- {{ $t("general.start") }}
-
- {{ $t("general.nextStep") }}
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
diff --git a/client/src/router/__tests__/guards.spec.ts b/client/src/router/__tests__/guards.spec.ts
new file mode 100644
index 00000000..c4384db1
--- /dev/null
+++ b/client/src/router/__tests__/guards.spec.ts
@@ -0,0 +1,39 @@
+import { createPinia, setActivePinia } from "pinia";
+import { beforeEach, describe, expect, vi } from "vitest";
+import * as courseSessions from "../../stores/courseSessions";
+import { expertRequired } from "../guards";
+
+describe("Guards", () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ beforeEach(() => {
+ // creates a fresh pinia and make it active so it's automatically picked
+ // up by any useStore() call without having to pass it to it:
+ // `useStore(pinia)`
+ setActivePinia(createPinia());
+ });
+
+ it("cannot route to cockpit", () => {
+ vi.spyOn(courseSessions, "useCourseSessionsStore").mockReturnValue({
+ hasCockpit: false,
+ });
+ const slug = "test";
+ expect(expertRequired({ params: { courseSlug: "test" } })).toEqual(
+ `/course/${slug}/learn`
+ );
+ });
+
+ it("can route to cockpit", () => {
+ vi.spyOn(courseSessions, "useCourseSessionsStore").mockReturnValue({
+ hasCockpit: true,
+ });
+ const to = {
+ params: {
+ courseSlug: "test",
+ },
+ };
+ expect(expertRequired(to)).toEqual(to);
+ });
+});
diff --git a/client/src/router/guards.ts b/client/src/router/guards.ts
index e4902f33..47dddb6c 100644
--- a/client/src/router/guards.ts
+++ b/client/src/router/guards.ts
@@ -1,7 +1,8 @@
+import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useUserStore } from "@/stores/user";
-import type { NavigationGuardWithThis, RouteLocationNormalized } from "vue-router";
+import type { NavigationGuard, RouteLocationNormalized } from "vue-router";
-export const updateLoggedIn: NavigationGuardWithThis
= async () => {
+export const updateLoggedIn: NavigationGuard = async () => {
const loggedIn = getCookieValue("loginStatus") === "true";
const userStore = useUserStore();
@@ -11,7 +12,7 @@ export const updateLoggedIn: NavigationGuardWithThis = async () => {
}
};
-export const redirectToLoginIfRequired: NavigationGuardWithThis = (to) => {
+export const redirectToLoginIfRequired: NavigationGuard = (to) => {
const userStore = useUserStore();
if (loginRequired(to) && !userStore.loggedIn) {
return `/login?next=${to.fullPath}`;
@@ -32,3 +33,13 @@ export const getCookieValue = (cookieName: string): string => {
const loginRequired = (to: RouteLocationNormalized) => {
return !to.meta?.public;
};
+
+export const expertRequired: NavigationGuard = (to: RouteLocationNormalized) => {
+ const courseSessionsStore = useCourseSessionsStore();
+ if (courseSessionsStore.hasCockpit) {
+ return to;
+ } else {
+ const courseSlug = to.params.courseSlug as string;
+ return `/course/${courseSlug}/learn`;
+ }
+};
diff --git a/client/src/router/index.ts b/client/src/router/index.ts
index 0fa65d18..340f6b6e 100644
--- a/client/src/router/index.ts
+++ b/client/src/router/index.ts
@@ -1,6 +1,10 @@
import DashboardPage from "@/pages/DashboardPage.vue";
import LoginPage from "@/pages/LoginPage.vue";
-import { redirectToLoginIfRequired, updateLoggedIn } from "@/router/guards";
+import {
+ expertRequired,
+ redirectToLoginIfRequired,
+ updateLoggedIn,
+} from "@/router/guards";
import { useAppStore } from "@/stores/app";
import { createRouter, createWebHistory } from "vue-router";
@@ -103,6 +107,7 @@ const router = createRouter({
path: "/course/:courseSlug/cockpit",
props: true,
component: () => import("@/pages/cockpit/CockpitParentPage.vue"),
+ beforeEnter: [expertRequired],
children: [
{
path: "",
diff --git a/client/src/services/circle.ts b/client/src/services/circle.ts
index 7aec902f..14d6da42 100644
--- a/client/src/services/circle.ts
+++ b/client/src/services/circle.ts
@@ -222,6 +222,12 @@ export class Circle implements WagtailCircle {
return false;
}
+ public isComplete(): boolean {
+ return this.learningSequences.every((ls) =>
+ this.allFinishedInLearningSequence(ls.translation_key)
+ );
+ }
+
public parseCompletionData(completionData: CourseCompletion[]) {
this.flatChildren.forEach((page) => {
const pageIndex = completionData.findIndex((e) => {
diff --git a/client/src/services/learningPath.ts b/client/src/services/learningPath.ts
index 564a4aff..82b9f432 100644
--- a/client/src/services/learningPath.ts
+++ b/client/src/services/learningPath.ts
@@ -10,6 +10,11 @@ import type {
WagtailLearningPath,
} from "@/types";
+export interface ContinueData {
+ url: string;
+ has_no_progress: boolean;
+}
+
function getLastCompleted(courseId: number, completionData: CourseCompletion[]) {
return orderBy(completionData, ["updated_at"], "desc").find((c: CourseCompletion) => {
return (
@@ -125,4 +130,25 @@ export class LearningPath implements WagtailLearningPath {
}
}
}
+
+ public get continueData(): ContinueData {
+ if (this.nextLearningContent) {
+ const circle = this.nextLearningContent.parentCircle;
+ const url =
+ this.nextLearningContent.parentLearningSequence?.frontend_url ||
+ circle.frontend_url;
+ const isFirst =
+ this.nextLearningContent.translation_key ===
+ this.circles[0].flatLearningContents[0].translation_key;
+ return {
+ url,
+ has_no_progress: isFirst,
+ };
+ }
+
+ return {
+ url: "",
+ has_no_progress: true,
+ };
+ }
}
diff --git a/client/src/stores/__tests__/courseSession.spec.ts b/client/src/stores/__tests__/courseSession.spec.ts
new file mode 100644
index 00000000..fd0a6121
--- /dev/null
+++ b/client/src/stores/__tests__/courseSession.spec.ts
@@ -0,0 +1,91 @@
+import { itGetCached, itPost } from "@/fetchHelpers";
+import type { CourseSession } from "@/types";
+import { createPinia, setActivePinia } from "pinia";
+import { beforeEach, describe, expect, vi } from "vitest";
+import { useRoute } from "vue-router";
+import { useCourseSessionsStore } from "../courseSessions";
+import { useUserStore } from "../user";
+
+let user = {};
+let courseSessions: CourseSession[] = [];
+
+describe("CourseSession Store", () => {
+ vi.mock("vue-router", () => ({
+ useRoute: () => ({
+ path: "/course/test-course/learn/",
+ }),
+ }));
+
+ vi.mock("@/fetchHelpers", () => {
+ const itGetCached = () => Promise.resolve([]);
+ const itPost = () => Promise.resolve([]);
+
+ return {
+ itGetCached,
+ itPost,
+ };
+ });
+
+ beforeEach(() => {
+ // creates a fresh pinia and make it active so it's automatically picked
+ // up by any useStore() call without having to pass it to it:
+ // `useStore(pinia)`
+ setActivePinia(createPinia());
+ user = {
+ is_superuser: false,
+ course_session_expert: [],
+ };
+ courseSessions = [
+ {
+ id: 1,
+ created_at: "2021-05-11T10:00:00.000000Z",
+ updated_at: "2023-05-11T10:00:00.000000Z",
+ course: {
+ id: 1,
+ title: "Test Course",
+ category_name: "Test Category",
+ slug: "test-course",
+ },
+ title: "Test Course Session",
+ start_date: "2022-05-11T10:00:00.000000Z",
+ end_date: "2023-05-11T10:00:00.000000Z",
+ learning_path_url: "/course/test-course/learn/",
+ competence_url: "/course/test-course/competence/",
+ course_url: "/course/test-course/",
+ media_library_url: "/course/test-course/media/",
+ additional_json_data: {},
+ documents: [],
+ users: [],
+ },
+ ];
+ });
+
+ it("normal user has no cockpit", () => {
+ const userStore = useUserStore();
+ const courseSessionsStore = useCourseSessionsStore();
+ userStore.$patch(user);
+ courseSessionsStore.$patch({ courseSessions });
+
+ expect(courseSessionsStore.hasCockpit).toBeFalsy();
+ });
+
+ it("superuser has cockpit", () => {
+ const userStore = useUserStore();
+ const courseSessionsStore = useCourseSessionsStore();
+ userStore.$patch(Object.assign(user, { is_superuser: true }));
+ courseSessionsStore.$patch({ courseSessions });
+
+ expect(courseSessionsStore.hasCockpit).toBeTruthy();
+ });
+
+ it("expert has cockpit", () => {
+ const userStore = useUserStore();
+ const courseSessionsStore = useCourseSessionsStore();
+ userStore.$patch(
+ Object.assign(user, { course_session_experts: [courseSessions[0].id] })
+ );
+ courseSessionsStore.$patch({ courseSessions });
+
+ expect(courseSessionsStore.hasCockpit).toBeTruthy();
+ });
+});
diff --git a/client/src/stores/notifications.ts b/client/src/stores/notifications.ts
index 104b3e9f..ec6dcdf3 100644
--- a/client/src/stores/notifications.ts
+++ b/client/src/stores/notifications.ts
@@ -1,7 +1,9 @@
import { itGet } from "@/fetchHelpers";
+import { useUserStore } from "@/stores/user";
import type { Notification } from "@/types";
+import log from "loglevel";
import { defineStore } from "pinia";
-import { ref } from "vue";
+import { ref, watch } from "vue";
type NotificationListData = {
all_count: number;
@@ -29,8 +31,18 @@ export const useNotificationsStore = defineStore("notifications", () => {
hasUnread.value = data.unread_count !== 0;
}
- updateUnreadCount();
- setInterval(async () => await updateUnreadCount(), 30000);
+ const userStore = useUserStore();
+ let timerHandle: number | null = null;
+ watch(userStore, () => {
+ if (userStore.loggedIn && timerHandle === null) {
+ log.debug("Notification polling started");
+ updateUnreadCount();
+ timerHandle = setInterval(async () => await updateUnreadCount(), 30000);
+ } else if (!userStore.loggedIn) {
+ log.debug("Notification polling stopped");
+ timerHandle = null;
+ }
+ });
return { loadNotifications, hasUnread, allCount };
});
diff --git a/client/src/stores/user.ts b/client/src/stores/user.ts
index 3c06473a..df9564f4 100644
--- a/client/src/stores/user.ts
+++ b/client/src/stores/user.ts
@@ -8,7 +8,7 @@ import { defineStore } from "pinia";
const logoutRedirectUrl = import.meta.env.VITE_LOGOUT_REDIRECT;
// typed state https://stackoverflow.com/questions/71012513/when-using-pinia-and-typescript-how-do-you-use-an-action-to-set-the-state
-export type availableLanguages = "de" | "fr" | "it";
+export type AvailableLanguages = "de" | "fr" | "it";
export type UserState = {
id: number;
@@ -20,7 +20,7 @@ export type UserState = {
is_superuser: boolean;
course_session_experts: number[];
loggedIn: boolean;
- language: availableLanguages;
+ language: AvailableLanguages;
};
const initialUserState: UserState = {
@@ -36,7 +36,7 @@ const initialUserState: UserState = {
language: "de",
};
-async function setLocale(language: availableLanguages) {
+async function setLocale(language: AvailableLanguages) {
await loadLocaleMessages(language);
setI18nLanguage(language);
}
@@ -92,7 +92,7 @@ export const useUserStore = defineStore({
appStore.userLoaded = true;
await setLocale(data.language);
},
- async setUserLanguages(language: availableLanguages) {
+ async setUserLanguages(language: AvailableLanguages) {
await setLocale(language);
this.$state.language = language;
await itPost("/api/core/me/", { language }, { method: "PUT" });
diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json
index 0d2e0c18..1a19788b 100644
--- a/client/tsconfig.app.json
+++ b/client/tsconfig.app.json
@@ -1,15 +1,15 @@
{
- "extends": "@vue/tsconfig/tsconfig.web.json",
- "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
- "exclude": ["src/**/__tests__/*"],
"compilerOptions": {
- "lib": ["ES2021", "DOM", "DOM.Iterable"],
- "composite": true,
- "strict": true,
"allowJs": true,
"baseUrl": ".",
+ "composite": true,
+ "lib": ["ES2021", "DOM", "DOM.Iterable"],
"paths": {
"@/*": ["./src/*"]
- }
- }
+ },
+ "strict": true
+ },
+ "exclude": ["src/**/__tests__/*"],
+ "extends": "@vue/tsconfig/tsconfig.web.json",
+ "include": ["env.d.ts", "src/**/*", "src/**/*.vue"]
}
diff --git a/client/tsconfig.vite-config.json b/client/tsconfig.vite-config.json
index d20d8726..e3f2fdc1 100644
--- a/client/tsconfig.vite-config.json
+++ b/client/tsconfig.vite-config.json
@@ -1,8 +1,8 @@
{
- "extends": "@vue/tsconfig/tsconfig.node.json",
- "include": ["vite.config.*"],
"compilerOptions": {
"composite": true,
"types": ["node", "vitest"]
- }
+ },
+ "extends": "@vue/tsconfig/tsconfig.node.json",
+ "include": ["vite.config.*"]
}
diff --git a/client/tsconfig.vitest.json b/client/tsconfig.vitest.json
index 67705fd2..5e470cc6 100644
--- a/client/tsconfig.vitest.json
+++ b/client/tsconfig.vitest.json
@@ -1,9 +1,9 @@
{
- "extends": "./tsconfig.app.json",
- "exclude": [],
"compilerOptions": {
"composite": true,
"lib": [],
"types": ["node", "jsdom", "vitest/globals"]
- }
+ },
+ "exclude": [],
+ "extends": "./tsconfig.app.json"
}
diff --git a/client/vue-i18n-extract.config.js b/client/vue-i18n-extract.config.js
new file mode 100644
index 00000000..211d01d8
--- /dev/null
+++ b/client/vue-i18n-extract.config.js
@@ -0,0 +1,12 @@
+// eslint-disable-next-line
+module.exports = {
+ vueFiles: "./src/**/*.?(js|vue)",
+ languageFiles: "./src/locales/*.json",
+ exclude: ["translation_key_1", "translation_key_2"],
+ output: false,
+ add: false,
+ remove: false,
+ ci: false,
+ separator: ".",
+ noEmptyTranslation: "",
+};
diff --git a/cypress/e2e/learningPath.cy.js b/cypress/e2e/learningPath.cy.js
index 83c3738e..bd235d3f 100644
--- a/cypress/e2e/learningPath.cy.js
+++ b/cypress/e2e/learningPath.cy.js
@@ -1,4 +1,4 @@
-import {login} from "./helpers";
+import { login } from "./helpers";
describe("learningPath page", () => {
beforeEach(() => {
@@ -19,7 +19,7 @@ describe("learningPath page", () => {
login("admin", "test");
cy.visit("/course/versicherungsvermittler-in-alt/learn");
- cy.get('[data-cy="circle-analyse"]').click({force: true});
+ cy.get('[data-cy="circle-Analyse"]').click({ force: true });
cy.url().should(
"include",
@@ -28,20 +28,15 @@ describe("learningPath page", () => {
cy.get('[data-cy="circle-title"]').should("contain", "Analyse");
});
- it("open listView and click on circle will open circle", () => {
+ it("switch between list and path view", () => {
login("admin", "test");
cy.visit("/course/versicherungsvermittler-in-alt/learn");
- cy.get('[data-cy="show-list-view"]').click();
- cy.get('[data-cy="full-screen-modal"]').should("be.visible");
-
- cy.get('[data-cy="circle-analyse-vertical"]').click({force: true});
-
- cy.url().should(
- "include",
- "/course/versicherungsvermittler-in-alt/learn/analyse"
- );
- cy.get('[data-cy="circle-title"]').should("contain", "Analyse");
+ cy.get('[data-cy="lp-path-view"]').should("be.visible");
+ cy.get('[data-cy="view-switch"]').click();
+ cy.get('[data-cy="lp-list-view"]').should("be.visible");
+ cy.get('[data-cy="view-switch"]').click();
+ cy.get('[data-cy="lp-path-view"]').should("be.visible");
});
it("weiter gehts button will open next circle", () => {
@@ -49,21 +44,25 @@ describe("learningPath page", () => {
cy.visit("/course/versicherungsvermittler-in-alt/learn");
// first click will open first circle
- cy.get('[data-cy="lp-continue-button"]').should("contain", "Los geht's");
- cy.get('[data-cy="lp-continue-button"]').click();
+ cy.get('[data-cy="lp-continue-button"]')
+ .filter(":visible")
+ .should("contain", "Los geht's")
+ .click();
cy.get('[data-cy="circle-title"]').should("contain", "Basis");
cy.get('[data-cy="back-to-learning-path-button"]').click();
// mark a learning content in second circle
- cy.get('[data-cy="circle-analyse"]').click({force: true});
+ cy.get('[data-cy="circle-Analyse"]').click({ force: true });
cy.get(
'[data-cy="versicherungsvermittler-in-alt-lp-circle-analyse-lc-fachcheck-fahrzeug-checkbox"] > .cy-checkbox'
).click();
cy.get('[data-cy="back-to-learning-path-button"]').click();
// click on continue should go to unit-test-circle
- cy.get('[data-cy="lp-continue-button"]').should("contain", "Weiter geht's");
- cy.get('[data-cy="lp-continue-button"]').click();
+ cy.get('[data-cy="lp-continue-button"]')
+ .filter(":visible")
+ .should("contain", "Weiter geht's")
+ .click();
cy.get('[data-cy="circle-title"]').should("contain", "Analyse");
});
});
diff --git a/git-crypt-encrypted-files.txt b/git-crypt-encrypted-files.txt
index b6fc905c..9136f91d 100644
--- a/git-crypt-encrypted-files.txt
+++ b/git-crypt-encrypted-files.txt
@@ -1,8 +1,8 @@
-encrypted: env_secrets/caprover_dev.env
-encrypted: env_secrets/caprover_prod.env
-encrypted: env_secrets/caprover_stage.env
-encrypted: env_secrets/local_chrigu.env
-encrypted: env_secrets/local_daniel.env
-encrypted: env_secrets/local_elia.env
-encrypted: env_secrets/local_lorenz.env
-encrypted: env_secrets/production.env
+ encrypted: env_secrets/caprover_dev.env
+ encrypted: env_secrets/caprover_prod.env
+ encrypted: env_secrets/caprover_stage.env
+ encrypted: env_secrets/local_chrigu.env
+ encrypted: env_secrets/local_daniel.env
+ encrypted: env_secrets/local_elia.env
+ encrypted: env_secrets/local_lorenz.env
+ encrypted: env_secrets/production.env
diff --git a/package.json b/package.json
index 6229e1a5..3f1eb448 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,8 @@
"test": "echo \"Error: no test specified\" && exit 1",
"cypress:open": "cypress open",
"cypress:ci": "currents run --parallel --record --key $CURRENTS_KEY",
- "prettier": "npm run prettier --prefix client"
+ "prettier": "npm run prettier --prefix client",
+ "vue-i18n-extract": "npm run vue-i18n-extract --prefix client"
},
"devDependencies": {
"@currents/cli": "^3.1.3",
diff --git a/server/vbv_lernwelt/competence/create_uk_competence_profile.py b/server/vbv_lernwelt/competence/create_uk_competence_profile.py
index 03808e4a..03f89947 100644
--- a/server/vbv_lernwelt/competence/create_uk_competence_profile.py
+++ b/server/vbv_lernwelt/competence/create_uk_competence_profile.py
@@ -4,12 +4,12 @@ from vbv_lernwelt.competence.factories import (
PerformanceCriteriaFactory,
)
from vbv_lernwelt.competence.models import CompetencePage
-from vbv_lernwelt.course.consts import COURSE_UK1
+from vbv_lernwelt.course.consts import COURSE_UK, COURSE_UK_FR
from vbv_lernwelt.course.models import CoursePage
from vbv_lernwelt.learnpath.models import LearningPath, LearningUnit
-def create_uk_competence_profile(course_id=COURSE_UK1):
+def create_uk_competence_profile(course_id=COURSE_UK):
course_page = CoursePage.objects.get(course_id=course_id)
slug_prefix = course_page.get_children().exact_type(LearningPath).first().slug
@@ -20,104 +20,33 @@ def create_uk_competence_profile(course_id=COURSE_UK1):
competences = [
{
- "competence_id": "A1",
- "title": "Weiterempfehlung für Neukunden generieren",
- "items": [
- "Verhandlungsgeschick",
- "Überzeugtes Auftreten",
- ],
- },
- {
- "competence_id": "A2",
- "title": "Kundengespräche vereinbaren",
- "items": [
- "Gesprächsführung / Fragetechniken",
- "Selbstorganisation",
- "Arbeitstechniken",
- "Psychologische Kenntnisse / Kommunikations-psychologie",
- ],
- },
- {
- "competence_id": "A3",
- "title": "Auftritt in den sozialen Medien zeitgemäss halten",
- "items": [
- "Gesetzliche und Compliance-Anforderungen der Versicherer",
- "Datenschutzgesetz",
- "Kommunikation in den sozialen Medien",
- ],
- },
- {"competence_id": "A4", "title": "Kundendaten erfassen", "items": []},
- {
- "competence_id": "B1",
- "title": "Wünsche, Ziele und Bedürfnisse der Kunden im Gespräch ermitteln",
- "items": [
- "Gesprächsführung",
- "Fragetechniken",
- "Kundenpsychologie",
- ],
- },
- {
- "competence_id": "B2",
- "title": "Analyse des Kundenbedarfs und des Kundenbedürfnisses durchführen",
- "items": [
- "Fragetechniken",
- "Visuelle Hilfsmittel / Visualisierungstechniken",
- ],
- },
- {
- "competence_id": "B3",
- "title": "Individuelle Lösungsvorschläge erarbeiten",
- "items": [
- "Fundierte Produktekenntnisse",
- "Regulatorische Vorschriften",
- ],
- },
- {
- "competence_id": "B4",
- "title": "Lösungsvorschläge präsentieren und umsetzen",
- "items": [
- "Verhandlungsstrategien",
- "Fundierte Produktkenntnisse",
- "Visuelle Hilfsmittel / Visualisierungstechniken",
- ],
- },
- {
- "competence_id": "C1",
- "title": "Cross- und Upselling; bestehende fremdverwaltete Versicherungspolicen prüfen und in das Portfolio aufnehmen",
- "items": [
- "Produktkenntnisse",
- "Gesprächsführung",
- "Kommunikation",
- "Fragetechnik",
- "Verhandlungsgeschick",
- "Vertragsrecht",
- "Regulatorische Vorgaben",
- "UVG, BVG, KVG, VVG",
- ],
- },
- {
- "competence_id": "C2",
- "title": "Änderungswünsche entgegennehmen und bestehende Verträge anpassen",
- "items": [
- "Produktkenntnisse",
- "Gesprächsführung",
- "Kommunikation",
- "Fragetechnik",
- "Verhandlungsgeschick",
- "Vertragsrecht",
- "Regulatorische Vorgaben",
- "UVG, BVG, KVG, VVG",
- ],
- },
- {
- "competence_id": "C3",
- "title": "Kunden im Schadenfall unterstützen",
+ "competence_id": "e4",
+ "title": "Betriebsbezogene Inhalte multimedial aufbereiten",
"items": [],
},
- {"competence_id": "C4", "title": "Bestehende Kunden pflegen", "items": []},
{
- "competence_id": "C5",
- "title": "Versicherungsanträge nachbearbeiten",
+ "competence_id": "c3",
+ "title": "Betriebliche Prozesse dokumentieren, koordinieren und umsetzen",
+ "items": [],
+ },
+ {
+ "competence_id": "e2",
+ "title": "Informationen im wirtschaftlichen und kaufmännischen Bereich recherchieren",
+ "items": [],
+ },
+ {
+ "competence_id": "d2",
+ "title": "Informations- und Beratungsgespräche mit Kunden oder Lieferanten führen",
+ "items": [],
+ },
+ {
+ "competence_id": "d3",
+ "title": "Betriebliche Prozesse dokumentieren, koordinieren und umsetzen",
+ "items": [],
+ },
+ {
+ "competence_id": "d1",
+ "title": "Anliegen von Kunden oder Lieferanten entgegennehmen",
"items": [],
},
]
@@ -130,165 +59,235 @@ def create_uk_competence_profile(course_id=COURSE_UK1):
items=[("item", i) for i in c["items"]],
)
- # Daten anhand von WEVM_Version Oktober 2022
- # Einstieg/Beobachten – Selbsteinschätzung «Einkommenssicherung»
PerformanceCriteriaFactory(
parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2"
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="e4"
),
- competence_id="A2.1",
- title="Ich bin fähig je nach (Neu-) Kunde Form und Ort für das Gespräch festzulegen.",
- learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-kickoff-lu-einführung"
- ),
- )
- PerformanceCriteriaFactory(
- parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2"
- ),
- competence_id="A2.2",
- title="Ich bin fähig mir intern und extern die nötigen Informationen über den (Neu-) Kunden zu beschaffen.",
- learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-kickoff-lu-arbeits-und-lerntechnik"
- ),
- )
- PerformanceCriteriaFactory(
- parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2"
- ),
- competence_id="A2.3",
- title="Ich bin fähig die Terminierung auf das Thema Einkommenssicherung auszurichten.",
- learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-kickoff-lu-versicherung"
- ),
- )
- PerformanceCriteriaFactory(
- parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2"
- ),
- competence_id="A2.4",
- title="Ich bin fähig für das zu führende Gespräch eine Agenda zu erstellen.",
- learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-kickoff-lu-beratung-und-verkauf"
- ),
- )
- PerformanceCriteriaFactory(
- parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2"
- ),
- competence_id="A2.5",
- title="Ich bin fähig für das Handlungsfeld «Einkommenssicherung» geeignete Hilfsmittel und Unterlagen zusammenzustellen.",
- learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-kickoff-lu-sozialer-auftritt"
- ),
- )
- PerformanceCriteriaFactory(
- parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="B1"
- ),
- competence_id="B1.1",
- title="Ich bin fähig dem Kunden den Gesprächsablauf und den Zeitrahmen (mittels Agenda) aufzuzeigen.",
- learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-basis-lu-einführung"
- ),
- )
- PerformanceCriteriaFactory(
- parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="B1"
- ),
- competence_id="B1.2",
- title="Ich bin fähig mich beim Kunden korrekt zu identifizieren (VAG 45).",
- learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-basis-lu-arbeits-und-lerntechnik"
- ),
- )
- PerformanceCriteriaFactory(
- parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="B2"
- ),
- competence_id="B2.3",
- title="Ich bin fähig alle erforderlichen Unterlagen einzufordern.",
- learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-basis-lu-versicherung"
- ),
- )
- PerformanceCriteriaFactory(
- parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A1"
- ),
- competence_id="A1.6",
- title="Ich bin fähig im täglichen Kontakt potenzielle Kundinnen und Kunden zu erkennen.",
- learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-basis-lu-versicherung"
- ),
- )
- PerformanceCriteriaFactory(
- parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2"
- ),
- competence_id="A2.1",
- title="Ich bin fähig je nach (Neu-) Kunde Form und Ort für das Gespräch festzulegen.",
- learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-basis-lu-beratung-und-verkauf"
- ),
- )
- PerformanceCriteriaFactory(
- parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2"
- ),
- competence_id="A2.2",
- title="Ich bin fähig mir intern und extern die nötigen Informationen über den (Neu-) Kunden zu beschaffen.",
- learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-basis-lu-sozialer-auftritt"
- ),
- )
- PerformanceCriteriaFactory(
- parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2"
- ),
- competence_id="A2.3",
- title="Ich bin fähig die Terminierung auf das Thema Fahrzeug auszurichten.",
- learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-fahrzeug-lu-versicherung"
- ),
- )
- PerformanceCriteriaFactory(
- parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2"
- ),
- competence_id="A2.4",
- title="Ich bin fähig für das zu führende Gespräch eine Agenda zu erstellen.",
+ competence_id="e4.pv.ük1",
+ title="Sie erläutern die Dienstleistungen des Betriebs. (K2)",
learning_unit=LearningUnit.objects.get(
slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung"
),
)
PerformanceCriteriaFactory(
parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2"
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="e4"
),
- competence_id="A2.5",
- title="Ich bin fähig für das zu führende Gespräch geeignete Hilfsmittel und Unterlagen zusammenzustellen.",
+ competence_id="e4.pv.ük3",
+ title="Sie stellen die Wertschöpfungskette und die verschiedenen organisatorischen Bereiche ihres Betriebs dar. (K2).",
learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-fahrzeug-lu-arbeits-und-lerntechnik"
+ slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung"
),
)
PerformanceCriteriaFactory(
parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2"
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="e4"
),
- competence_id="A2.3",
- title="Ich bin fähig die Terminierung auf das Thema Reisen auszurichten.",
+ competence_id="e4.pv.ük4",
+ title="Sie erläutern die relevanten rechtlichen Grundlagen ihrer Branche situationsgerecht. (K2)",
learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-fahrzeug-lu-versicherung"
+ slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung"
),
)
PerformanceCriteriaFactory(
parent=CompetencePage.objects.get(
- slug__startswith=slug_prefix.replace("-lp", ""), competence_id="A2"
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2"
),
- competence_id="A2.4",
- title="Ich bin fähig für das zu führende Gespräch eine Agenda zu erstellen.",
+ competence_id="d2.pv.ük1",
+ title="Sie beschreiben die wichtigsten gesetzlichen Grundlagen im Versicherungsbereich umfassend. (K2)",
learning_unit=LearningUnit.objects.get(
- slug=f"{slug_prefix}-circle-fahrzeug-lu-sozialer-auftritt"
+ slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung"
+ ),
+ )
+ PerformanceCriteriaFactory(
+ parent=CompetencePage.objects.get(
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2"
+ ),
+ competence_id="d2.pv.ük2",
+ title="Sie nennen die relevanten Dienstleistungen und Produkte im Versicherungsbereich. (K1)",
+ learning_unit=LearningUnit.objects.get(
+ slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung"
+ ),
+ )
+ PerformanceCriteriaFactory(
+ parent=CompetencePage.objects.get(
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2"
+ ),
+ competence_id="d2.pv.ük3",
+ title="Sie erklären die Leistungen und Produkte im Versicherungsbereich. (K2)",
+ learning_unit=LearningUnit.objects.get(
+ slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung"
+ ),
+ )
+ PerformanceCriteriaFactory(
+ parent=CompetencePage.objects.get(
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2"
+ ),
+ competence_id="d2.pv.ük4",
+ title="Sie erläutern die Prozesse und Abläufe im privaten Versicherungsbereich verständlich. (K2)",
+ learning_unit=LearningUnit.objects.get(
+ slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung"
+ ),
+ )
+ PerformanceCriteriaFactory(
+ parent=CompetencePage.objects.get(
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="c3"
+ ),
+ competence_id="c3.pv.ük7",
+ title="Sie erläutern die gesetzlichen Bestimmungen bei Schaden- und Leistungsfällen. (K2)",
+ learning_unit=LearningUnit.objects.get(
+ slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung"
+ ),
+ )
+ PerformanceCriteriaFactory(
+ parent=CompetencePage.objects.get(
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="c3"
+ ),
+ competence_id="c3.pv.ük11",
+ title="Sie beurteilen gängige Versicherungslösungen fachkundig. (K3)",
+ learning_unit=LearningUnit.objects.get(
+ slug=f"{slug_prefix}-circle-fahrzeug-lu-einführung"
+ ),
+ )
+
+
+def create_uk_fr_competence_profile(course_id=COURSE_UK_FR):
+ course_page = CoursePage.objects.get(course_id=course_id)
+ slug_prefix = course_page.get_children().exact_type(LearningPath).first().slug
+
+ competence_profile_page = CompetenceProfilePageFactory(
+ title="KompetenzNavi",
+ parent=course_page,
+ )
+
+ competences = [
+ {
+ "competence_id": "e4",
+ "title": "Betriebsbezogene Inhalte multimedial aufbereiten",
+ "items": [],
+ },
+ {
+ "competence_id": "c3",
+ "title": "Betriebliche Prozesse dokumentieren, koordinieren und umsetzen",
+ "items": [],
+ },
+ {
+ "competence_id": "e2",
+ "title": "Informationen im wirtschaftlichen und kaufmännischen Bereich recherchieren",
+ "items": [],
+ },
+ {
+ "competence_id": "d2",
+ "title": "Informations- und Beratungsgespräche mit Kunden oder Lieferanten führen",
+ "items": [],
+ },
+ {
+ "competence_id": "d3",
+ "title": "Betriebliche Prozesse dokumentieren, koordinieren und umsetzen",
+ "items": [],
+ },
+ {
+ "competence_id": "d1",
+ "title": "Anliegen von Kunden oder Lieferanten entgegennehmen",
+ "items": [],
+ },
+ ]
+
+ for c in competences:
+ CompetencePageFactory(
+ parent=competence_profile_page,
+ competence_id=c["competence_id"],
+ title=c["title"],
+ items=[("item", i) for i in c["items"]],
+ )
+
+ PerformanceCriteriaFactory(
+ parent=CompetencePage.objects.get(
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="e4"
+ ),
+ competence_id="e4.pv.ük1",
+ title="Sie erläutern die Dienstleistungen des Betriebs. (K2)",
+ learning_unit=LearningUnit.objects.get(
+ slug=f"{slug_prefix}-circle-véhicule-lu-einführung"
+ ),
+ )
+ PerformanceCriteriaFactory(
+ parent=CompetencePage.objects.get(
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="e4"
+ ),
+ competence_id="e4.pv.ük3",
+ title="Sie stellen die Wertschöpfungskette und die verschiedenen organisatorischen Bereiche ihres Betriebs dar. (K2).",
+ learning_unit=LearningUnit.objects.get(
+ slug=f"{slug_prefix}-circle-véhicule-lu-einführung"
+ ),
+ )
+ PerformanceCriteriaFactory(
+ parent=CompetencePage.objects.get(
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="e4"
+ ),
+ competence_id="e4.pv.ük4",
+ title="Sie erläutern die relevanten rechtlichen Grundlagen ihrer Branche situationsgerecht. (K2)",
+ learning_unit=LearningUnit.objects.get(
+ slug=f"{slug_prefix}-circle-véhicule-lu-einführung"
+ ),
+ )
+ PerformanceCriteriaFactory(
+ parent=CompetencePage.objects.get(
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2"
+ ),
+ competence_id="d2.pv.ük1",
+ title="Sie beschreiben die wichtigsten gesetzlichen Grundlagen im Versicherungsbereich umfassend. (K2)",
+ learning_unit=LearningUnit.objects.get(
+ slug=f"{slug_prefix}-circle-véhicule-lu-einführung"
+ ),
+ )
+ PerformanceCriteriaFactory(
+ parent=CompetencePage.objects.get(
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2"
+ ),
+ competence_id="d2.pv.ük2",
+ title="Sie nennen die relevanten Dienstleistungen und Produkte im Versicherungsbereich. (K1)",
+ learning_unit=LearningUnit.objects.get(
+ slug=f"{slug_prefix}-circle-véhicule-lu-einführung"
+ ),
+ )
+ PerformanceCriteriaFactory(
+ parent=CompetencePage.objects.get(
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2"
+ ),
+ competence_id="d2.pv.ük3",
+ title="Sie erklären die Leistungen und Produkte im Versicherungsbereich. (K2)",
+ learning_unit=LearningUnit.objects.get(
+ slug=f"{slug_prefix}-circle-véhicule-lu-einführung"
+ ),
+ )
+ PerformanceCriteriaFactory(
+ parent=CompetencePage.objects.get(
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="d2"
+ ),
+ competence_id="d2.pv.ük4",
+ title="Sie erläutern die Prozesse und Abläufe im privaten Versicherungsbereich verständlich. (K2)",
+ learning_unit=LearningUnit.objects.get(
+ slug=f"{slug_prefix}-circle-véhicule-lu-einführung"
+ ),
+ )
+ PerformanceCriteriaFactory(
+ parent=CompetencePage.objects.get(
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="c3"
+ ),
+ competence_id="c3.pv.ük7",
+ title="Sie erläutern die gesetzlichen Bestimmungen bei Schaden- und Leistungsfällen. (K2)",
+ learning_unit=LearningUnit.objects.get(
+ slug=f"{slug_prefix}-circle-véhicule-lu-einführung"
+ ),
+ )
+ PerformanceCriteriaFactory(
+ parent=CompetencePage.objects.get(
+ slug__startswith=slug_prefix.replace("-lp", ""), competence_id="c3"
+ ),
+ competence_id="c3.pv.ük11",
+ title="Sie beurteilen gängige Versicherungslösungen fachkundig. (K3)",
+ learning_unit=LearningUnit.objects.get(
+ slug=f"{slug_prefix}-circle-véhicule-lu-einführung"
),
)
diff --git a/server/vbv_lernwelt/core/management/commands/cypress_reset.py b/server/vbv_lernwelt/core/management/commands/cypress_reset.py
index 74d3961d..de26ad62 100644
--- a/server/vbv_lernwelt/core/management/commands/cypress_reset.py
+++ b/server/vbv_lernwelt/core/management/commands/cypress_reset.py
@@ -1,5 +1,6 @@
import djclick as click
+from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import CourseCompletion
from vbv_lernwelt.notify.models import Notification
@@ -9,3 +10,4 @@ def command():
print("cypress reset data")
CourseCompletion.objects.all().delete()
Notification.objects.all().delete()
+ User.objects.all().update(language="de")
diff --git a/server/vbv_lernwelt/course/consts.py b/server/vbv_lernwelt/course/consts.py
index ee148f60..94ba8517 100644
--- a/server/vbv_lernwelt/course/consts.py
+++ b/server/vbv_lernwelt/course/consts.py
@@ -1,4 +1,5 @@
COURSE_TEST_ID = -1
COURSE_VERSICHERUNGSVERMITTLERIN_OLD_ID = -2
-COURSE_UK1 = -3
+COURSE_UK = -3
COURSE_VERSICHERUNGSVERMITTLERIN_ID = -4
+COURSE_UK_FR = -5
diff --git a/server/vbv_lernwelt/course/management/commands/create_default_courses.py b/server/vbv_lernwelt/course/management/commands/create_default_courses.py
index d51d1076..7e717c9e 100644
--- a/server/vbv_lernwelt/course/management/commands/create_default_courses.py
+++ b/server/vbv_lernwelt/course/management/commands/create_default_courses.py
@@ -3,6 +3,7 @@ from wagtail.models import Page
from vbv_lernwelt.competence.create_uk_competence_profile import (
create_uk_competence_profile,
+ create_uk_fr_competence_profile,
)
from vbv_lernwelt.competence.create_vv_competence_profile import (
create_vv_competence_profile,
@@ -14,7 +15,8 @@ from vbv_lernwelt.core.create_default_users import default_users
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.consts import (
COURSE_TEST_ID,
- COURSE_UK1,
+ COURSE_UK,
+ COURSE_UK_FR,
COURSE_VERSICHERUNGSVERMITTLERIN_ID,
COURSE_VERSICHERUNGSVERMITTLERIN_OLD_ID,
)
@@ -23,6 +25,7 @@ from vbv_lernwelt.course.creators.versicherungsvermittlerin import (
create_versicherungsvermittlerin_with_categories,
)
from vbv_lernwelt.course.management.commands.create_uk_course import (
+ create_uk_fr_learning_path,
create_uk_learning_path,
)
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
@@ -66,13 +69,21 @@ def command():
create_vv_new_learning_path()
create_default_media_library(course_id=COURSE_VERSICHERUNGSVERMITTLERIN_ID)
- # Überbetriebliche Kurse
+ # Überbetriebliche Kurse DE
create_versicherungsvermittlerin_with_categories(
- course_id=COURSE_UK1, title="Überbetriebliche Kurse"
+ course_id=COURSE_UK, title="Überbetriebliche Kurse"
)
- create_uk_learning_path(course_id=COURSE_UK1)
- create_uk_competence_profile(course_id=COURSE_UK1)
- create_default_media_library(course_id=COURSE_UK1)
+ create_uk_learning_path(course_id=COURSE_UK)
+ create_uk_competence_profile(course_id=COURSE_UK)
+ create_default_media_library(course_id=COURSE_UK)
+
+ # Überbetriebliche Kurse FR
+ create_versicherungsvermittlerin_with_categories(
+ course_id=COURSE_UK_FR, title="Course hors établissement"
+ )
+ create_uk_fr_learning_path(course_id=COURSE_UK_FR)
+ create_uk_fr_competence_profile(course_id=COURSE_UK_FR)
+ create_default_media_library(course_id=COURSE_UK_FR)
# test course
create_test_course()
@@ -137,88 +148,120 @@ def command():
)
# course session Überbetriebliche Kurse Lehrjahr 1 - Region Bern
- cs = CourseSession.objects.create(
- course_id=COURSE_UK1,
- title="Überbetriebliche Kurse Lehrjahr 1 - Region Bern",
- )
- # for user_data in default_users:
- # CourseSessionUser.objects.create(
- # course_session=cs,
- # user=User.objects.get(username=user_data["email"]),
- # )
- # csu = CourseSessionUser.objects.create(
- # course_session=cs,
- # user=User.objects.get(username="trainer-uk1.einstieg@eiger-versicherungen.ch"),
- # )
- # csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-einstieg"))
- # csu = CourseSessionUser.objects.create(
- # course_session=cs,
- # user=User.objects.get(username="trainer-uk1.analyse@eiger-versicherungen.ch"),
- # )
- # csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-analyse"))
- # csu = CourseSessionUser.objects.create(
- # course_session=cs,
- # user=User.objects.get(username="student-uk1-bern@eiger-versicherungen.ch"),
- # )
+ courses = [
+ {
+ "course_id": COURSE_UK,
+ "title": "Überbetriebliche Kurse Lehrjahr 1 - Region Bern",
+ "basis_slug": "überbetriebliche-kurse-lp-circle-basis",
+ "kickoff_slug": "überbetriebliche-kurse-lp-circle-kickoff",
+ "haushalt1_slug": "überbetriebliche-kurse-lp-circle-haushalt-teil-1",
+ "fahrzeug_slug": "überbetriebliche-kurse-lp-circle-fahrzeug",
+ "haushalt2_slug": "überbetriebliche-kurse-lp-circle-haushalt-teil-2",
+ },
+ {
+ "course_id": COURSE_UK_FR,
+ "title": "Cours hors établissement année 1 - Région Fribourg",
+ "basis_slug": "course-hors-établissement-lp-circle-basis",
+ "kickoff_slug": "course-hors-établissement-lp-circle-kickoff",
+ "haushalt1_slug": "course-hors-établissement-lp-circle-haushalt-teil-1",
+ "fahrzeug_slug": "course-hors-établissement-lp-circle-fahrzeug",
+ "haushalt2_slug": "course-hors-établissement-lp-circle-haushalt-teil-2",
+ },
+ ]
+ for course in courses:
+ cs = CourseSession.objects.create(
+ course_id=course["course_id"],
+ title=course["title"],
+ )
+ # for user_data in default_users:
+ # CourseSessionUser.objects.create(
+ # course_session=cs,
+ # user=User.objects.get(username=user_data["email"]),
+ # )
+ # csu = CourseSessionUser.objects.create(
+ # course_session=cs,
+ # user=User.objects.get(username="trainer-uk1.einstieg@eiger-versicherungen.ch"),
+ # )
+ # csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-einstieg"))
+ # csu = CourseSessionUser.objects.create(
+ # course_session=cs,
+ # user=User.objects.get(username="trainer-uk1.analyse@eiger-versicherungen.ch"),
+ # )
+ # csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-analyse"))
+ # csu = CourseSessionUser.objects.create(
+ # course_session=cs,
+ # user=User.objects.get(username="student-uk1-bern@eiger-versicherungen.ch"),
+ # )
- # figma demo users and data
- csu = CourseSessionUser.objects.create(
- course_session=cs,
- user=User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch"),
- role=CourseSessionUser.Role.EXPERT,
- )
- csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-kickoff"))
- csu.expert.add(
- Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-1")
- )
- csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-fahrzeug"))
- csu = CourseSessionUser.objects.create(
- course_session=cs,
- user=User.objects.get(username="andreas.feuz@eiger-versicherungen.ch"),
- role=CourseSessionUser.Role.EXPERT,
- )
- csu.expert.add(
- Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-2")
- )
- csu.expert.add(Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-basis"))
- _csu = CourseSessionUser.objects.create(
- course_session=cs,
- user=User.objects.get(username="michael.meier@example.com"),
- )
- _csu = CourseSessionUser.objects.create(
- course_session=cs,
- user=User.objects.get(username="lina.egger@example.com"),
- )
- _csu = CourseSessionUser.objects.create(
- course_session=cs,
- user=User.objects.get(username="evelyn.schmid@example.com"),
- )
+ # figma demo users and data
+ csu = CourseSessionUser.objects.create(
+ course_session=cs,
+ user=User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch"),
+ role=CourseSessionUser.Role.EXPERT,
+ )
+ csu.expert.add(
+ Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-kickoff")
+ )
+ csu.expert.add(
+ Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-1")
+ )
+ csu.expert.add(
+ Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-fahrzeug")
+ )
+ csu = CourseSessionUser.objects.create(
+ course_session=cs,
+ user=User.objects.get(username="andreas.feuz@eiger-versicherungen.ch"),
+ role=CourseSessionUser.Role.EXPERT,
+ )
+ csu.expert.add(
+ Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-2")
+ )
+ csu.expert.add(
+ Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-basis")
+ )
+ _csu = CourseSessionUser.objects.create(
+ course_session=cs,
+ user=User.objects.get(username="michael.meier@example.com"),
+ )
+ _csu = CourseSessionUser.objects.create(
+ course_session=cs,
+ user=User.objects.get(username="lina.egger@example.com"),
+ )
+ _csu = CourseSessionUser.objects.create(
+ course_session=cs,
+ user=User.objects.get(username="evelyn.schmid@example.com"),
+ )
- _csu = CourseSessionUser.objects.create(
- course_session=cs,
- user=User.objects.get(username="christoph.bosshard@vbv-afa.ch"),
- )
+ _csu = CourseSessionUser.objects.create(
+ course_session=cs,
+ user=User.objects.get(username="christoph.bosshard@vbv-afa.ch"),
+ )
- _csu = CourseSessionUser.objects.create(
- course_session=cs,
- user=User.objects.get(username="axel.manderbach@lernetz.ch"),
- )
+ _csu = CourseSessionUser.objects.create(
+ course_session=cs,
+ user=User.objects.get(username="bianca.muster@eiger-versicherungen.ch"),
+ )
- create_feedback(
- Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-kickoff"), cs, 3
- )
- create_feedback(
- Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-2"),
- cs,
- 14,
- )
- create_feedback(
- Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-basis"), cs, 4
- )
+ _csu = CourseSessionUser.objects.create(
+ course_session=cs,
+ user=User.objects.get(username="axel.manderbach@lernetz.ch"),
+ )
+
+ create_feedback(
+ Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-kickoff"), cs, 3
+ )
+ create_feedback(
+ Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-2"),
+ cs,
+ 14,
+ )
+ create_feedback(
+ Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-basis"), cs, 4
+ )
# course session Überbetriebliche Kurse Lehrjahr 1 - Region Zürich
cs = CourseSession.objects.create(
- course_id=COURSE_UK1,
+ course_id=COURSE_UK,
title="Überbetriebliche Kurse Lehrjahr 1 - Region Zürich",
)
# for user_data in default_users:
@@ -248,7 +291,7 @@ def command():
# initial completion data
for slug, status, email in [
(
- "überbetriebliche-kurse-competence-crit-a21-allgemein",
+ "überbetriebliche-kurse-competence-crit-e4pvük4-allgemein",
"success",
"michael.meier@example.com",
),
@@ -273,7 +316,7 @@ def command():
"michael.meier@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a22-allgemein",
+ "überbetriebliche-kurse-competence-crit-e4pvük3-allgemein",
"success",
"michael.meier@example.com",
),
@@ -293,7 +336,7 @@ def command():
"michael.meier@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a23-allgemein",
+ "überbetriebliche-kurse-competence-crit-e4pvük1-allgemein",
"success",
"michael.meier@example.com",
),
@@ -313,7 +356,7 @@ def command():
"michael.meier@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a24-allgemein",
+ "überbetriebliche-kurse-competence-crit-d2pvük4-allgemein",
"fail",
"michael.meier@example.com",
),
@@ -338,7 +381,7 @@ def command():
"michael.meier@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a25-allgemein",
+ "überbetriebliche-kurse-competence-crit-d2pvük3-allgemein",
"success",
"michael.meier@example.com",
),
@@ -368,7 +411,7 @@ def command():
"michael.meier@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a24-allgemein-1",
+ "überbetriebliche-kurse-competence-crit-d2pvük2-allgemein",
"success",
"michael.meier@example.com",
),
@@ -393,7 +436,7 @@ def command():
"michael.meier@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a25-allgemein-1",
+ "überbetriebliche-kurse-competence-crit-d2pvük1-allgemein",
"success",
"michael.meier@example.com",
),
@@ -413,7 +456,7 @@ def command():
"michael.meier@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a23-allgemein-1",
+ "überbetriebliche-kurse-competence-crit-c3pvük7-allgemein",
"fail",
"michael.meier@example.com",
),
@@ -433,7 +476,7 @@ def command():
"michael.meier@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a23-allgemein-2",
+ "überbetriebliche-kurse-competence-crit-c3pvük11-allgemein",
"fail",
"michael.meier@example.com",
),
@@ -498,7 +541,7 @@ def command():
"lina.egger@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a21-allgemein",
+ "überbetriebliche-kurse-competence-crit-e4pvük4-allgemein",
"success",
"lina.egger@example.com",
),
@@ -523,7 +566,7 @@ def command():
"lina.egger@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a22-allgemein",
+ "überbetriebliche-kurse-competence-crit-e4pvük3-allgemein",
"success",
"lina.egger@example.com",
),
@@ -543,7 +586,7 @@ def command():
"lina.egger@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a23-allgemein",
+ "überbetriebliche-kurse-competence-crit-e4pvük1-allgemein",
"success",
"lina.egger@example.com",
),
@@ -563,7 +606,7 @@ def command():
"lina.egger@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a24-allgemein",
+ "überbetriebliche-kurse-competence-crit-d2pvük4-allgemein",
"success",
"lina.egger@example.com",
),
@@ -583,7 +626,7 @@ def command():
"lina.egger@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a25-allgemein",
+ "überbetriebliche-kurse-competence-crit-d2pvük3-allgemein",
"success",
"lina.egger@example.com",
),
@@ -613,7 +656,7 @@ def command():
"lina.egger@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a24-allgemein-1",
+ "überbetriebliche-kurse-competence-crit-d2pvük2-allgemein",
"success",
"lina.egger@example.com",
),
@@ -638,7 +681,7 @@ def command():
"lina.egger@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a25-allgemein-1",
+ "überbetriebliche-kurse-competence-crit-d2pvük1-allgemein",
"success",
"lina.egger@example.com",
),
@@ -658,12 +701,12 @@ def command():
"lina.egger@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a23-allgemein-1",
+ "überbetriebliche-kurse-competence-crit-c3pvük7-allgemein",
"success",
"lina.egger@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a23-allgemein-2",
+ "überbetriebliche-kurse-competence-crit-c3pvük11-allgemein",
"success",
"lina.egger@example.com",
),
@@ -697,11 +740,6 @@ def command():
"success",
"lina.egger@example.com",
),
- (
- "überbetriebliche-kurse-competence-crit-a24-allgemein-2",
- "success",
- "lina.egger@example.com",
- ),
(
"überbetriebliche-kurse-lp-circle-fahrzeug-lc-mediathek-4",
"success",
@@ -773,7 +811,7 @@ def command():
"evelyn.schmid@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a21-allgemein",
+ "überbetriebliche-kurse-competence-crit-e4pvük4-allgemein",
"success",
"evelyn.schmid@example.com",
),
@@ -798,7 +836,7 @@ def command():
"evelyn.schmid@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a22-allgemein",
+ "überbetriebliche-kurse-competence-crit-e4pvük3-allgemein",
"fail",
"evelyn.schmid@example.com",
),
@@ -818,7 +856,7 @@ def command():
"evelyn.schmid@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a23-allgemein",
+ "überbetriebliche-kurse-competence-crit-e4pvük1-allgemein",
"fail",
"evelyn.schmid@example.com",
),
@@ -838,7 +876,7 @@ def command():
"evelyn.schmid@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a24-allgemein",
+ "überbetriebliche-kurse-competence-crit-d2pvük4-allgemein",
"success",
"evelyn.schmid@example.com",
),
@@ -858,7 +896,7 @@ def command():
"evelyn.schmid@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a25-allgemein",
+ "überbetriebliche-kurse-competence-crit-d2pvük3-allgemein",
"success",
"evelyn.schmid@example.com",
),
@@ -873,7 +911,7 @@ def command():
"evelyn.schmid@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a24-allgemein-1",
+ "überbetriebliche-kurse-competence-crit-d2pvük2-allgemein",
"success",
"evelyn.schmid@example.com",
),
@@ -898,7 +936,7 @@ def command():
"evelyn.schmid@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a25-allgemein-1",
+ "überbetriebliche-kurse-competence-crit-d2pvük1-allgemein",
"fail",
"evelyn.schmid@example.com",
),
@@ -918,12 +956,12 @@ def command():
"evelyn.schmid@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a23-allgemein-1",
+ "überbetriebliche-kurse-competence-crit-c3pvük7-allgemein",
"success",
"evelyn.schmid@example.com",
),
(
- "überbetriebliche-kurse-competence-crit-a23-allgemein-2",
+ "überbetriebliche-kurse-competence-crit-c3pvük11-allgemein",
"fail",
"evelyn.schmid@example.com",
),
@@ -962,11 +1000,6 @@ def command():
"success",
"evelyn.schmid@example.com",
),
- (
- "überbetriebliche-kurse-competence-crit-a24-allgemein-2",
- "fail",
- "evelyn.schmid@example.com",
- ),
(
"überbetriebliche-kurse-lp-circle-fahrzeug-lc-lerninhalt-offen-1",
"success",
diff --git a/server/vbv_lernwelt/course/management/commands/create_uk_course.py b/server/vbv_lernwelt/course/management/commands/create_uk_course.py
index 372f24d7..89f039da 100644
--- a/server/vbv_lernwelt/course/management/commands/create_uk_course.py
+++ b/server/vbv_lernwelt/course/management/commands/create_uk_course.py
@@ -5,7 +5,7 @@ from wagtail.models import Locale, Page, Site
from wagtail_localize.models import LocaleSynchronization
from vbv_lernwelt.core.admin import User
-from vbv_lernwelt.course.consts import COURSE_UK1
+from vbv_lernwelt.course.consts import COURSE_UK, COURSE_UK_FR
from vbv_lernwelt.course.models import CoursePage
from vbv_lernwelt.learnpath.create_vv_learning_path import (
create_learning_content_beenden,
@@ -19,11 +19,8 @@ from vbv_lernwelt.learnpath.tests.learning_path_factories import (
TopicFactory,
)
-# todo: remove when all Handlungsfelder are ready
-READY_HF = ["Fahrzeug", "Reisen"]
-
-def create_uk_learning_path(course_id=COURSE_UK1, user=None, skip_locales=True):
+def create_uk_learning_path(course_id=COURSE_UK, user=None, skip_locales=True):
if user is None:
user = User.objects.get(username="info@iterativ.ch")
@@ -42,27 +39,26 @@ def create_uk_learning_path(course_id=COURSE_UK1, user=None, skip_locales=True):
parent=course_page,
)
- TopicFactory(title="Kickoff", is_visible=False, parent=lp)
+ TopicFactory(title="üK1", is_visible=True, parent=lp)
create_uk_circle(lp, title="Kickoff")
-
- TopicFactory(title="Basis", is_visible=False, parent=lp)
create_uk_circle(lp, title="Basis")
-
- TopicFactory(title="Fahrzeug", is_visible=False, parent=lp)
create_uk_circle(lp, title="Fahrzeug")
-
- TopicFactory(title="Haushalt Teil 1", is_visible=False, parent=lp)
create_uk_circle(lp, title="Haushalt Teil 1")
-
- TopicFactory(title="Haushalt Teil 2", is_visible=False, parent=lp)
create_uk_circle(lp, title="Haushalt Teil 2")
- # circle_analyse = create_circle("Betreuen", lp)
- # create_circle_children(circle_analyse, "Betreuen")
- #
- # TopicFactory(title="Prüfung", is_visible=True, parent=lp)
- # circle_analyse = create_circle("Prüfungsvorbereitung", lp)
- # create_circle_children(circle_analyse, "Prüfungsvorbereitung")
+ TopicFactory(title="üK2", is_visible=True, parent=lp)
+ create_uk_circle(lp, title="Reisen & Rechtsstreitigkeiten")
+ create_uk_circle(lp, title="Wohneigentum")
+ create_uk_circle(lp, title="KMU Teil 1")
+ create_uk_circle(lp, title="KMU Teil 2")
+ create_uk_circle(lp, title="3-Säulenkonzept")
+ create_uk_circle(lp, title="Einkommenssicherung (Invalidität)")
+ create_uk_circle(lp, title="Einkommenssicherung (Todesfall)")
+ create_uk_circle(lp, title="Pensionierung (Todesfall)")
+ TopicFactory(title="üK3", is_visible=True, parent=lp)
+ create_uk_circle(lp, title="Gesundheit")
+ create_uk_circle(lp, title="Prüfungsvorbereitung Teil 1")
+ create_uk_circle(lp, title="Prüfungsvorbereitung Teil 2")
# locales
if not skip_locales:
@@ -81,6 +77,62 @@ def create_uk_learning_path(course_id=COURSE_UK1, user=None, skip_locales=True):
Page.objects.update(owner=user)
+def create_uk_fr_learning_path(course_id=COURSE_UK_FR, user=None, skip_locales=True):
+ if user is None:
+ user = User.objects.get(username="info@iterativ.ch")
+
+ site = Site.objects.filter(is_default_site=True).first()
+
+ if not site:
+ site = wagtail_factories.SiteFactory(is_default_site=True)
+
+ if settings.APP_ENVIRONMENT == "development":
+ site.port = 8000
+ site.save()
+
+ course_page = CoursePage.objects.get(course_id=course_id)
+ lp = LearningPathFactory(
+ title="Lernpfad",
+ parent=course_page,
+ )
+
+ TopicFactory(title="1ère année", is_visible=True, parent=lp)
+ create_uk_circle(lp, title="Coup d'envoi")
+ create_uk_circle(lp, title="Base")
+ create_uk_circle(lp, title="Véhicule")
+ create_uk_circle(lp, title="Budget Partie 1")
+ create_uk_circle(lp, title="Budget Partie 2")
+
+ TopicFactory(title="2ème année", is_visible=True, parent=lp)
+ create_uk_circle(lp, title="Voyages / Protection juridique")
+ create_uk_circle(lp, title="Proprieté du logement")
+ create_uk_circle(lp, title="PME Partie 1")
+ create_uk_circle(lp, title="PME Partie 2")
+ create_uk_circle(lp, title="Concept des 3")
+ create_uk_circle(lp, title="Garantie des revenus, Partie 1")
+ create_uk_circle(lp, title="Garantie des revenus, Partie 2")
+ create_uk_circle(lp, title="Retraite")
+ TopicFactory(title="3ème année", is_visible=True, parent=lp)
+ create_uk_circle(lp, title="Santé")
+ create_uk_circle(lp, title="Préparation à l'examen, Partie 1")
+ create_uk_circle(lp, title="Préparation à l'examen, Partie 2")
+ # locales
+ if not skip_locales:
+ locale_de = Locale.objects.get(language_code="de-CH")
+ locale_fr, _ = Locale.objects.get_or_create(language_code="fr-CH")
+ LocaleSynchronization.objects.get_or_create(
+ locale_id=locale_fr.id, sync_from_id=locale_de.id
+ )
+ locale_it, _ = Locale.objects.get_or_create(language_code="it-CH")
+ LocaleSynchronization.objects.get_or_create(
+ locale_id=locale_it.id, sync_from_id=locale_de.id
+ )
+ call_command("sync_locale_trees")
+
+ # all pages belong to 'admin' by default
+ Page.objects.update(owner=user)
+
+
def create_uk_circle(lp, title="Kickoff"):
circle = CircleFactory(
title=title,
diff --git a/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py b/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py
index b8378e75..de82a091 100644
--- a/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py
+++ b/server/vbv_lernwelt/learnpath/create_vv_new_learning_path.py
@@ -61,15 +61,11 @@ def create_vv_new_learning_path(
create_circle_standard(
lp, title="Haushalt", lc_title="Rafael und Claudia ziehen zusammen"
)
- create_circle_standard_small(
- lp,
- title="Rechtsstreitigkeiten",
- lc_title="Rafael Fasel hat Ärger mit seinem Vermieter",
- )
+
+ create_circle_rechtsstreitigkeiten(lp)
create_circle_reisen(lp)
- create_circle_standard(
- lp, title="Einkommenssicherung", lc_title="Patrizia und Marco sichern sich ab"
- )
+ create_circle_einkommenssicherung(lp)
+
create_circle_standard_small(
lp,
title="Wohneigentum",
@@ -100,17 +96,17 @@ def create_vv_new_learning_path(
lc_title="Familie Babic spart auf ein Ziel",
lu_title="Sparinstrumente, Idee und Funktionsweise von Anlagefonds",
)
- create_circle_standard(
- lp,
- title="KMU",
- lc_title="Anne Fleur übernimmt den Blumenladen",
- )
create_circle_standard_small(
lp,
title="Selbstständigkeit",
lc_title="Patrizia macht sich selbstständig",
lu_title="Selbsständigerwerbende versichern",
)
+ create_circle_standard(
+ lp,
+ title="KMU",
+ lc_title="Anne Fleur übernimmt den Blumenladen",
+ )
TopicFactory(title="Prüfung", parent=lp)
create_circle_pruefungsvorbereitung(lp)
@@ -386,7 +382,7 @@ def create_circle_fahrzeug(lp, title="Fahrzeug"):
parent=circle,
category_name="Fahrzeug",
competence_id="V1",
- wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-einstieg-xapi-yXLHE5Xo/index.html",
+ wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-einstieg-xapi-L3QlrrCt/index.html",
learning_unit_title="Gesprächsvorbereitung und -einstieg",
)
@@ -396,7 +392,7 @@ def create_circle_fahrzeug(lp, title="Fahrzeug"):
parent=circle,
category_name="Fahrzeug",
competence_id="V2",
- wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-analyse-xapi-SmrKAa0J/index.html",
+ wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-analyse-xapi-YtviM_SF/index.html",
learning_unit_title="Bedarfsanalyse, Ist- und Soll-Situation",
)
@@ -406,7 +402,7 @@ def create_circle_fahrzeug(lp, title="Fahrzeug"):
parent=circle,
category_name="Fahrzeug",
competence_id="V3",
- wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-losung-xapi-3rzf8ySd/index.html",
+ wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-losung-xapi-jTdlOjOF/index.html",
learning_unit_title="Lösungsvorschlag erarbeiten und präsentieren",
)
@@ -416,7 +412,7 @@ def create_circle_fahrzeug(lp, title="Fahrzeug"):
parent=circle,
category_name="Fahrzeug",
competence_id="V4",
- wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-abschluss-xapi-WbFBv-4y/index.html",
+ wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-wechselt-sein-auto-abschluss-xapi-OBWheB8q/index.html",
learning_unit_title="Gesprächszusammenfassung, Abschluss und Nachbereitung",
)
@@ -450,6 +446,46 @@ def create_circle_fahrzeug(lp, title="Fahrzeug"):
)
+def create_circle_rechtsstreitigkeiten(lp, title="Rechtsstreitigkeiten"):
+ circle = CircleFactory(
+ title=title,
+ parent=lp,
+ )
+ LearningSequenceFactory(title="Einführung", parent=circle, icon="it-icon-ls-start")
+ LearningUnitFactory(title="Einführung", parent=circle)
+ LearningContentFactory(
+ title="Verschaff dir einen Überblick",
+ parent=circle,
+ contents=[
+ (
+ "video",
+ VideoBlockFactory(
+ url="https://player.vimeo.com/video/772512710?h=30f912f15a",
+ description="Willkommen im Lehrgang Versicherungsvermitler VBV",
+ ),
+ )
+ ],
+ )
+ LearningContentFactory(
+ title="Mediathek",
+ parent=circle,
+ )
+
+ LearningSequenceFactory(
+ title="Rechtsstreitigkeiten", parent=circle, icon="it-icon-ls-watch"
+ )
+ create_standard_learning_unit(
+ "Rafael Fasel hat Ärger mit seinem Vermieter",
+ parent=circle,
+ category_name=title,
+ competence_id="V1",
+ wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/rafael-fasel-hat-arger-mit-seinem-vermieter-xapi-qI-Aqj8O/index.html",
+ learning_unit_title="Terminvereinbarung, Vorbereitung und Gesprächseröffnung",
+ )
+
+ create_learning_sequence_transfer(circle, title="Rechtsstreitigkeiten")
+
+
def create_circle_reisen(lp, title="Reisen"):
circle = CircleFactory(
title=title,
@@ -481,7 +517,7 @@ def create_circle_reisen(lp, title="Reisen"):
parent=circle,
category_name=title,
competence_id="V1",
- wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-einstieg-xapi-_BfVBK8d/index.html",
+ wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-einstieg-xapi-UnxjqXFB/index.html",
learning_unit_title="Terminvereinbarung, Vorbereitung und Gesprächseröffnung",
)
@@ -491,7 +527,7 @@ def create_circle_reisen(lp, title="Reisen"):
parent=circle,
category_name=title,
competence_id="V2",
- wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-analyse-xapi-mtXA4uBz/index.html",
+ wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-analyse-xapi-FZoZOP9y/index.html",
learning_unit_title="Bedarfsanalyse, Ist- und Soll-Situation",
)
@@ -501,7 +537,7 @@ def create_circle_reisen(lp, title="Reisen"):
parent=circle,
category_name=title,
competence_id="V3",
- wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-losung-xapi-hn7GwUTn/index.html",
+ wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-losung-xapi-DK5AsZ5M/index.html",
learning_unit_title="Lösungsvorschlag erarbeiten und präsentieren",
)
@@ -511,7 +547,7 @@ def create_circle_reisen(lp, title="Reisen"):
parent=circle,
category_name=title,
competence_id="V4",
- wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-abschluss-xapi-Qv4X-yua/index.html",
+ wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/emma-und-ayla-campen-durch-amerika-abschluss-xapi--4t5XKAf/index.html",
learning_unit_title="Gesprächszusammenfassung, Abschluss und Nachbereitung",
)
@@ -520,6 +556,76 @@ def create_circle_reisen(lp, title="Reisen"):
)
+def create_circle_einkommenssicherung(lp, title="Einkommenssicherung"):
+ circle = CircleFactory(
+ title=title,
+ parent=lp,
+ )
+ LearningSequenceFactory(title="Einführung", parent=circle, icon="it-icon-ls-start")
+ LearningUnitFactory(title="Einführung", parent=circle)
+ LearningContentFactory(
+ title="Verschaff dir einen Überblick",
+ parent=circle,
+ contents=[
+ (
+ "video",
+ VideoBlockFactory(
+ url="https://player.vimeo.com/video/772512710?h=30f912f15a",
+ description="Willkommen im Lehrgang Versicherungsvermitler VBV",
+ ),
+ )
+ ],
+ )
+ LearningContentFactory(
+ title="Mediathek",
+ parent=circle,
+ )
+
+ LearningSequenceFactory(title="Einstieg", parent=circle, icon="it-icon-ls-watch")
+ create_standard_learning_unit(
+ "Patrizia und Marco sichern sich ab - Einstieg",
+ parent=circle,
+ category_name=title,
+ competence_id="V1",
+ wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/patrizia-marco-sichern-sich-ab-einstieg-xapi-jkH97GgC/index.html",
+ learning_unit_title="Terminvereinbarung, Vorbereitung und Gesprächseröffnung",
+ )
+
+ LearningSequenceFactory(title="Analyse", parent=circle, icon="it-icon-ls-watch")
+ create_standard_learning_unit(
+ "Patrizia und Marco sichern sich ab - Analyse",
+ parent=circle,
+ category_name=title,
+ competence_id="V2",
+ wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/patrizia-marco-sichern-sich-ab-analyse-xapi-MGcooCtm/index.html",
+ learning_unit_title="Bedarfsanalyse, Ist- und Soll-Situation",
+ )
+
+ LearningSequenceFactory(title="Lösung", parent=circle, icon="it-icon-ls-watch")
+ create_standard_learning_unit(
+ "Patrizia und Marco sichern sich ab - Lösung",
+ parent=circle,
+ category_name=title,
+ competence_id="V3",
+ wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/patrizia-marco-sichern-sich-ab-losung-xapi-nzoOdKIE/index.html",
+ learning_unit_title="Lösungsvorschlag erarbeiten und präsentieren",
+ )
+
+ LearningSequenceFactory(title="Abschluss", parent=circle, icon="it-icon-ls-watch")
+ create_standard_learning_unit(
+ "Patrizia und Marco sichern sich ab - Abschluss",
+ parent=circle,
+ category_name=title,
+ competence_id="V4",
+ wbt_url="https://s3.eu-central-1.amazonaws.com/myvbv-wbt.iterativ.ch/patrizia-marco-sichern-sich-ab-abschluss-xapi--lXKe6xt/index.html",
+ learning_unit_title="Gesprächszusammenfassung, Abschluss und Nachbereitung",
+ )
+
+ create_learning_sequence_transfer(
+ circle, title="Einkommenssicherung", lc_praxis_title="Heirat: Was ändert sich?"
+ )
+
+
def create_circle_standard_small(
lp, title, lc_title, lu_title=None, lc_praxis_title=None
):
diff --git a/server/vbv_lernwelt/static/icons/icon-globe.svg b/server/vbv_lernwelt/static/icons/icon-globe.svg
new file mode 100644
index 00000000..0cdad129
--- /dev/null
+++ b/server/vbv_lernwelt/static/icons/icon-globe.svg
@@ -0,0 +1,3 @@
+
+
+