diff --git a/CHANGELOG.md b/CHANGELOG.md index 57676fc10..21d843eee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,183 @@ All notable changes to this project will be documented in this file. See [standa * **Makefile:** add missing dependency on well-known for backend-builds ([a09dc59](https://fraport@dev.azure.com/fraport/Fahrerausbildung/_git/FRADrive/commit/a09dc59f260843f8815c382576bb5254d21104bf)) * **frontend:** fixed icon colour in table headers ([4c4571d](https://fraport@dev.azure.com/fraport/Fahrerausbildung/_git/FRADrive/commit/4c4571d2d0879e89f2572eba6015d34a7f4794c8)) * **doc:** minor haddock problems ([d4f8a6c](https://fraport@dev.azure.com/fraport/Fahrerausbildung/_git/FRADrive/commit/d4f8a6c77b2a4a4540935f7f0beca0d0605508c8)) +## [27.4.76](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.75...v27.4.76) (2024-08-08) + + +### Bug Fixes + +* **ap:** disambiguate action message ([8b0466e](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/8b0466e74e36e1d0d07518fd317d46b00ab53eff)) +* **avs:** fix [#173](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/173) by not using firm superior email as display email ([43f5c5f](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/43f5c5f4854d1ab2af27b479e72a58e2818a5696)) +* **avs:** towards [#117](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/117) update if current value is Nothing even if oldval == newval ([d1fa01f](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d1fa01fcc5125c4adee8849f9c944884926f78ad)) +* **avs:** using firm superior as UserEmail is a no-go due to uniqueness constraints ([507a7e0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/507a7e02fc68476d01031dc9f9ee1a669a453ed1)) +* **build:** linter likes it ([f929e03](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/f929e03129378e08c8a08ed4bd6f8e8716401813)) +* **course:** fix [#150](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/150) course edit for associated qualifications requires school admin or lecturer rights ([5b6e4e6](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5b6e4e60e7d2957fbce93ee2e2d6d3464b4e3db7)) +* **course:** fix [#148](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/148) course qualification ordering ([cfd2534](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/cfd25348ad3b63ac6bc5031467a3c4ead2e07eed)), closes [#150](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/150) +* **course:** fix [#149](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/149) course cloning proposes associated qualifications ([e141976](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/e1419766f3a06f702abad0ea42f6552305504ba0)) +* **course:** fix [#150](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/150) no longer allow duplicated associated qualifications and orders due to editing existing ([ec02767](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ec027675525b30198378745ed281f60a42471807)) +* **course:** WIP course cloning should propose same associated qualifications, towards [#149](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/149) ([bc47387](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/bc47387c91dda60a2f12e52dba28ea7b079316f0)) +* **lms:** max e-learning tries default removed and info added to lms overview ([11fdcf0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/11fdcf0d445b8cfe97c3a3c26513a9229937c536)) +* **user:** format userDisplayNames having umlaut substitutes with respect to userSurname correctly ([e35a5e9](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/e35a5e99a6cea0976fd1c28f919e7d0ac0338503)) + +## [27.4.75](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.74...v27.4.75) (2024-07-12) + + +### Bug Fixes + +* **build:** make linter happy again ([c17c18f](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/c17c18f9247ef322bc051602a3cb4a52cd50affa)) +* **build:** minor ([ab28c8c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ab28c8c2437680023d80e6ab43113d4328b3a151)) +* **firm:** fix [#157](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/157) by removing redundant duplicated code in firm user and supervision handling ([28e2739](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/28e2739e515700d15c75647c0efe2fe9a9cf15b1)) +* **job:** change some queueJob' to queueJob instead ([fa0541a](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/fa0541aa4eaf10f98535a0959593b148b8346109)) +* **lms:** allow 2nd reminders to be independent of renewal period ([d853e85](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d853e8559b753865ee818bf24764f5c8d2e2303f)) +* **lms:** move lms reuse info from QualificationR to LmsR ([468af9d](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/468af9de9da44a8ad685ca4bb6890a3e630b58be)) +* **lms:** send second reminder indepentently from renewal period ([a97c3a5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/a97c3a5c9d3cb9dddf90f561712f0845400893bd)) +* **nix:** workaround parsing port numbers failed in nix-shell ([b5215cc](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/b5215cc7e8df3a7ad636271c8e6950979b2b8e42)) +* **users:** nameHtml no longer complains about differing case for surname and displayname ([a1668f8](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/a1668f891a36b887439afb098f016ef22535af42)) +* **users:** remove users with company post address from list of unreachable users ([c813c66](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/c813c665ed306135b7813d91d23310341c689f41)) + +## [27.4.74](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.73...v27.4.74) (2024-07-04) + + +### Bug Fixes + +* **lms:** fix [#161](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/161) lms for multiple joint qualifications ([f869a82](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/f869a829d2c1a726930864b3af62d1f0fbebe955)) + +## [27.4.73](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.72...v27.4.73) (2024-07-03) + + +### Bug Fixes + +* **letter:** rephrase some minor letter parts ([0ac75e0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/0ac75e0d5948cb90855d0e36ca8e99c22a0f6fcb)) + +## [27.4.72](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.71...v27.4.72) (2024-07-02) + + +### Bug Fixes + +* **avs:** do not associate users by AvsInfoPersonEmail ([9e2f221](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/9e2f2214ce5c7ee1e8d80e6fa75298b7a70d9043)) +* **avs:** fix superfluous quotes for matriculation numbers on newly created users ([ff9014c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ff9014ce05d197c1dc0fce0774a640789cb38b26)) +* **avs:** towards [#169](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/169) - superiors are elevated to max priority for that company ([5bf8539](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5bf85394d4db6de8f10b4e318d667130d37601ac)) +* **firm:** supervisor secondary did not work as intended ([d4f3ce7](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d4f3ce7bf3d208b16f95ab81971b47dfa752939a)) + +## [27.4.71](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.70...v27.4.71) (2024-06-27) + + +### Bug Fixes + +* **avs:** company superior emails become company wide supervisors ([37efc89](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/37efc89e0723452e6d271ba5b43d6bd026642190)) +* **avs:** match mobile number better between LDAP and AVS ([f108c6c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/f108c6cfec2d94d866e7c1605b0abe5471fd0f2b)) +* **avs:** new AVS from existing LDAP user no longer misses fields ([2559346](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/2559346d963ede802321dfc8cbd2088d9a5de685)) +* **avs:** priority for picking primary email demote superior ([e4fa1dd](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/e4fa1ddd6873910bef82d569fe16aca936efc567)) +* **build:** add missing license file ([8721bdb](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/8721bdb3f349658baab144d64c19942bfd7fa49a)) +* **build:** hlint wants a newtype instead ([18cdc52](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/18cdc52df094b9dbccd4f015561367cea59e33fe)) +* **doc:** fix erroneous unintentional haddock annotations ([3dfc7f8](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/3dfc7f8c8b12dd6ef87848a75f1669d700fffe4c)) +* **i18n:** add missing translation for new primary company ([c212f2e](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/c212f2e8d735616e59c9b8111a34118e3a48fd47)) +* **i18n:** add missing translation for new primary company ([2cc529b](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/2cc529be39655c317ca028f8f09fa80826ec668d)) +* **ldap:** match mobile number better between LDAP and AVS ([47e5628](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/47e56280fce4ad37e6bc3b9f1c61cb7867069cc5)) +* **letter:** adjust spacing, pin location and interpolation ([d4a0e1f](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d4a0e1f201151f76e8e9afd67b456cc878d2afde)) +* **letter:** convenience links working again ([5f1af13](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5f1af130edae7ada2f0c7f7829890bbe0d4f395a)) +* **letter:** expiry and valid dates were wrong ([f8c3663](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/f8c36636ff1f2591507e993af32ed01af94cf1fc)) +* **letter:** switch markdown for renewal letter too ([c38e87e](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/c38e87e1e0e9285a10c00521b7440cd8246af88a)) +* **print:** fix [#167](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/167) by sotring affected user in PrintJob ([73aecc2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/73aecc2df833bdeed93a113b6c756e36b50491b7)) + +## [27.4.70](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.69...v27.4.70) (2024-06-21) + + +### Bug Fixes + +* **build:** hlint wants a newtype instead ([0766351](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/07663516e520814e26740d671325b7cd10855dd4)) + +## [27.4.69](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.68...v27.4.69) (2024-06-21) + + +### Bug Fixes + +* **avs:** fix type causing avs surname upate not working ([822c43c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/822c43c8a7db2086954ad187502ec2c4f1811d17)) +* **avs:** keep company on unchange address/email only if either is non-empty ([766b858](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/766b8589d6945df21fc6ce90d35a004655ffa471)) +* **avs:** synch job deletes used row instead of truncation ([d7acc7a](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d7acc7a2d0fe5fc18929a8cb2d9c9f8a259c9944)) + +## [27.4.68](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.67...v27.4.68) (2024-06-19) + + +### Bug Fixes + +* **letter:** minor ([2ae11dc](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/2ae11dc25c000486af9acc26439a0580f5c687f2)) + +## [27.4.67](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.66...v27.4.67) (2024-06-17) + + +### Bug Fixes + +* **avs:** fix rare avs update bug involving values optional in avs but compulsory in user entity ([a6d0105](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/a6d0105903caba0eb47715eeb217ea2c53d99e23)) + +## [27.4.66](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.65...v27.4.66) (2024-06-12) + + +### Bug Fixes + +* **avs:** fix [#164](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/164) by removing companyPersonalNumber and companyDepartment upon ldap sync expiry ([da74b95](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/da74b957295caefb010c90297af557f997b18e7c)) +* **avs:** fix [#165](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/165) by updating userCompanyDepartmen and userCompanyPersonalNumer ([76e0710](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/76e0710c7b54a40d2c236299ea4fabd009d3f35a)) +* **avs:** repeated avs sync enqueue no longe violates duplicate db uniqueness constraints ([996e6a0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/996e6a0ce563bda96638863efd40ce38fce8ac2b)) +* **avs:** update email on manual company switch ([9fd80f2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/9fd80f25526eefce217c659f6ea2991771c11ece)), closes [#164](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/164) + +## [27.4.65](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.64...v27.4.65) (2024-06-10) + + +### Bug Fixes + +* **avs:** company update no longer fails on duplicate key ([bb101de](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/bb101dee7b40cd3d8ba10a559af642396d5b87b5)) +* **avs:** profile page correctly indicates automatic email and postal addresses ([e553ad4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/e553ad4358a71fc96fa946533f0441d4af5202c9)) +* **avs:** steps towards [#164](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/164) ([aa1d230](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/aa1d230e497f0e59dbea9f4fd5c7da773f5a4280)) +* **lette:** adjust window for new pin letters ([6acfd84](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/6acfd849aeb473a018f7a9c34e69f61b3c22b6f8)) + +## [27.4.64](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.63...v27.4.64) (2024-05-27) + +## [27.4.63](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.62...v27.4.63) (2024-05-23) + + +### Bug Fixes + +* **avs:** company update checks uniques and ignores those updates if necessary ([9451d90](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/9451d90a9e00d08a2a7d169c4674d99ff1018ee9)) + +## [27.4.62](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.61...v27.4.62) (2024-05-19) + + +### Bug Fixes + +* **avs:** avs update on company shorthands working now ([ff2347b](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ff2347b1c950c7a2bb281cdcd07a52925e23b9f0)) +* **avs:** deal gracefully with empty card status results ([ccf9340](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ccf934044938277d821eb4b9ea08a8a134e84189)) + +## [27.4.61](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.60...v27.4.61) (2024-05-06) + + +### Bug Fixes + +* **avs:** fix [#76](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/76) allowing company changes and fix [#69](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/69) ([3c4a0b8](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/3c4a0b86c1e3d8a28405ab73b964ba1b988d2822)) +* **build:** add missing tex packages ([6750798](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/6750798920dc76882f4e8ef39b47018fb7b77e44)) +* **build:** workaround non modal form result handler ([2fbd281](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/2fbd28154cd7aea282eaa2604a42263ac90e3b1e)) + +## [27.4.60](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.59...v27.4.60) (2024-04-26) + + +### Bug Fixes + +* **avs:** disable caching by 0s no longer causes an exception ([d578e80](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d578e80282c8bf6872fa6040514a9d2c85582707)) +* **avs:** fix [#152](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/152) by providing new online avs card filter throughout ([ad2375b](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ad2375b338866f37c8b7825a9eab12fa6c9abccb)) +* **avs:** fix [#36](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/36) and remove dead code ([4f8850b](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/4f8850b3b4f710f9cf59163175b27599c97ac5c0)) +* **avs:** fix [#69](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/69) by redesigning live avs status page ([697979c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/697979c277ce7198f4573d6cea30373a1fcc17da)) +* **avs:** invalidate contact cache after licence writes ([c382be9](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/c382be9325fcc92e13cb5dc2ad7c20b198db26fc)) +* **avs:** several minor bugfixes ([a52c8a6](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/a52c8a6ad709029a8822d383370b0d2bdd25e7d7)), closes [#158](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/158) +* **build:** add import needed for production only ([724e4a0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/724e4a0bec343ab9c6d172d8e93b8040bbe3fe7d)) +* **build:** migration needs to check for table existens first ([f439ea4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/f439ea45af9b1c4a029fc1b9b6383f3c97194ed0)) +* **build:** minor error non-development code ([66eaa4f](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/66eaa4f7dcc124b631414d4a1adbe555a4029100)) +* **build:** missing parameters added ([83afdf7](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/83afdf760f93fc1a553de3a122b444412ed84ba4)) +* **build:** simple type error ([d56a1cd](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d56a1cdd46259418faa737b9bb0a9d9ffba442e0)) +* **build:** type error in test db fill data ([f465cc9](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/f465cc972367233a4944dd0aeb81b223a187bb85)) +* **doc:** minor haddock problems ([d4f8a6c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d4f8a6c77b2a4a4540935f7f0beca0d0605508c8)) +* **firm:** supervisor filter acts weird in test environment ([b566e59](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/b566e59eb1325485fe26dc4f0b5cb63165c58f74)) +* **i18n:** fix some bad plurals ([890f8ad](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/890f8ad8b60115533faa6b99f4c4504243cbfb1d)) +* **lint:** remove minor superfluous dollar ([64a1233](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/64a123387f3539b73649d02a6ecd97de577097e6)) +* **qualification:** fix [#159](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/159) by removing an misleadingly named column for user qualification table ([fd6a538](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/fd6a5384d3517958a3c7726e32eed3bad197a591)) ## [27.4.59](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v27.4.58...v27.4.59) (2024-02-13) diff --git a/config/develop-settings.yml b/config/develop-settings.yml new file mode 100644 index 000000000..054a7dfd4 --- /dev/null +++ b/config/develop-settings.yml @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2024 Steffen Jost +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +# Values formatted like "_env:ENV_VAR_NAME:default_value" can be overridden by the specified environment variable. +# See https://github.com/yesodweb/yesod/wiki/Configuration#overriding-configuration-values-with-environment-variables +# NB: If you need a numeric value (e.g. 123) to parse as a String, wrap it in single quotes (e.g. "_env:PGPASS:'123'") +# See https://github.com/yesodweb/yesod/wiki/Configuration#parsing-numeric-values-as-strings + + +#DEVELOPMENT ONLY, NOT TO BE USED IN PRODUCTION + +avs-licence-synch: + times: [12] + level: 4 + reason-filter: "(firm|block)" + max-changes: 999 + +# Enqueue at specified hour, a few minutes later +job-lms-qualifications-enqueue-hour: 16 +job-lms-qualifications-dequeue-hour: 4 diff --git a/config/settings.yml b/config/settings.yml index 043905fbb..36e0d8576 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -83,6 +83,7 @@ health-check-matching-cluster-config-timeout: "_env:HEALTHCHECK_MATCHING_CLUSTER synchronise-ldap-users-within: "_env:SYNCHRONISE_LDAP_WITHIN:1209600" # 14 Tage in Sekunden synchronise-ldap-users-interval: "_env:SYNCHRONISE_LDAP_INTERVAL:3600" # jede Stunde +synchronise-ldap-users-expire: "_env:SYNCHRONISE_LDAP_EXPIRE:15897600" # halbes Jahr in Sekunden synchronise-avs-users-within: "_env:SYNCHRONISE_AVS_WITHIN:5702400" # alle 66 Tage synchronise-avs-users-interval: "_env:SYNCHRONISE_AVS_INTERVAL:21600" # alle 6 Stunden @@ -90,10 +91,6 @@ synchronise-avs-users-interval: "_env:SYNCHRONISE_AVS_INTERVAL:21600" # alle 6 study-features-recache-relevance-within: 172800 study-features-recache-relevance-interval: 293 -# Enqueue at specified hour, a few minutes later -# job-lms-qualifications-enqueue-hour: 15 -# job-lms-qualifications-dequeue-hour: 3 - log-settings: detailed: "_env:DETAILED_LOGGING:false" all: "_env:LOG_ALL:false" @@ -157,10 +154,12 @@ lms-direct: deletion-days: "_env:LMSDELETIONDAYS:7" avs: - host: "_env:AVSHOST:skytest.fra.fraport.de" - port: "_env:AVSPORT:443" - user: "_env:AVSUSER:fradrive" - pass: "_env:AVSPASS:" + host: "_env:AVSHOST:skytest.fra.fraport.de" + port: "_env:AVSPORT:443" + user: "_env:AVSUSER:fradrive" + pass: "_env:AVSPASS:\"0000\"" + timeout: "_env:AVSTIMEOUT:42" + cache-expiry: "_env:AVSCACHEEXPIRY:420" lpr: host: "_env:LPRHOST:fravm017173.fra.fraport.de" @@ -207,9 +206,6 @@ memcached: timeout: "_env:MEMCACHED_TIMEOUT:20" expiration: "_env:MEMCACHED_EXPIRATION:300" memcache-auth: true -memcached-local: - maximum-ghost: 512 - maximum-weight: 104857600 # 100MiB upload-cache: host: "_env:UPLOAD_S3_HOST:localhost" # should be optional, but all file transfers will be empty without an S3 cache @@ -278,8 +274,8 @@ user-defaults: max-favourites: 0 max-favourite-terms: 2 theme: Default - date-time-format: "%d %b %y %R" - date-format: "%d %b %Y" + date-time-format: "%d.%m.%Y %R" + date-format: "%d.%m.%y" time-format: "%R" download-files: false warning-days: 1209600 @@ -321,17 +317,6 @@ fallback-personalised-sheet-files-keys-expire: 2419200 download-token-expire: 604801 -file-source-arc: - maximum-ghost: 512 - maximum-weight: 1073741824 # 1GiB -file-source-prewarm: - maximum-weight: 1073741824 # 1GiB - start: 1800 # 30m - end: 600 # 10m - inhibit: 3600 # 60m - steps: 20 - max-speedup: 3 - bot-mitigations: - only-logged-in-table-sorting - unauthorized-form-honeypots diff --git a/fixtest.sh b/fixtest.sh new file mode 100755 index 000000000..d59f51144 --- /dev/null +++ b/fixtest.sh @@ -0,0 +1,6 @@ +if [[ ! -d .stack-work-test ]]; then + mv -vT .stack-work .stack-work-test + [[ -d .stack-work-build ]] && mv -vT .stack-work-build .stack-work +else + echo "Directory .stack-work-test exists already." +fi diff --git a/load/Load.hs b/load/Load.hs index 843127132..fd1c47886 100644 --- a/load/Load.hs +++ b/load/Load.hs @@ -96,7 +96,7 @@ sampleIntegral = sampleN scaleIntegral instance PathPiece DiffTime where toPathPiece = (toPathPiece :: Pico -> Text) . MkFixed . diffTimeToPicoseconds fromPathPiece t = fromPathPiece t <&> \(MkFixed ps :: Pico) -> picosecondsToDiffTime ps - + data LoadSimulation = LoadSheetDownload @@ -214,13 +214,13 @@ runSimulation sim = do delays <- replicateM (fromIntegral p) $ do d <- view $ _2 . _simDelay sampleNDiffTime d - + forConcurrently_ ([1..p] `zip` sort delays) $ \(n, d') -> do begin <- liftIO getCurrentTime dur <- view $ _2 . _simDuration tDuration <- sampleNDiffTime dur - + let MkFixed us = realToFrac d' :: Micro threadDelay $ fromInteger us start <- liftIO getCurrentTime @@ -268,7 +268,7 @@ runSimulation' LoadSheetSubmission = do -- Just formData <- return . getFormData FIDsubmission $ resp ^. responseBody -- Just addButtonData <- return . flip (runFormScraper FIDsubmission) (resp ^. responseBody) $ do -- let btnSel = "button" Scalpel.@: [Scalpel.hasClass "btn-mass-input-add"] - + -- name <- Scalpel.attr "name" btnSel -- value <- Scalpel.attr "value" btnSel -- guard $ value == "add__0__0" @@ -305,7 +305,7 @@ runSimulation' LoadSheetSubmission = do procEnd <- join $ asks runtime print ("proc", procEnd - procStart) - + resp3 <- liftIO . httpRetry $ Session.post session (uriToString id formURI mempty) subData void . evaluate $! resp3 where @@ -328,11 +328,11 @@ runSimulation' LoadSheetSubmission = do -> m () logRetry shouldRetry err status = liftIO . putStrLn . pack $ Retry.defaultLogMsg shouldRetry err status - + -- runSimulation' other = terror $ "Not implemented: " <> tshow other runFormScraper :: FormIdentifier -> Scalpel.Scraper Lazy.ByteString a -> Lazy.ByteString -> Maybe a -runFormScraper fid innerS = fmap join . flip Scalpel.scrapeStringLike $ +runFormScraper fid innerS = fmap join . flip Scalpel.scrapeStringLike $ fmap listToMaybe . Scalpel.chroots "form" $ do fid' <- Scalpel.attr "value" $ "input" Scalpel.@: ["name" Scalpel.@= "form-identifier"] guard $ fid' == encodeUtf8 (fromStrict $ toPathPiece fid) @@ -341,11 +341,11 @@ runFormScraper fid innerS = fmap join . flip Scalpel.scrapeStringLike $ getFormData :: FormIdentifier -> Lazy.ByteString -> Maybe [FormParam] getFormData = flip runFormScraper $ - Scalpel.chroots ("input") $ do + Scalpel.chroots "input" $ do name <- Scalpel.attr "name" Scalpel.anySelector value <- Scalpel.attr "value" Scalpel.anySelector <|> pure "" return $ toStrict name := value - + newLoadSession :: ReaderT SimulationContext IO Session newLoadSession = do @@ -354,7 +354,7 @@ newLoadSession = do let withToken = case loadToken of Nothing -> id Just (Jwt bs) -> (:) (hAuthorization, "Bearer " <> bs) . filter ((/= hAuthorization) . fst) - + liftIO . Session.newSessionControl (Just mempty) $ tlsManagerSettings { managerModifyRequest = \req -> return $ req { requestHeaders = withToken $ requestHeaders req } diff --git a/messages/uniworx/categories/admin/de-de-formal.msg b/messages/uniworx/categories/admin/de-de-formal.msg index eb6cfe753..a80ceead2 100644 --- a/messages/uniworx/categories/admin/de-de-formal.msg +++ b/messages/uniworx/categories/admin/de-de-formal.msg @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Gregor Kleen ,Winnie Ros +# SPDX-FileCopyrightText: 2022-24 Gregor Kleen ,Winnie Ros ,Steffen Jost # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -67,6 +67,7 @@ BearerTokenExpiresTip: Wird der Ablaufzeitpunkt überschrieben und kein Ablaufze BearerTokenOverrideStart: Startzeitpunkt BearerTokenOverrideStartTip: Wird kein Startzeitpunkt angegeben, wird bei Verwendung des Tokens nur der Ablaufzeitpunkt überprüft. HeadingAdminTokens: Tokens ausstellen +UserUnknown: Unbekannter Benutzer:in #templates adminFeautures StudyFeaturesDegrees: Abschlüsse @@ -101,7 +102,7 @@ ProblemsHeadingDrivers: Fahrberechtigungen ProblemsHeadingNotifications: Benachrichtigungen ProblemsHeadingMisc: Allgemein ProblemsAvsProblem: Synchronisation mit AVS/MoBaKo komplett fehlgeschlagen -ProblemsDriverSynch n@Int: #{tshow n} Diskrepanzen zwischen AVS und FRADrive +ProblemsDriverSynch n@Int: #{n} #{pluralDE n "Diskrepanz" "Diskrepanzen"} zwischen AVS und FRADrive ProblemsDriverSynch0: Alle Sperrungen von Vorfeld-Fahrberechtigungen 'F' sind im AVS eingetragen ProblemsDriverSynch1down: Alle Sperrungen von Rollfeld-Fahrberechtigungen 'R' sind im AVS eingetragen ProblemsDriverSynch1up: Alle gültigen Vorfeld-Fahrberechtigungen 'F' sind im AVS eingetragen @@ -109,10 +110,11 @@ ProblemsDriverSynch2: Alle gültigen Rollfeld-Fahrberechtigungen 'R' sind im AVS ProblemsRDriversHaveFs: Alle Inhaber einer Rollfeld-Fahrberechtigung besitzen auch eine gültige Vorfeld-Fahrberechtigung ProblemsDriversHaveAvsIds: Alle Inhaber einer Fahrberechtigung konnten einer AVS Identifikationsnummer zugeordnet werden ProblemsUsersAreReachable: Für alle Benutzer ist eine E-Mail oder postalische Adresse bekannt -ProblemsNoStalePrintJobs n@Integer: Alle Briefversandaufträge der vergangenen #{show n} Tage wurden von der Druckerei bestätigt +ProblemsNoStalePrintJobs n@Integer: Alle Briefversandaufträge #{pluralDE n "des vergangenen Tages" ("der vergangenen "<> tshow n <> " Tage")} wurden von der Druckerei bestätigt ProblemsNoBadAPCIds: Alle kürzlich empfangenen Druckauftragsbestätigungen waren gültig ProblemsUnreachableHeading: Unerreichbare Benutzer ProblemsUnreachableBody: Benutzer ohne E-Mail oder Postadresse, welche z.B. bei ablaufenden Berechtigungen nicht benachrichtigt werden können: +ProblemsUnreachableButtons: Synchronisation für Unerreichbare starten ProblemsRWithoutFHeading: Fahrer mit R ohne F ProblemsRWithoutFBody: Diese Fahrer sind wegen einer ungültigen Vorfeld-Fahrberechtigung komplett gesperrt, obwohl eine gültige Rollfeld-Fahrberechtigung besteht: ProblemsNoAvsIdHeading: Fahrer ohne AVS-Id @@ -120,6 +122,24 @@ ProblemsNoAvsIdBody: Fahrer mit gültiger Fahrberechtigung in FRADrive, welche t ProblemsAvsSynchHeading: Synchronisation AVS Fahrberechtigungen ProblemsAvsErrorHeading: Fehlermeldungen ProblemsInterfaceSince: Berücksichtigt werden nur Erfolge und Fehler seit +ProblemAvsUsrHadR: Momentan gültiges R im AVS + +AdminProblemSolved: Erledigt +AdminProblemSolver: Bearbeitet von +AdminProblemCreated: Erkannt +AdminProblemInfo: Problembeschreibung +AdminProblemsSolved n@Int: #{pluralDEeN n "Admin Problem"} als erledigt markiert +AdminProblemsReopened n@Int: #{pluralDEeN n "Admin Problem"} erneut eröffnet +AdminProblemNewCompany: Neue Firma über AVS automatisch erstellt; prüfen und ggf. Standardansprechpartner eintragen +AdminProblemSupervisorNewCompany b@Bool: Standardansprechpartner #{boolText mempty "mit Standardumleitung" b} wechselte zu neuer Firma +AdminProblemSupervisorLeftCompany b@Bool: Einziger Standardansprechpartner #{boolText mempty "mit Standardumleitung" b} dieses Fahrers wechselte zu neuer Firma +AdminProblemCompanySuperiorChange: Neuer firmenweiter Vorgesetzter. +AdminProblemCompanySuperiorNotFound t@Text: Neuer unbekannter firmenweiter Vorgesetzter mit E-Mail #{t}, keine Ansprechpartnerbeziehungen eingerichtet. +AdminProblemCompanySuperiorPrevious: Ehemaliger Vorgesetzter: +AdminProblemNewlyUnsupervised: Fahrer hat keinen Firmenansprechpartner mehr nach AVS Firmenwechsel zu Firma +AdminProblemUser: Betroffener +ProblemTableMarkSolved: Als erledigt markieren +ProblemTableMarkUnsolved: Erledigt Markierung löschen InterfacesOk: Schnittstellen sind ok. InterfacesFail n@Int: #{pluralDEeN n "Schnittstellenproblem"}! @@ -130,4 +150,13 @@ InterfaceSubtype: Betreffend InterfaceWrite: Schreibend InterfaceSuccess: Rückmeldung InterfaceInfo: Nachricht -InterfaceFreshness: Prüfungszeitraum (h) \ No newline at end of file +InterfaceFreshness: Maximale Zugriffsfrist +InterfaceFreshnessTooltip: Zeitspanne innerhalb der ein erneuter erfolgreicher Schnittstellenzugriff erfolgen muss, ohne Warnungen auszulösen +ConfigInterfacesHeading: Konfiguration Zugriffsfristen + +IWTActAdd: Hinzufügen/Ändern +IWTActDelete: Entfernen +InterfaceWarningAdded: Schnittstellenwarnungszeit hinzugefügt oder geändert +InterfaceWarningDeleted n@Int: #{pluralDEeN n "Schnittstellenwarnungszeit"} gelöscht +InterfaceWarningDisabledEntirely: Alle Fehler ignorieren +InterfaceWarningDisabledInterval: Keine Zugriffsfrist \ No newline at end of file diff --git a/messages/uniworx/categories/admin/en-eu.msg b/messages/uniworx/categories/admin/en-eu.msg index 13f35ed9f..f69fda9e5 100644 --- a/messages/uniworx/categories/admin/en-eu.msg +++ b/messages/uniworx/categories/admin/en-eu.msg @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Sarah Vaupel ,Winnie Ros +# SPDX-FileCopyrightText: 2022-24 Sarah Vaupel ,Winnie Ros ,Steffen Jost # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -18,10 +18,10 @@ NoNameCandidatesInferred: No new name-mappings inferred AllNameIncidencesDeleted: Successfully deleted all name observations AllParentIncidencesDeleted: Successfully deleted all parent-relation observations AllStandaloneIncidencesDeleted: Successfully deleted all standalone observations -IncidencesDeleted n: Successfully deleted #{show n} #{pluralEN n "observation" "observations"} -RedundantParentCandidatesRemoved n: Successfully removed #{n} rendundant #{pluralEN n "parent-candidate" "parent-candidates"} -RedundantStandaloneCandidatesRemoved n: Successfully removed #{n} rendundant #{pluralEN n "standalone-candidate" "standalone-candidates"} -ParentCandidatesInferred n: Successfully inferred #{n} field #{pluralEN n "parent-relation" "parent-reliations"} +IncidencesDeleted n: Successfully deleted #{pluralENsN n "observation"} +RedundantParentCandidatesRemoved n: Successfully removed #{n} rendundant #{pluralENs n "parent-candidate"} +RedundantStandaloneCandidatesRemoved n: Successfully removed #{n} rendundant #{pluralENs n "standalone-candidate"} +ParentCandidatesInferred n: Successfully inferred #{n} field #{pluralENs n "parent-relation"} NoParentCandidatesInferred: No new parent-relations inferred StudyDegreeChangeSuccess: Successfully updated degrees StudyTermsShort: Field shorthand @@ -67,6 +67,7 @@ BearerTokenExpiresTip: If no expiration time is given, the token will not expire BearerTokenOverrideStart: Start time BearerTokenOverrideStartTip: If no start time is given, only the expiration time will be checked when the token is used. HeadingAdminTokens: Issue tokens +UserUnknown: User unknown #templates adminfeatures StudyFeaturesDegrees: Degrees @@ -101,7 +102,7 @@ ProblemsHeadingDrivers: Driving Licences ProblemsHeadingNotifications: User communication ProblemsHeadingMisc: Miscellaneous ProblemsAvsProblem: Synchronisation with AVS/MoBaKo failed entirely -ProblemsDriverSynch n: #{tshow n} mismatches between AVS and FRADrive +ProblemsDriverSynch n: #{tshow n} #{pluralEN n "mismatch" "mismatches"} between AVS and FRADrive ProblemsDriverSynch0: All revocations of apron driving licences 'F' were successfully registered with AVS ProblemsDriverSynch1down: All revocations of maneuvering area driving licences 'R' were successfully registered with AVS ProblemsDriverSynch1up: All valid apron driving licences 'F' were successfully registered with AVS @@ -109,17 +110,36 @@ ProblemsDriverSynch2: All valid maneuvering area driving licences 'R' were succe ProblemsRDriversHaveFs: All driving licence 'R' holders also have a valid 'F' licence ProblemsDriversHaveAvsIds: All driving licence holder could be matched with their AVS id ProblemsUsersAreReachable: Either Email or postal address is known for all users -ProblemsNoStalePrintJobs n: All requests for letter mailing within the last #{show n} days were acknowledged as printed by the airport printing center +ProblemsNoStalePrintJobs n: All requests for letter mailing within the last #{pluralENsN n "day"} were acknowledged as printed by the airport printing center ProblemsNoBadAPCIds: All recently received print job ids from Airport Print Center were legit ProblemsUnreachableHeading: Unreachable Users ProblemsUnreachableBody: Users without Email nor postal address, who thus cannot be notified about expiring qualifications: +ProblemsUnreachableButtons: Start synchronisation for unreachable users only ProblemsRWithoutFHeading: Drivers having 'R' but not 'F' ProblemsRWithoutFBody: Drivers without apron driving licence are prohibited from driving, even if they own a valid maneuvering driving licence: ProblemsNoAvsIdHeading: Drivers without AVS id ProblemsNoAvsIdBody: Drivers having a valid apron driving licence within FRADrive only, but who may not drive since a missing AVS id prevents communication of the driving licence to AVS: ProblemsAvsSynchHeading: Synchronisation AVS Driving Licences ProblemsAvsErrorHeading: Error Log -ProblemsInterfaceSince: Only considering successes and errors since +ProblemsInterfaceSince: Only considering successes and errors since +ProblemAvsUsrHadR: Currenlt R valid in AVS + +AdminProblemSolved: Done +AdminProblemSolver: Solved by +AdminProblemCreated: Recognized +AdminProblemInfo: Problem +AdminProblemsSolved n: #{pluralENsN n "admin problem"} marked as solved +AdminProblemsReopened n: #{pluralENsN n "admin problem"} reopened +AdminProblemNewCompany: New company from AVS; verify and add default supervisors +AdminProblemSupervisorNewCompany b: Default company supervisor #{boolText mempty "with reroute" b} changed to new company +AdminProblemSupervisorLeftCompany b: Only default company supervisor #{boolText mempty "with reroute" b} for this user changed to new company +AdminProblemCompanySuperiorChange: New company wide superior. +AdminProblemCompanySuperiorNotFound t: Unable to set supervision for new unknown company wide superior having Email #{t}. +AdminProblemCompanySuperiorPrevious: Previous superior: +AdminProblemNewlyUnsupervised: Driver has no longer a company default supervisor after AVS update at new company +AdminProblemUser: Affected +ProblemTableMarkSolved: Mark done +ProblemTableMarkUnsolved: Reopen as undone InterfacesOk: Interfaces are ok. InterfacesFail n: #{pluralENsN n "interface problem"}! @@ -130,4 +150,13 @@ InterfaceSubtype: Affecting InterfaceWrite: Write InterfaceSuccess: Returned InterfaceInfo: Message -InterfaceFreshness: Check hours \ No newline at end of file +InterfaceFreshness: Maximum usage period +InterfaceFreshnessTooltip: Time period within which the next successful interface access must occur to avoid a warning +ConfigInterfacesHeading: Configure interface usage warnings + +IWTActAdd: Add/Edit +IWTActDelete: Delete +InterfaceWarningAdded: Interface warning time added/changed +InterfaceWarningDeleted n: #{pluralENsN n "interface warning time"} deleted +InterfaceWarningDisabledEntirely: Ignore all errors +InterfaceWarningDisabledInterval: No maximum usage period \ No newline at end of file diff --git a/messages/uniworx/categories/avs/de-de-formal.msg b/messages/uniworx/categories/avs/de-de-formal.msg index bd5c01716..fd790cef2 100644 --- a/messages/uniworx/categories/avs/de-de-formal.msg +++ b/messages/uniworx/categories/avs/de-de-formal.msg @@ -2,17 +2,21 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later AvsPersonInfo: AVS Personendaten -AvsPersonId: AVS Personen Id +AvsPersonId: AVS Personen Id AvsPersonNo: AVS Personennummer +AvsPersonNoNotId: AVS Personennummer dient zur menschlichen Kommunikation mit der Ausweisstelle und darf nicht verwechselt werden mit der maschinell verwendeten AVS Personen Id +AvsPersonNoMismatch: AVS Personennummer hat sich geändert und wurde in FRADrive noch nicht aktualisiert +AvsPersonNoDiffers: Es sind derzeit zwei verschiedene AVS Personennummern zugeordnet. Bitte einen Administrator kontaktieren. AvsCardNo: Ausweiskartennummer AvsFirstName: Vorname AvsLastName: Nachname +AvsPrimaryCompany: Primäre Firma AvsInternalPersonalNo: Personalnummer (nur Fraport AG) AvsVersionNo: Versionsnummer +AvsQueryNeeded: Benötigt Verbindung zum AVS. AvsQueryEmpty: Bitte mindestens ein Anfragefeld ausfüllen! AvsQueryStatusInvalid t@Text: Nur numerische IDs eingeben, durch Komma getrennt! Erhalten: #{show t} AvsLicence: Fahrberechtigung -AvsPersonNoNotId: AVS Personennummer dient zur menschlichen Kommunikation mit der Ausweisstelle und darf nicht verwechselt werden mit der maschinell verwendeten AVS Personen Id AvsTitleLicenceSynch: Abgleich Fahrberechtigungen zwischen AVS und FRADrive BtnAvsRevokeUnknown: Fahrberechtigungen im AVS sofort entziehen BtnAvsImportUnknown: AVS Daten unbekannter Personen importieren @@ -27,13 +31,33 @@ RevokeFraDriveLicences alic@AvsLicence n@Int: _{alic} in FRADrive entzogen für RevokeUnknownLicencesOk: AVS Fahrberechtigungen unbekannter Fahrer wurden gesperrt RevokeUnknownLicencesFail: Nicht alle AVS Fahrberechtigungen unbekannter Fahrer konnten entzogen werden, siehe Log für Details AvsCommunicationError: AVS Schnittstelle lieferte einen unerwarteten Fehler. +AvsCommunicationTimeout: AVS Schnittstelle antwortete nicht. LicenceTableChangeAvs: Im AVS ändern LicenceTableGrantFDrive: In FRADrive erteilen LicenceTableRevokeFDrive: In FRADrive entziehen TableAvsActiveCards: Gültige Ausweise +TableAvsCardValid: Aktuell gültig +TableAvsCardIssueDate: Ausgestellt am +TableAvsCardValidTo: Gültig bis +AvsCardAreas: Ausweiszusätze +AvsCardColor: Ausweisfarbe AvsCardColorGreen: Grün AvsCardColorBlue: Blau AvsCardColorRed: Rot AvsCardColorYellow: Gelb LastAvsSynchronisation: Letzte AVS-Synchronisation +LastAvsSyncedBefore: Letzte AVS-Synchronisation vor LastAvsSynchError: Letzte AVS-Fehlermeldung + +AvsInterfaceUnavailable: AVS Schnittstelle nicht richtig konfiguriert oder antwortet nicht +AvsUserUnassociated user@UserDisplayName: AVS Id unbekannt für Nutzer #{user} +AvsUserUnknownByAvs api@AvsPersonId: AVS kennt Id #{tshow api} nicht (mehr) +AvsUserAmbiguous api@AvsPersonId: AVS Id #{tshow api} ist nicht eindeutig +AvsStatusSearchEmpty: AVS lieferte keine Ausweisinformationen +AvsPersonSearchEmpty: AVS Suche lieferte leeres Ergebnis +AvsPersonSearchAmbiguous: AVS Suche lieferte mehrere uneindeutige Ergebnisse +AvsSetLicencesFailed reason@Text: Setzen der Fahrlizenz im AVS fehlgeschlagen. Grund: #{reason} +AvsIdMismatch api1@AvsPersonId api2@AvsPersonId: AVS Suche für Id #{tshow api1} lieferte stattdessen Id #{tshow api2} +AvsUserCreationFailed api@AvsPersonId: Für AVS Id #{tshow api} konnte kein neuer Benutzer angelegt werden, da es eine gemeinsame Id (z.B. Personalnummer) mit einem existierenden, aber verschiedenen Nutzer gibt. +AvsCardsEmpty: AVS Suche lieferte keinerlei Ausweiskarten +AvsCurrentData: Alle angezeigte Daten wurden kürzlich direkt über die AVS Schnittstelle abgerufen. \ No newline at end of file diff --git a/messages/uniworx/categories/avs/en-eu.msg b/messages/uniworx/categories/avs/en-eu.msg index ec7288d7d..787d38a16 100644 --- a/messages/uniworx/categories/avs/en-eu.msg +++ b/messages/uniworx/categories/avs/en-eu.msg @@ -1,18 +1,23 @@ # SPDX-FileCopyrightText: 2022 Steffen Jost # # SPDX-License-Identifier: AGPL-3.0-or-later -AvsPersonInfo: AVS Person Info -AvsPersonId: AVS Person Id -AvsPersonNo: AVS Person Number +AvsPersonInfo: AVS person info +AvsPersonId: AVS person id +AvsPersonNo: AVS person number +AvsPersonNoNotId: AVS person number is used in human communication only and must not be mistaken for the AVS personen id used in machine communications +AvsPersonNoMismatch: AVS person number has changed and was not yet updated in FRADrive +AvsPersonNoDiffers: There are currently two differing AVS person numbers associated with this user. Please contact an administrator to resolve this. AvsCardNo: Card number AvsFirstName: First name AvsLastName: Last name +AvsPrimaryCompany: Primary company AvsInternalPersonalNo: Personnel number (Fraport AG only) AvsVersionNo: Version number +AvsQueryNeeded: AVS connection required. AvsQueryEmpty: At least one query field must be filled! AvsQueryStatusInvalid t: Numeric IDs only, comma seperated! #{show t} AvsLicence: Driving Licence -AvsPersonNoNotId: AVS person number is used in human communication only and must not be mistaken for the AVS personen id used in machine communications + AvsTitleLicenceSynch: Synchronisation driving licences between AVS and FRADrive BtnAvsRevokeUnknown: Revoke AVS driving licences for unknown persons immediately BtnAvsImportUnknown: Import AVS data for unknown persons @@ -27,13 +32,33 @@ RevokeFraDriveLicences alic@AvsLicence n@Int: _{alic} revoked in FRADrive for #{ RevokeUnknownLicencesOk: AVS driving licences of unknown drivers revoked RevokeUnknownLicencesFail: Not all AVS driving licences of unknown drivers could be revoked, see log for details AvsCommunicationError: AVS interface returned an unexpected error. +AvsCommunicationTimeout: AVS interface returned no response within timeout limit. LicenceTableChangeAvs: Change in AVS LicenceTableGrantFDrive: Grant in FRADrive LicenceTableRevokeFDrive: Revoke in FRADrive TableAvsActiveCards: Valid Cards +TableAvsCardValid: Currently valid +TableAvsCardIssueDate: Issued +TableAvsCardValidTo: Valid to +AvsCardAreas: Card areas +AvsCardColor: Color AvsCardColorGreen: Green AvsCardColorBlue: Blue AvsCardColorRed: Red AvsCardColorYellow: Yellow LastAvsSynchronisation: Last AVS synchronisation -LastAvsSynchError: Last AVS Error \ No newline at end of file +LastAvsSyncedBefore: Last AVS synchronisation before +LastAvsSynchError: Last AVS Error + +AvsInterfaceUnavailable: AVS interface was not configured correctly or does not respond +AvsUserUnassociated user: AVS id unknown for user #{user} +AvsUserUnknownByAvs api: AVS reports id #{tshow api} as unknown (or no longer known) +AvsUserAmbiguous api: Multiple matching users found for #{tshow api} +AvsStatusSearchEmpty: AVS returned no card information +AvsPersonSearchEmpty: AVS search returned empty result +AvsPersonSearchAmbiguous: AVS search returned more than one result +AvsSetLicencesFailed reason: Set driving licence within AVS failed. Reason: #{reason} +AvsIdMismatch api1 api2: AVS search for id #{tshow api1} returned id #{tshow api2} instead +AvsUserCreationFailed api@AvsPersonId: No new user could be created for AVS Id #{tshow api}, since an existing user shares at least one id presumed as unique +AvsCardsEmpty: AVS search returned no id cards +AvsCurrentData: All shown data has been recently received via the AVS interface. \ No newline at end of file diff --git a/messages/uniworx/categories/courses/courses/de-de-formal.msg b/messages/uniworx/categories/courses/courses/de-de-formal.msg index d8faf2d87..e0c589aba 100644 --- a/messages/uniworx/categories/courses/courses/de-de-formal.msg +++ b/messages/uniworx/categories/courses/courses/de-de-formal.msg @@ -70,6 +70,10 @@ CourseInvalidInput: Eingaben bitte korrigieren. CourseEditTitle: Kursart editieren/anlegen CourseEditOk tid@TermId ssh@SchoolId csh@CourseShorthand: Kursart #{tid}-#{ssh}-#{csh} wurde erfolgreich geändert. CourseEditDupShort tid@TermId ssh@SchoolId csh@CourseShorthand: Kursart #{tid}-#{ssh}-#{csh} konnte nicht geändert werden: Es gibt bereits einen andere Kursart mit dem selben Kürzel oder Titel in diesem Jahr und Bereich. +CourseEditQualificationFail: Eine Qualifikation konnte uas unbekanntem Grund nicht mit diesem Kurs assoziert werden. +CourseEditQualificationFailRights qsh@QualificationShorthand ssh@SchoolId: Qualifikation #{qsh} konnte nicht mit diesem Kurs assoziert werden, da Ihre Berechtigungen für Bereich #{ssh} dazu nicht ausreichen. +CourseEditQualificationFailExists: Diese Qualifikation ist bereits assoziert +CourseEditQualificationFailOrder: Diese Sortierpriorität existiert bereits CourseLecturer: Kursverwalter:in MailSubjectParticipantInvitation tid@TermId ssh@SchoolId csh@CourseShorthand: [#{tid}-#{ssh}-#{csh}] Einladung zur Kursartteilnahme CourseParticipantInviteHeading courseName@Text: Einladung zum Kursartteilnahmer für #{courseName} diff --git a/messages/uniworx/categories/courses/courses/en-eu.msg b/messages/uniworx/categories/courses/courses/en-eu.msg index 9f14a46a7..9f7835095 100644 --- a/messages/uniworx/categories/courses/courses/en-eu.msg +++ b/messages/uniworx/categories/courses/courses/en-eu.msg @@ -70,8 +70,12 @@ CourseInvalidInput: Invalid input CourseEditTitle: Edit/Create course CourseEditOk tid ssh csh: Successfully edited course type #{tid}-#{ssh}-#{csh} CourseEditDupShort tid ssh csh: Could not edit course type #{tid}-#{ssh}-#{csh}. Another course type with the same shorthand or title already exists for the given year and school. +CourseEditQualificationFail: A qualifikation could not be associated with this course for unknown reasons. +CourseEditQualificationFailRights qsh ssh: Qualification #{qsh} could not be associated with this course, due to your insufficient rights for department #{ssh}. +CourseEditQualificationFailExists: This qualification is already associated +CourseEditQualificationFailOrder: This sort order priority is used already CourseLecturer: Course administrator -MailSubjectParticipantInvitation tid@TermId ssh@SchoolId csh@CourseShorthand: [#{tid}-#{ssh}-#{csh}] Invitaion to join the course +MailSubjectParticipantInvitation tid ssh csh: [#{tid}-#{ssh}-#{csh}] Invitaion to join the course CourseParticipantInviteHeading courseName: Invitation to enrol for #{courseName} CourseParticipantInviteExplanation: You were invited to be a participant of a course. CourseParticipantInviteField: Email addresses to invite diff --git a/messages/uniworx/categories/courses/tutorial/de-de-formal.msg b/messages/uniworx/categories/courses/tutorial/de-de-formal.msg index 5a4cef6b6..4311bf005 100644 --- a/messages/uniworx/categories/courses/tutorial/de-de-formal.msg +++ b/messages/uniworx/categories/courses/tutorial/de-de-formal.msg @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Winnie Ros +# SPDX-FileCopyrightText: 2022-24 Winnie Ros , Steffen Jost # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -36,6 +36,7 @@ TutorialDelete: Löschen TutorialsHeading: Kurse TutorialNew: Neuer Kurs TutorialRegisteredSuccess tutn@TutorialName: Erfolgreich zum Kurs #{tutn} angemeldet +TutorialRegisteredFail tutn@TutorialName: Anmeldung zum Kurs #{tutn} fehlgeschlagen. Existiert bereits eine Anmeldung? TutorialDeregisteredSuccess tutn@TutorialName: Erfolgreich vom Kurs #{tutn} abgemeldet MailSubjectTutorInvitation tid@TermId ssh@SchoolId csh@CourseShorthand tutn@TutorialName: [#{tid}-#{ssh}-#{csh}] Einladung zum Ausbilder für #{tutn} TutorInviteHeading tutn@TutorialName: Einladung zum Ausbilder/zur Ausbilderin für #{tutn} @@ -49,4 +50,9 @@ TutorialUserGrantQualification: Qualifikation vergeben TutorialUserRenewQualification: Qualifikation regulär verlängern TutorialUserRenewedQualification n@Int: Qualifikation für #{tshow n} Kurs-#{pluralDE n "Teilnehmer:in" "Teilnehmer:innen"} regulär verlängert TutorialUserGrantedQualification n@Int: Qualifikation erfolgreich an #{tshow n} Kurs-#{pluralDE n "Teilnehmer:in" "Teilnehmer:innen"} vergeben -CommTutorial: Kursmitteilung \ No newline at end of file +CommTutorial: Kursmitteilung +TutorialDrivingPermit: Führerschein +TutorialEyeExam: Sehtest +TutorialNote: Kursnotiz +TutorialDayAttendance day@Text: Anwesenheit am #{day} +TutorialDayNote day@Text: Anwesenheitsnotiz für #{day} \ No newline at end of file diff --git a/messages/uniworx/categories/courses/tutorial/en-eu.msg b/messages/uniworx/categories/courses/tutorial/en-eu.msg index 20df36d50..407bb1b88 100644 --- a/messages/uniworx/categories/courses/tutorial/en-eu.msg +++ b/messages/uniworx/categories/courses/tutorial/en-eu.msg @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Winnie Ros +# SPDX-FileCopyrightText: 2022-24 Winnie Ros , Steffen Jost # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -36,6 +36,7 @@ TutorialDelete: Delete TutorialsHeading: Courses TutorialNew: New course TutorialRegisteredSuccess tutn: Successfully registered for the course #{tutn} +TutorialRegisteredFail tutn: Registering for the course #{tutn} failed. Probably already registered? TutorialDeregisteredSuccess tutn: Successfully de-registered for the course #{tutn} MailSubjectTutorInvitation tid ssh csh tutn: [#{tid}-#{ssh}-#{csh}] Invitation to be a instructor for #{tutn} TutorInviteHeading tutn: Invitation to be instructor for #{tutn} @@ -51,3 +52,8 @@ TutorialUserRenewQualification: Renew qualification TutorialUserRenewedQualification n@Int: Successfully renewed qualification #{tshow n} course #{pluralEN n "user" "users"} TutorialUserGrantedQualification n: Successfully granted qualification #{tshow n} course #{pluralEN n "user" "users"} CommTutorial: Course message +TutorialDrivingPermit: Driving permit +TutorialEyeExam: Eye exam +TutorialNote: Course note +TutorialDayAttendance day: Attendance #{day} +TutorialDayNote day: Attendance note #{day} \ No newline at end of file diff --git a/messages/uniworx/categories/firm/de-de-formal.msg b/messages/uniworx/categories/firm/de-de-formal.msg index c7a92efb3..8f27c24c4 100644 --- a/messages/uniworx/categories/firm/de-de-formal.msg +++ b/messages/uniworx/categories/firm/de-de-formal.msg @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2023 Steffen Jost +# SPDX-FileCopyrightText: 2023-24 Steffen Jost # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -7,7 +7,6 @@ FirmSuperForeign: Firmenfremde Ansprechpartner FirmSuperIrregular: Irreguläre Ansprechpartner FirmAssociates: Firmenangehörige FirmContact: Firmenkontakt -FirmNoContact: Keine allgemeinen Kontaktinformationen bekannt. FirmEmail: Allgemeine Email FirmAddress: Postanschrift FirmDefaultPreferenceInfo: Diese Voreinstellungen gelten nur für neue Firmenangehörige @@ -16,11 +15,15 @@ FirmActionInfo: Betrifft alle Firmenangehörigen unter Ihrer Aufsicht. FirmActNotify: Mitteilung versenden FirmActResetSupervision: Ansprechpartner für alle Firmenangehörigen zurücksetzen FirmActResetSuperKeep: Bisherige Ansprechpartner der Firmenangehörigen zusätzlich beibehalten? +FirmActRemoveSupers: Alle rein firmenbezogenen Ansprechpartnerbeziehungen für diese Personen entfernen? FirmActResetMutualSupervision: Ansprechpartner beaufsichtigen sich gegenseitig -FirmActAddSupersvisors: Ansprechpartner hinzufügen +FirmActResetSupersKeepAll: Alle behalten +FirmActResetSupersRemoveAps: Nur Standardansprechpartner entfernen +FirmActResetSupersRemoveAll: Alle entfernen +FirmActAddSupervisors: Ansprechpartner hinzufügen FirmActAddSupersEmpty: Es konnten keine Ansprechpartner hinzugefügt werden FirmActAddSupersSet n@Int64 postal@(Maybe Bool): #{n} Standardansprechpartner geändert #{maybeBoolMessage postal "" "und auf Briefversand geschaltet" "und Benachrichtigungen per Email gesetzt"}, aber nicht nicht aktiviert. -RemoveSupervisors ndef@Int64 nact@Int64: #{ndef} Standard Ansprechpartner entfernt#{bool ", aber noch nicht deaktiviert" (", " <> tshow nact <> " aktive Ansprechpartnerbeziehungen gelöscht") (nact > 0)} +RemoveSupervisors ndef@Int64: #{ndef} Standardansprechpartner entfernt. FirmActChangeContactUser: Kontaktinformationen von allen Firmenangehörigen ändern FirmActChangeContactFirm: Kontaktinformationen der Firma ändern FirmActChangeContactFirmInfo: Firmenkontaktinformationen werden nur für neue Firmenangehörige verwendet, für die sonst keine Kontaktinformationen vorliegen. @@ -28,17 +31,23 @@ FirmActChangeContactFirmResult: Firmenkontaktinformationen geändert. Betrifft n FirmUserActNotify: Mitteilung versenden FirmUserActResetSupervision: Ansprechpartner auf Firmenstandard zurücksetzen FirmUserActSetSupervisor: Ansprechpartner ändern +FirmUserActChangeContact: Kontaktinformationen für ausgewählte Firmenangehörige ändern +FirmUserActChangeDetails: Firmenassoziation bearbeiten +FirmUserActRemove: Firmenassoziation entfernen +FirmUserActMkSuper: Zum Firmenansprechpartner ernennen +FirmUserActChangeDetailsResult n@Int64 t@Int64: Firmenassoziation von #{n}/#{t} #{pluralDE n "Firmenangehörigen" "Firmenangehörige"} wurden aktualisiert +FirmUserActChangeResult n@Int64 t@Int64: Benachrichtigungseinstellung für #{n}/#{t} #{pluralDE n "Firmenangehörigen" "Firmenangehörige"} wurden geändert +FirmUserActRemoveResult uc@Int64: #{uc} #{pluralDE uc "Firmenassoziation" "Firmenassoziationen"} entfernt. +FirmRemoveSupervision sup@Int64 sub@Int64: #{noneMoreDE sup "" (tshow sup <> " Ansprechpartnerbeziehungen wegen entferntem Ansprechpartner gelöscht. ")} #{noneOneMoreDE sub "Keine Ansprechpartnerbeziehung" "Eine Ansprechpartnerbeziehung" (tshow sup <> " Ansprechpartnerbeziehungen")} wegen entferntem Angesprochenem gelöscht. FirmNewSupervisor: Neue individuelle Ansprechpartner hinzufügen FirmSetSupervisor: Existierende Ansprechpartner hinzufügen FirmSetSupersReport nusr@Int64 nspr@Int64 nrem@Int64: Für #{nusr} Firmenangehörige wurden #{nspr} individuelle Ansprechpartner eingetragen#{bool "." (" und " <> tshow nrem <> " individuelle Ansprechpartnerbeziehungen gelöscht.") (nrem >0)} -FirmUserActMkSuper: Zum Firmenansprechpartner ernennen -FirmUserActChangeContact: Kontaktinformationen für ausgewählte Firmenangehörige ändern FirmResetSupervision rem@Int64 set@Int64: #{tshow set} Ansprechpartner gesetzt#{bool mempty (", " <> tshow rem <> " zuvor gelöscht") (rem > 0)} FirmSuperActNotify: Mitteilung versenden FirmSuperActSwitchSuper: Standard Firmenansprechpartner abändern FirmSuperActSwitchSuperInfo: Betrifft keine firmenfremden Ansprechpartner und ändert keine aktiven individuellen Ansprechpartnerbeziehungen. Gegebenfalls im Anschluss die Funktion "Ansprechpartner auf Firmenstandard zurücksetzen" nutzen. FirmSuperActRMSuperDef: Firmenansprechpartner entfernen -FirmSuperActRMSuperActive: Auch aktive Ansprechpartnerbeziehungen innerhalb dieser Firma beenden +FirmSuperActRMSuperActive: Aktive Ansprechpartnerbeziehungen innerhalb dieser Firma beenden? FirmsNotification: Firmen E-Mail versenden FirmNotification fsh@CompanyShorthand: E-Mail an #{fsh} senden FirmsNotificationTitle: Firmen benachrichtigen @@ -47,14 +56,23 @@ FilterSupervisor: Hat aktiven Ansprechpartner FilterSupervisorCompany fsh@CompanyShorthand: Hat aktiven Ansprechpartner, #{fsh} der angehört FilterSupervisorForeign fsh@CompanyShorthand: Hat aktiven Ansprechpartner, der selbst nicht #{fsh} angehört FilterForeignSupervisor: Hat firmenfremde Ansprechpartner +FilterIsForeignSupervisee: Ist Ansprechpartner für Firmenfremde FilterFirmExtern: Externe Firma +FilterFirmExternTooltip: Hat die Firma eine Postanschrift im AVS? +FilterFirmPrimary: Ist primäre Firma in FRADrive +FilterHasQualification: Hat Firmenangehörige mit aktuell gültiger Qualifikation FirmSupervisorOf fsh@CompanyShorthand: Ansprechpartner #{fsh} angehörig FirmSupervisorIndependent: Ansprechpartner ohne jegliche Firmenzugehörigkeit FirmEmployeeOf fsh@CompanyShorthand: Firmenangehörige #{fsh} NoCompanySelected: Bitte wählen Sie mindestens eine Firma aus. TableIsDefaultSupervisor: Standardansprechpartner +TableSuperior: Vorgesetzter TableIsDefaultReroute: Standardumleitung FormFieldPostal: Benachrichtigungseinstellung FormFieldPostalTip: Gilt für alle Benachrichtigungen an diese Person, nicht nur für Umleitungen an diesen Ansprechpartner -FirmUserChanges n@Int64: Benachrichtigungseinstellung für #{n} Firmenangehörige wurden geändert -FirmSupervisionKeyData: Kennzahlen Ansprechpartner \ No newline at end of file +FirmSupervisionKeyData: Kennzahlen Ansprechpartner +CompanyUserPriority: Firmenpriorität +CompanyUserPriorityTip: Firmenpriorität ist lediglich relativ zu anderen Firmenassoziation der Person +CompanyUserUseCompanyAddress: Verwendet Firmenkontaktaddresse +CompanyUserUseCompanyAddressTip: sofern im Benutzer keine Postanschrift hinterlegt ist +CompanyUserUseCompanyPostalError: Postalische Adresse muss leer bleiben, damit die Firmenanschrift genutzt wird! diff --git a/messages/uniworx/categories/firm/en-eu.msg b/messages/uniworx/categories/firm/en-eu.msg index 043312a20..fe4dbc045 100644 --- a/messages/uniworx/categories/firm/en-eu.msg +++ b/messages/uniworx/categories/firm/en-eu.msg @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2023 Steffen Jost +# SPDX-FileCopyrightText: 2023-24 Steffen Jost # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -7,7 +7,6 @@ FirmSuperForeign: External supervisor FirmSuperIrregular: Irregular supervisor FirmAssociates: Company associated users FirmContact: Company Contact -FirmNoContact: No general contact information known. FirmEmail: General company email FirmAddress: Postal address FirmDefaultPreferenceInfo: Default setting for new company associates only @@ -16,11 +15,15 @@ FirmActionInfo: Affects alle company associates under your supervision. FirmActNotify: Send message FirmActResetSupervision: Reset supervisors for all company associates FirmActResetSuperKeep: Additionally keep existing supervisors of company associates? +FirmActRemoveSupers: Terminate all company related supervisonships? FirmActResetMutualSupervision: Supervisors supervise each other -FirmActAddSupersvisors: Add supervisors +FirmActResetSupersKeepAll: Keep all +FirmActResetSupersRemoveAps: Remove default supervisors only +FirmActResetSupersRemoveAll: Remove all +FirmActAddSupervisors: Add supervisors FirmActAddSupersEmpty: No supervisors added FirmActAddSupersSet n postal: #{n} default company supervisors changed #{maybeBoolMessage postal "" "and switched to postal notifications" "and switched to email notifications"}, but not yet activated. -RemoveSupervisors ndef nact: #{ndef} default supervisors removed#{bool ", but not yet deactivated" (" and " <> tshow nact <> " active supervisions terminated") (nact > 0)} +RemoveSupervisors ndef: #{ndef} default supervisors removed. FirmActChangeContactUser: Change contact data for all company associates FirmActChangeContactFirm: Change company contact data FirmActChangeContactFirmInfo: The company contact data is only used for new company associates that would habe no contact information of their own otherwise. @@ -28,17 +31,23 @@ FirmActChangeContactFirmResult: Company contact data changed, affecting future c FirmUserActNotify: Send message FirmUserActResetSupervision: Reset supervisors to company default FirmUserActSetSupervisor: Change supervision +FirmUserActChangeContact: Change contact data for selected company associates +FirmUserActChangeDetails: Edit company association +FirmUserActRemove: Delete company association +FirmUserActMkSuper: Mark as company supervisor +FirmUserActChangeDetailsResult n t: #{n}/#{t} #{pluralENs n "company association"} updated +FirmUserActChangeResult n t: Notification settings changed for #{n}/#{t} company #{pluralENs n "associate"} +FirmUserActRemoveResult uc: #{pluralENsN uc "Company association"} deleted. +FirmRemoveSupervision sup sub: #{noneMoreEN sup "" ((pluralENsN sup "supervision") <> " removed due to eliminated supervisors.")} #{noneMoreEN sub "No supervision" (pluralENsN sub "supervision")} removed due to eliminated supervisees. FirmNewSupervisor: Appoint new individual supervisors FirmSetSupervisor: Add existing supervisors -FirmSetSupersReport nusr@Int64 nspr@Int64 nrem@Int64: #{nspr} individal supervisors set for #{nusr} company associates#{bool "." (" and " <> tshow nrem <> " other individual supervisions terminated.") (nrem >0)} +FirmSetSupersReport nusr nspr nrem: #{nspr} individual supervisors set for #{nusr} company associates#{bool "." (" and " <> tshow nrem <> " other individual supervisions terminated.") (nrem >0)} FirmResetSupervision rem set: #{tshow set} supervisors set#{bool mempty (", " <> tshow rem <> " deleted before") (rem > 0)} -FirmUserActChangeContact: Change contact data for selected company associates -FirmUserActMkSuper: Mark as company supervisor FirmSuperActNotify: Send message FirmSuperActSwitchSuper: Change default company supervisor -FirmSuperActSwitchSuperInfo: Does not affect company-external supervisors and does not change any active individal supervisions. Additionally use reset action, if desired. +FirmSuperActSwitchSuperInfo: Does not affect company-external supervisors and does not change any active individual supervisions. Additionally use reset action, if desired. FirmSuperActRMSuperDef: Remove default supervisor -FirmSuperActRMSuperActive: Also remove active supervisions within this company +FirmSuperActRMSuperActive: Terminate active supervisions within this company? FirmsNotification: Send company notification e-mail FirmNotification fsh: Send e-mail to #{fsh} FirmsNotificationTitle: Company notification @@ -47,14 +56,23 @@ FilterSupervisor: Has active supervisor FilterSupervisorCompany fsh: Has active company supervisor belonging to #{fsh} FilterSupervisorForeign fsh: Has active supervisor not belonging to #{fsh} FilterForeignSupervisor: Has company-external supervisors +FilterIsForeignSupervisee: Supervisor for company external users FilterFirmExtern: External company +FilterFirmExternTooltip: i.e. is a postal address registered within AVS? +FilterFirmPrimary: Is primary company in FRADrive +FilterHasQualification: Has company associates with currently valid qualification FirmSupervisorOf fsh@CompanyShorthand: Supervisors belonging to #{fsh} FirmSupervisorIndependent: Independent supervisors FirmEmployeeOf fsh@CompanyShorthand: #{fsh} associated users NoCompanySelected: Select at least one company, please. TableIsDefaultSupervisor: Default supervisor +TableSuperior: Superior TableIsDefaultReroute: Default reroute FormFieldPostal: Notification type FormFieldPostalTip: Affects all notifications to this person, not just reroutes to this supervisor -FirmUserChanges n: Notification settings changed for #{n} company associates -FirmSupervisionKeyData: Supervision key data \ No newline at end of file +FirmSupervisionKeyData: Supervision key data +CompanyUserPriority: Company priority +CompanyUserPriorityTip: Company priority is relative to other company associations for a user +CompanyUserUseCompanyAddress: Use company postal address +CompanyUserUseCompanyAddressTip: if and only if the postal address of the user is empty +CompanyUserUseCompanyPostalError: Individual postal address must left empty for the company address to be used! diff --git a/messages/uniworx/categories/print/de-de-formal.msg b/messages/uniworx/categories/print/de-de-formal.msg index 32fe30556..f14def9d8 100644 --- a/messages/uniworx/categories/print/de-de-formal.msg +++ b/messages/uniworx/categories/print/de-de-formal.msg @@ -18,6 +18,7 @@ PrintJobAcknowledgeFailed: Keine Druckaufträge bestätigt aufgrund zwischenzeit PrintJobAcknowledgeQuestion n@Int d@Text: #{n} #{pluralDE n "Druckauftrag" "Druckaufräge"} vom #{d} als gedruckt und versendet bestätigen? PrintJobAcknowledgements: Versanddatum von Briefen an PrintRecipient: Empfänger +PrintAffected: Betroffener PrintSender !ident-ok: Sender PrintCourse: Kursarten PrintQualification: Qualifikation @@ -25,4 +26,7 @@ PrintPDF !ident-ok: PDF PrintManualRenewal: Vorfeldführerschein Renewal-Brief testweise versenden PrintLmsUser: E‑Learning Id PrintJobs: Druckaufräge -PrintLetterType: Brieftypkürzel \ No newline at end of file +PrintLetterType: Brieftypkürzel + +MCActDummy: Platzhalter +CCActDummy: Platzhalter \ No newline at end of file diff --git a/messages/uniworx/categories/print/en-eu.msg b/messages/uniworx/categories/print/en-eu.msg index 053fd1a7e..d757cf2cf 100644 --- a/messages/uniworx/categories/print/en-eu.msg +++ b/messages/uniworx/categories/print/en-eu.msg @@ -18,6 +18,7 @@ PrintJobAcknowledgeFailed: No print-jobs acknowledged, due to intermediate chang PrintJobAcknowledgeQuestion n d: Mark #{n} #{pluralENs n "print-job"} issued on #{d} as printed and mailed already? PrintJobAcknowledgements: Sent-dates for Letter to PrintRecipient: Recipient +PrintAffected: Affetcted PrintSender: Sender PrintCourse: Course type PrintQualification: Qualification @@ -25,4 +26,7 @@ PrintPDF: PDF PrintManualRenewal: Manual sending of an apron driver's licence renewal letter PrintLmsUser: E‑learning id PrintJobs: Print jobs -PrintLetterType: Letter type shorthand \ No newline at end of file +PrintLetterType: Letter type shorthand + +MCActDummy: Placeholder +CCActDummy: Placeholder \ No newline at end of file diff --git a/messages/uniworx/categories/qualification/de-de-formal.msg b/messages/uniworx/categories/qualification/de-de-formal.msg index e0fee7cb8..b5d3a36bc 100644 --- a/messages/uniworx/categories/qualification/de-de-formal.msg +++ b/messages/uniworx/categories/qualification/de-de-formal.msg @@ -9,23 +9,31 @@ QualificationValidIndicator: Gültigkeit QualificationValidDuration: Gültigkeitsdauer QualificationAuditDuration: Aufbewahrung Audit Log QualificationAuditDurationTooltip n@Int: Optionaler Zeitraum zur Löschung von E‑Learning Daten. Hinweis: Der E‑Learning Server kann seine anonymisierten Daten schon früher löschen, aber spätestens #{n} Tage nach Abschluss. +QualificationAuditDurationReuseError: Diese Qualifikation nutzt das E‑Learning einer anderen Qualifikation, für die derzeit keinen Löschzeitraum konfiguriert wurde. QualificationRefreshWithin: Erneurerungszeitraum -QualificationRefreshWithinTooltip: Optionaler Zeitraum vor Ablauf für automatischen Start des E‑Learnings und Versand einer Benachrichtigung per Brief oder Email. -QualificationRefreshReminder: 2. Erinnerung -QualificationRefreshReminderTooltip: Optionaler Zeitraum vor Ablauf zur Versendung einer zweiten Erinnerung per Brief oder Email mit identischen Zugangsdaten, sofern in diesem Zeitraum vor Ablauf noch keine Ablaufbenachrichtigung versendet wurde. +QualificationRefreshWithinTooltip: Optionaler Zeitraum vor Ablauf für eine Benachrichtigung per Email. Bei aktiviertem automatischem E‑Learning wird dieses gestartet und die Benachrichtigung erfolgt per Brief oder Email. +QualificationRefreshReminder: Zweite Erinnerung +QualificationRefreshReminderTooltip: Optionaler Zeitraum vor Ablauf zur Versendung einer zweiten Erinnerung per Brief oder Email mit identischen E‑Learning Zugangsdaten, sofern die Qualifikation noch gültig und das E‑Learning noch offen ist. QualificationElearningStart: Wird das E‑Learning automatisch gestartet? +QualificationElearningRenew: Verlängert ein erfolgreiches E‑Learning die Qualifikation automatisch um die reguläre Gültigkeitsdauer? +QualificationElearningLimit: Ist die Anzahl der E‑Learning Versuche limitiert? +QualificationElearningLimitMax n@Int: Maximal #{n} Versuche +QualificationElearningNoLimit: Nicht limitiert QualificationExpiryNotification: Ungültigkeitsbenachrichtigung? QualificationExpiryNotificationTooltip: Nutzer werden benachrichtigt, wenn die Qualifikation ungültig wird, sofern der jeweilige Nutzer in seinen Benutzereinstellungen diese Art Benachrichtigung aktiviert hat. TableQualificationCountActive: Aktive TableQualificationCountActiveTooltip: Anzahl Personen mit momentan gültiger Qualifikation TableQualificationCountTotal: Gesamt +TableQualificationLmsReuses: LMS nutzt +TableQualificationLmsReusesTooltip: Diese Qualifikation hat kein eigenes E‑Learning, sondern wird über das E‑Learning der angegebenen Qualifikation abgewickelt. TableQualificationIsAvsLicence: AVS TableQualificationIsAvsLicenceTooltip: Unter welchem Namen wird diese Qualifikation mit dem Ausweisverwaltungssystem (AVS) synchronisiert? Betrifft nur Benutzer mit AVS PersonenID. TableQualificationSapExport: SAP TableQualificationSapExportTooltip: Wird die Qualifikation an das SAP übermittelt? Betrifft nur Benutzer mit Fraport Personalnummer. LmsQualificationValidUntil: Gültig bis TableQualificationLastRefresh: Zuletzt erneuert -TableQualificationLastNotified: Letzte Benachrichtigung +TableQualificationLastNotified: Letzte Benachrichtigung über erfolgte Gültigkeitsänderung +TableQualificationLastNotifiedTooltip: Hier werden ausschließlich Benachrichtigungen berücksichtigt, die über einen bereits erfolgten Ablauf/Entzug/Wiedererteilung informieren. Dies ignoriert insbesondere reguläre Verlängerung, z.B. durch E-Learning. TableQualificationFirstHeld: Erstmalig TableQualificationBlockedDue: Entzug TableQualificationBlockedTooltip: Wann wurde die Qualifikation vorübergehend außer Kraft gesetzt und warum wurde dies veranlasst? @@ -46,11 +54,13 @@ QualificationExpired: Ungültig seit LmsUser: Inhaber LmsURL: Link E‑Learning TableLmsEmail: E‑Mail -TableLmsIdent: E-Learning Benutzer +TableLmsIdent: E‑Learning Benutzer TableLmsElearning: E‑Learning +TableLmsElearningRenews: Automatische Verlängerung +TableLmsElearningLimit: Maximale Versuche TableLmsPin: E‑Learning Passwort -TableLmsResetPin: E-Learning Passwort zurücksetzen? -TableLmsDatePin: E-Learning Passwort erstellt +TableLmsResetPin: E‑Learning Passwort zurücksetzen? +TableLmsDatePin: E‑Learning Passwort erstellt TableLmsDate: Datum TableLmsDelete: Löschen? TableLmsStaff: Interner Mitarbeiter? @@ -88,7 +98,8 @@ LmsReportInsert: Neues LMS Ereignis LmsReportUpdate: LMS Ereignis Aktualisierung LmsReportCsvExceptionDuplicatedKey: CSV-Import LmsReport fand uneindeutigen Schlüssel LmsDirectUpload: Direkter Upload für automatisierte Systeme -LmsErrorNoRefreshElearning: Fehler: E‑Learning wird nicht automatisch gestartet, da die Zeitspanne für den Erneurerungszeitraum nicht festgelegt wurde. +LmsErrorNoRefreshElearning: Fehler: E‑Learning wird nicht automatisch gestartet, da die Zeitspanne für den Erneurerungszeitraum nicht festgelegt wurde! +LmsErrorNoRenewElearning: Fehler: Erfoglreiches E‑Learning verlängert die Qualifikation nicht automatisch, da die Gültigkeitsdauer nicht festgelegt wurde! MailSubjectQualificationRenewal qname@Text: Qualifikation #{qname} muss demnächst erneuert werden MailSubjectQualificationExpiry qname@Text: Qualifikation #{qname} läuft demnächst ab MailSubjectQualificationExpired qname@Text: Qualifikation #{qname} ist ab sofort ungültig @@ -106,11 +117,13 @@ QualificationActUnblock: Entzug aufheben QualificationActRenew: Qualifikation regulär verlängern QualificationActGrant: Qualifikation vergeben QualificationActGrantWarning: Diese Funktion ist nur für seltene Ausnahmefälle vorgesehen! Ein Entzug wird ggf. aufgehoben. +QualificationActStartELearning: E‑Learning für gültige Inhaber (neu) starten +QualificationActStartELearningStatus l@QualificationShorthand n@Int m@Int: E‑Learning #{l} für #{n}/#{m} Teilnehmer (neu) gestartet. Hinweis: Es kann länger dauern, bis das LMS tatsächlich startet. QualificationStatusBlock l@QualificationShorthand n@Int m@Int: #{n}/#{m} #{l} entzogen QualificationStatusUnblock l@QualificationShorthand n@Int m@Int: #{n}/#{m} #{l} reaktiviert LmsInactive: Aktuell kein E‑Learning aktiv LmsRenewalInstructions: Weitere Anweisungen zur Verlängerung finden Sie im angehängten PDF. Um Missbrauch zu verhindern wurde das PDF mit dem im FRADrive hinterlegten PDF-Passwort des Prüflings verschlüsselt. Falls kein PDF-Passwort manuell hinterlegt wurde, ist das PDF-Passwort die Flughafen Ausweisnummer, inklusive Punkt und der Ziffer danach. -LmsNoRenewal: Leider kann diese Qualifikation nicht alleine durch E‑Learning verlängert werden. +LmsNoRenewal: Leider kann diese Qualifikation nicht alleine durch E‑Learning verlängert werden. Bitte setzen Sie sich mit uns in Verbindung, wenn Sie die Qualifikation verlängern möchten und noch nicht wissen, wie Sie das tun können. Ignorieren Sie diese automatisch generierte Erinnerung, falls Sie sich bereits um die Verlängerung gekümmert haben LmsRenewalReminder: Erinnerung LmsActNotify: Benachrichtigung E‑Learning erneut per Post oder E-Mail versenden LmsActRenewPin: Neues zufällige E‑Learning Passwort zuweisen @@ -119,7 +132,7 @@ LmsActReset: E‑Learning Fehlversuche zurücksetzen und entsperren LmsActResetInfo: E‑Learning Login, Passwort und Fortschritt bleiben unverändert, eine neue Benachrichtigung ist nicht notwendig. Nur möglich für bereits gesperrte Lerner. Es kann bis zu 2 Stunden dauern, bis das LMS die Anfrage umgesetzt hat. LmsActResetFeedback n@Int m@Int: Für #{n}/#{m} E‑Learning Nutzer wurden alle Fehlversuche zurückgesetzt. LmsActRestart: E‑Learning komplett neu starten -LmsActRestartWarning: Das vorhandene E‑Learning wird komplett gelöscht! Für Inhaber einer gültigen Fahrlizenz werden später Benutzer und Passwort neu vergeben und es sollte eine neue Benachrichtigung versendet werden. Hinweis: Es kann mehrere Stunden dauern, bis das LMS diese Anfrage umgesetzt hat. +LmsActRestartWarning: Das vorhandene E‑Learning wird komplett gelöscht! Für Inhaber einer gültigen Lizenz werden später Benutzer und Passwort neu vergeben und es sollte eine neue Benachrichtigung versendet werden. Hinweis: Es kann mehrere Stunden dauern, bis das LMS diese Anfrage umgesetzt hat. LmsActRestartFeedback n@Int m@Int: #{n}/#{m} E‑Learning Nutzer wurden komplett neu gestartet mit neuem Login und Passwort. LmsActRestartExtend: Gültig bis ggf. erhöhen für die nächsten # Tage LmsActRestartUnblock: Entzug ggf. aufheben diff --git a/messages/uniworx/categories/qualification/en-eu.msg b/messages/uniworx/categories/qualification/en-eu.msg index c886cb843..b8fb5c38e 100644 --- a/messages/uniworx/categories/qualification/en-eu.msg +++ b/messages/uniworx/categories/qualification/en-eu.msg @@ -9,23 +9,31 @@ QualificationValidIndicator: Validity QualificationValidDuration: Validity period QualificationAuditDuration: Audit log retention period QualificationAuditDurationTooltip n@Int: Optional period for deletion of e‑learning data. Note that the e‑learning server may delete its anonymised data earlier, at most #{n} days after closing. +QualificationAuditDurationReuseError: This qualification reuses the e‑learning from another qualification, which has no audit duration configured. QualificationRefreshWithin: Refresh within -QualificationRefreshWithinTooltip: Optional period before expiry to start e‑learning and send a notification by post or email. -QualificationRefreshReminder: 2. Reminder -QualificationRefreshReminderTooltip: Optional period before expiry to send a second notification by post or email once more, provided that no renewal notification was sent in this period before expiry. +QualificationRefreshWithinTooltip: Optional period before expiry to send a notification by email. If e‑learning is set to start automatically, it will be started and e‑learning credentials are send with this notification by post or email. +QualificationRefreshReminder: Second reminder +QualificationRefreshReminderTooltip: Optional period before expiry to send a second notification by post or email once more, including the existing credentials, provided that the e‑learning is still undecided and the qualification has not yet expired. QualificationElearningStart: Is e‑learning automatically started? +QualificationElearningRenew: Does successful e‑learning automatically extend a qualification by the default validity period? +QualificationElearningLimit: Is the number of e‑learning attempts limited? +QualificationElearningLimitMax n: #{n} attempts maximum +QualificationElearningNoLimit: No limit QualificationExpiryNotification: Invalidity notification? QualificationExpiryNotificationTooltip: Qualification holder are notfied upon invalidity, provided they have activated such notification in their user settings. TableQualificationCountActive: Active TableQualificationCountActiveTooltip: Number of currently valid qualification holders TableQualificationCountTotal: Total +TableQualificationLmsReuses: Reuse LMS +TableQualificationLmsReusesTooltip: This qualification reuses the e‑learning of the given qualification, instead of having a separate e‑learning of its own. TableQualificationIsAvsLicence: AVS driving license TableQualificationIsAvsLicenceTooltip: Under which name is this qualification synchronized with AVS, if any? Only applies to qualification holders having an AVS PersonID. TableQualificationSapExport: Sent to SAP TableQualificationSapExportTooltip: Is this qualification transmitted to SAP? Only applies to qualification holder having a Fraport AG personnel number. LmsQualificationValidUntil: Valid until TableQualificationLastRefresh: Last renewed -TableQualificationLastNotified: Last notified +TableQualificationLastNotified: Last notified about validity change +TableQualificationLastNotifiedTooltip: The date of the last notification about any already effective change in validity due to revocation or reissue. This does not entail regular validity extensions, e.g. due to e-learning. TableQualificationFirstHeld: First held TableQualificationBlockedDue: Revocations TableQualificationBlockedTooltip: Why and when was this qualification temporarily suspended? @@ -49,6 +57,8 @@ TableLmsEmail: Email TableLmsIdent: E‑learning user TableLmsPin: E‑learning password TableLmsElearning: E‑learning +TableLmsElearningRenews: Automatic renewal +TableLmsElearningLimit: Max attempts TableLmsResetPin: Reset E‑learning password? TableLmsDatePin: E‑learning password created TableLmsDate: Date @@ -88,7 +98,8 @@ LmsReportInsert: New LMS event LmsReportUpdate: Update of LMS event LmsReportCsvExceptionDuplicatedKey: CSV Import LmsReport with ambiguous key LmsDirectUpload: Direct upload for automated systems -LmsErrorNoRefreshElearning: Error: E‑learning will not be started automatically due to refresh-within time period not being set. +LmsErrorNoRefreshElearning: Error: E‑learning will not be started automatically due to refresh-within time period not being set! +LmsErrorNoRenewElearning: Error: E‑learning will not automatically extend validity due to validity duration not being set! MailSubjectQualificationRenewal qname: Qualification #{qname} must be renewed shortly MailSubjectQualificationExpiry qname: Qualification #{qname} expires soon MailSubjectQualificationExpired qname: Qualification #{qname} is no longer valid @@ -106,11 +117,13 @@ QualificationActUnblock: Clear revocation QualificationActRenew: Renew qualification QualificationActGrant: Grant qualification QualificationActGrantWarning: Use with caution in rare exceptional cases only! Any revocation will be undone. +QualificationActStartELearning: Manually (re)start e‑learning for valid qualification holders +QualificationActStartELearningStatus l n m: E‑learning #{l} (re)started for #{n}/#{m} users. Note: It may take a while, until the e‑learning is activated. QualificationStatusBlock l n m: #{n}/#{m} #{l} revoked QualificationStatusUnblock l n m: #{n}/#{m} #{l} reactivated LmsInactive: Currently no active e‑learning LmsRenewalInstructions: Instruction on how to accomplish the renewal are enclosed in the attached PDF. In order to avoid misuse, the PDF is encrypted with the FRADrive PDF-password of the examinee. If no PDF-password had been chosen yet, then the password is the Fraport id card number of the examinee, including the punctuation mark and the digit thereafter. -LmsNoRenewal: Unfortunately, this particular qualification cannot be renewed through e‑learning only. +LmsNoRenewal: Unfortunately, this particular qualification cannot be renewed through e‑learning only. Please contact us, if you do not yet know how to renew this qualification. Ignore this automatically generated reminder email, if you have made arrangements for the renewal of this qualification already. LmsRenewalReminder: Reminder LmsActNotify: Resend e‑learning notification by post or email LmsActRenewPin: Randomly replace e‑learning password diff --git a/messages/uniworx/categories/school/de-de-formal.msg b/messages/uniworx/categories/school/de-de-formal.msg index eedea789f..9d678454f 100644 --- a/messages/uniworx/categories/school/de-de-formal.msg +++ b/messages/uniworx/categories/school/de-de-formal.msg @@ -40,4 +40,6 @@ SchoolAuthorshipStatementSheetDefinitionTip: Bitte in sowohl deutscher als auch SchoolAuthorshipStatementSheetExamDefinition: Eigenständigkeitserklärung für prüfungszugehörige Übungsblattabgaben SchoolAuthorshipStatementSheetExamDefinitionTip: Bitte in sowohl deutscher als auch englischer Sprache angeben. SchoolAuthorshipStatementSheetAllowOther: Abweichende Eigenständigkeitserklärungen für nicht-prüfungszugehörige Übungsblätter erlauben? -SchoolAuthorshipStatementSheetExamAllowOther: Abweichende Eigenständigkeitserklärungen für prüfungszugehörige Übungsblätter erlauben? \ No newline at end of file +SchoolAuthorshipStatementSheetExamAllowOther: Abweichende Eigenständigkeitserklärungen für prüfungszugehörige Übungsblätter erlauben? + +DailyActDummy: Platzhalter ohne Funktion \ No newline at end of file diff --git a/messages/uniworx/categories/school/en-eu.msg b/messages/uniworx/categories/school/en-eu.msg index 32109bfa4..5f2a79667 100644 --- a/messages/uniworx/categories/school/en-eu.msg +++ b/messages/uniworx/categories/school/en-eu.msg @@ -40,4 +40,6 @@ SchoolAuthorshipStatementSheetDefinitionTip: Please enter both german and englis SchoolAuthorshipStatementSheetExamDefinition: Statement of Authorship for exam-related exercise sheets SchoolAuthorshipStatementSheetExamDefinitionTip: Please enter both german and english statements. SchoolAuthorshipStatementSheetAllowOther: Allow adaptations for exam-unrelated exercise sheets? -SchoolAuthorshipStatementSheetExamAllowOther: Allow adaptations for exam-related exercise sheets? \ No newline at end of file +SchoolAuthorshipStatementSheetExamAllowOther: Allow adaptations for exam-related exercise sheets? + +DailyActDummy: Placholder without function \ No newline at end of file diff --git a/messages/uniworx/categories/settings/personal_settings/de-de-formal.msg b/messages/uniworx/categories/settings/personal_settings/de-de-formal.msg index cc01e920d..827732551 100644 --- a/messages/uniworx/categories/settings/personal_settings/de-de-formal.msg +++ b/messages/uniworx/categories/settings/personal_settings/de-de-formal.msg @@ -25,10 +25,14 @@ PersonalInfoTutorialsWip: Die Anzeige von Kurse, zu denen Sie angemeldet sind wi ProfileGroupSubmissionDates: Bei Gruppenabgaben wird kein Datum angezeigt, wenn Sie die Gruppenabgabe nie selbst hochgeladen haben. ProfileCorrectorRemark: Die oberhalb angezeigte Tabelle zeigt nur prinzipielle Einteilungen als Korrektor zu einem Übungsblatt. Auch ohne Einteilung können Korrekturen einzeln zugewiesen werden, welche hier dann nicht aufgeführt werden. ProfileCorrections: Auflistung aller zugewiesenen Korrekturen -Remarks: Hinweise +Remarks: Hinweis: -ProfileSupervisor: Übergeordnete Ansprechpartner -ProfileSupervisee: Ist Ansprechpartner für +ProfileNoSupervisor: Keine übergeordneten Ansprechpartner vorhanden +ProfileSupervisor n@Int m@Int: #{n} #{pluralDE n "übergeordneter" "übergeordnete"} Ansprechpartner#{noneMoreDE m "" (", davon " <> tshow m <> " mit Benachrichtigungsumleitung")} +ProfileSupervisorRemark n@Int m@Int l@Int: #{m} von #{n} #{pluralDE m "übergeordneter" "übergeordnete"} Ansprechpartner mit Benachrichtigungsumleitung#{noneMoreDE l "" (", davon " <> tshow l <> " mit postalischer Benachrichtigung")} +ProfileNoSupervisee: Ist kein Ansprechpartner für irgendjemand +ProfileSupervisee n@Int m@Int: Ist Ansprechpartner für #{n} #{pluralDE n "Person" "Personen"}#{noneMoreDE m "" (", davon " <> tshow m <> " mit Benachrichtigungsumleitung")} +ProfileSuperviseeRemark n@Int m@Int: Dieser Nutzer ist Ansprechpartner für #{n} #{pluralDE n "Person" "Personen"}#{noneMoreDE m "" (", davon " <> tshow m <> " mit Benachrichtigungsumleitung")} UserTelephone: Telefon UserMobile: Mobiltelefon diff --git a/messages/uniworx/categories/settings/personal_settings/en-eu.msg b/messages/uniworx/categories/settings/personal_settings/en-eu.msg index b61ac5678..db67e3940 100644 --- a/messages/uniworx/categories/settings/personal_settings/en-eu.msg +++ b/messages/uniworx/categories/settings/personal_settings/en-eu.msg @@ -25,10 +25,14 @@ PersonalInfoTutorialsWip: The feature to display courses you have registered for ProfileGroupSubmissionDates: No date is shown for group submissions if you have never uploaded the submission yourself. ProfileCorrectorRemark: The table above only shows registration as a corrector in principle. Even without registration corrections can be assigned individually and are not listed. ProfileCorrections: List of all assigned corrections -Remarks: Remarks +Remarks: Remark: -ProfileSupervisor: Supervised by -ProfileSupervisee: Supervises +ProfileNoSupervisor: Is not supervised by anynone +ProfileSupervisor n m: #{pluralENsN n "supervisor"} #{noneMoreEN m "" ("with " <> tshow m <> " active notification rerouting")} +ProfileSupervisorRemark n@Int m@Int l@Int: #{m} of #{n} #{pluralENs m "supervisor"} with active notification rerouting#{noneMoreEN l "" (", and " <> tshow l <> "of these prefer postal notifications")} +ProfileNoSupervisee: Does not supervise anynone +ProfileSupervisee n m: Supervises #{pluralENsN n "person"} #{noneMoreEN m "" ("with " <> tshow m <> " active notification rerouting")} +ProfileSuperviseeRemark n m: This person supervises #{pluralENsN n "person"}#{noneMoreEN m "" (" with " <> tshow m <> " having active notifications rerouting to this user")} UserTelephone: Phone UserMobile: Mobile diff --git a/messages/uniworx/categories/user/de-de-formal.msg b/messages/uniworx/categories/user/de-de-formal.msg index a3c630c46..737e627bf 100644 --- a/messages/uniworx/categories/user/de-de-formal.msg +++ b/messages/uniworx/categories/user/de-de-formal.msg @@ -22,6 +22,7 @@ AdminUserPostAddress: Postalische Anschrift AdminUserPrefersPostal: Briefe anstatt Email bevorzugt AdminUserPinPassword: Passwort zur Verschlüsselung von PDF Anhängen in Emails AdminUserNoPassword: Kein Passwort gesetzt +AdminUserPinPassNotIncluded: Hinweis: Das Passwort wird hier zur Bequemlichkeit zusätzlich angezeigt und ist selbstverständlich nicht im originalem Inhalt enthalten. AdminUserAssimilate: Diesen Benutzer assimilieren von UserAdded: Benutzer erfolgreich angelegt UserCollision: Benutzer konnte wegen Eindeutigkeit nicht angelegt werden @@ -37,9 +38,10 @@ AuthPWHashAlreadyConfigured: Nutzer:in meldet sich bereits mit FRADrive spezifis AuthPWHashConfigured: Nutzer:in meldet sich nun mit FRADrive spezifischer Kennung an UsersCourseSchool: Bereich ActionNoUsersSelected: Keine Benutzer:innen ausgewählt -SynchroniseAvsUserQueued n@Int: AVS-Synchronisation von #{n} #{pluralDE n "Benutzer:in" "Benutzer:innen"} angestoßen -SynchroniseLdapUserQueued n@Int: LDAP-Synchronisation von #{n} #{pluralDE n "Benutzer:in" "Benutzer:innen"} angestoßen -SynchroniseLdapAllUsersQueued: LDAP-Synchronisation von allen Benutzer:innen angestoßen +SynchroniseAvsUserQueued n@Int: AVS-Synchronisation von #{n} #{pluralDE n "Benutzer:in" "Benutzer:innen"} zwingend angestoßen, die Ausführung wird mehrere Minuten benötigen! +SynchroniseAvsAllUsersQueued n@Int64: AVS-Synchronisation von allen #{n} #{pluralDE n "Benutzer:in" "Benutzer:innen"} angestoßen, welche heute noch nicht synchronisiert wurden, die Ausführung wird eine Weile brauchen! +SynchroniseLdapUserQueued n@Int: LDAP-Synchronisation von #{n} #{pluralDE n "Benutzer:in" "Benutzer:innen"} angestoßen, die Ausführung wird mehrere Minuten benötigen! +SynchroniseLdapAllUsersQueued: LDAP-Synchronisation von allen Benutzer:innen angestoßen, die Ausführung kann eine Weile brauchen! UserListTitle: Komprehensive Benutzerliste AccessRightsSaved: Berechtigungen erfolgreich verändert AccessRightsNotChanged: Berechtigungen wurden nicht verändert @@ -89,12 +91,19 @@ NewPasswordLink: Neues Passwort setzen UserAccountDeleteWarning: Achtung, dies löscht den kompletten Benutzer/die komplette Benutzerin unwiderruflich und mit allen assoziierten Daten aus der Datenbank. Prüfungsdaten müssen jedoch langfristig gespeichert bleiben! UserAvsSync: AVS-Synchronisieren UserLdapSync: LDAP-Synchronisieren -AllUsersLdapSync: Alle LDAP-Synchronisieren UserHijack: Sitzung übernehmen UserAddSupervisor: Ansprechpartner hinzufügen UserSetSupervisor: Ansprechpartner ersetzen UserRemoveSupervisor: Alle Ansprechpartner entfernen +UserRemoveSubordinates: Alle Ansprechpartnerbeziehungen zu Untergebenen beenden UserIsSupervisor: Ist Ansprechpartner +UserAvsSwitchCompany: Als Primärfirma verwenden +UserAvsSwitchCompanyField: Primärfirma auswählen +UserAvsCompanySwitched c@CompanyShorthand: Primärfirma gewechselt zu #{tshow c} +AllUsersLdapSync: Alle LDAP-Synchronisieren +AllUsersAvsSync: Alle AVS-Synchronisieren +ThisUserLdapSync: LDAP Synchronisation +ThisUserAvsSync: AVS Synchronisation AuthKindLDAP: Fraport AG Kennung AuthKindPWHash: FRADrive Kennung AuthKindNoLogin: Kein Login möglich @@ -102,3 +111,9 @@ Name !ident-ok: Name UsersChangeSupervisorsSuccess usr@Int spr@Int: #{tshow spr} Ansprechpartner für #{tshow usr} Benutzer gesetzt. UsersChangeSupervisorsWarning usr@Int spr@Int bad@Int: Nur _{MsgUsersChangeSupervisorsSuccess usr spr} #{tshow bad} Ansprechpartner #{pluralDE bad "wurde" "wurden"} nicht gefunden! UsersRemoveSupervisors usr@Int: Alle Ansprechpartner für #{tshow usr} Benutzer gelöscht. +UsersRemoveSubordinates usr@Int: Alle Ansprechpartnerbeziehungen für #{tshow usr} #{pluralDE usr "ehemaligen" "ehemalige"} Ansprechpartner gelöscht. +UserCompanyReason: Begründung der Firmenassoziation +UserCompanyReasonTooltip: Optionale Notiz für besondere Fälle. Kann ggf. autmatische Entfernung bei AVS Firmenwechsel verhindern. +UserSupervisorReason: Begründung Ansprechpartner +UserSupervisorReasonTooltip: Optionale Notiz für besondere Fälle. Kann ggf. autmatische Entfernung bei AVS Firmenwechsel verhindern. +AdminUserAllNotifications: Alle Benachrichtigungen and diesen Benutzer \ No newline at end of file diff --git a/messages/uniworx/categories/user/en-eu.msg b/messages/uniworx/categories/user/en-eu.msg index 10c42830d..67ae441d8 100644 --- a/messages/uniworx/categories/user/en-eu.msg +++ b/messages/uniworx/categories/user/en-eu.msg @@ -22,6 +22,7 @@ AdminUserPostAddress: Postal Address AdminUserPrefersPostal: Prefers postal letters over email AdminUserPinPassword: Password used for PDF attachments to emails AdminUserNoPassword: No password set +AdminUserPinPassNotIncluded: Note: the password is shown here only for convenience, but is not contained in the original content, of course. AdminUserAssimilate: Assimilate user by another user UserAdded: Successfully added user UserCollision: Could not create user due to uniqueness constraint @@ -37,9 +38,10 @@ AuthPWHashAlreadyConfigured: User already logs in using their FRADrive specific AuthPWHashConfigured: User now logs in using their FRADrive specific account UsersCourseSchool: Department ActionNoUsersSelected: No users selected -SynchroniseAvsUserQueued n: Triggered AVS synchronisation of #{n} #{pluralEN n "user" "users"}. -SynchroniseLdapUserQueued n: Triggered LDAP synchronisation of #{n} #{pluralEN n "user" "users"}. -SynchroniseLdapAllUsersQueued: Triggered LDAP synchronisation of all users +SynchroniseAvsUserQueued n: Triggered forced AVS synchronisation of #{n} #{pluralEN n "user" "users"}, which may take several minutes to complete. +SynchroniseAvsAllUsersQueued n: Triggered AVS synchronisation of all #{n} #{pluralEN n "user" "users"} that were not already synchronised today, which may take quite a while to complete. +SynchroniseLdapUserQueued n: Triggered LDAP synchronisation of #{n} #{pluralEN n "user" "users"}, which may take several minutes to complete. +SynchroniseLdapAllUsersQueued: Triggered LDAP synchronisation of all users, which may take quite a while to complete. UserListTitle: Comprehensive list of users AccessRightsSaved: Successfully updated permissions AccessRightsNotChanged: Permissions left unchanged @@ -89,16 +91,29 @@ NewPasswordLink: Set password UserAccountDeleteWarning: Caution, this permanently deletes users and all of their associated data. Exam results must be stored long term! UserAvsSync: Synchronise with AVS UserLdapSync: Synchronise with LDAP -AllUsersLdapSync: Synchronise all with LDAP UserHijack: Hijack session UserAddSupervisor: Add supervisor UserSetSupervisor: Replace supervisors UserRemoveSupervisor: Set to unsupervised +UserRemoveSubordinates: Remove all subordinates UserIsSupervisor: Is supervisor +UserAvsSwitchCompany: Use as primary company +UserAvsSwitchCompanyField: Select primary company +UserAvsCompanySwitched c: Primary company switched to #{tshow c} +AllUsersLdapSync: Synchronise all with LDAP +AllUsersAvsSync: Synchronise all with AVS +ThisUserLdapSync: Synchronise user with LDAP +ThisUserAvsSync: Synchronise user with AVS AuthKindLDAP: Fraport AG account AuthKindPWHash: FRADrive account AuthKindNoLogin: No login Name: Name UsersChangeSupervisorsSuccess usr spr: #{pluralENsN spr "supervisor"} for #{pluralENsN usr "user"} set. UsersChangeSupervisorsWarning usr spr bad: Only _{MsgUsersChangeSupervisorsSuccess usr spr} #{pluralENsN bad "supervisors"} could not be identified! -UsersRemoveSupervisors usr: Removed all supervisors for #{pluralENsN usr "user"}. \ No newline at end of file +UsersRemoveSupervisors usr: Removed all supervisors for #{pluralENsN usr "user"}. +UsersRemoveSubordinates usr: Removed all subordinates for #{pluralENsN usr "previous supervisor"}. +UserCompanyReason: Reason for company association +UserCompanyReasonTooltip: Optional note for special cases. In some case this may prevent automatic removel upon AVS user company changes. +UserSupervisorReason: Reason for supervision +UserSupervisorReasonTooltip: Optional note for special cases. In some case this may prevent automatic removel upon AVS user company changes. +AdminUserAllNotifications: All notification sent to this user \ No newline at end of file diff --git a/messages/uniworx/misc/de-de-formal.msg b/messages/uniworx/misc/de-de-formal.msg index 3fcd6ffe6..f7cf7a561 100644 --- a/messages/uniworx/misc/de-de-formal.msg +++ b/messages/uniworx/misc/de-de-formal.msg @@ -4,24 +4,31 @@ #messages or constructors that are used all over the code -Logo !ident-ok: Uni2work -EmailInvitationWarning: Diese Adresse konnte keinem Uni2work-Benutzer/keiner Uni2work-Benutzerin zugeordnet werden. Es wird eine Einladung per E-Mail versandt. +Logo !ident-ok: FRADrive +EmailInvitationWarning: Diese Adresse konnte keinem FRADrive-Benutzer/-Benutzerin zugeordnet werden. Es wird eine Einladung per E-Mail versandt. BoolIrrelevant !ident-ok: — FieldPrimary: Hauptfach FieldSecondary: Nebenfach MultiEmailFieldTip: Es sind mehrere, Komma-separierte, E-Mail-Adressen möglich MultiSelectTip: Mehrfachauswahl und Abwählen mit Strg-Klick WeekDay: Wochentag +Hours: Stunden LdapIdentificationOrEmail: Fraport AG-Kennung / E-Mail-Adresse Months num@Int64: #{num} #{pluralDE num "Monat" "Monate"} Days num@Int64: #{num} #{pluralDE num "Tag" "Tage"} +NoAutomaticUpdateTip: Dieser Wert wurde manuell editiert und wird daher nicht mehr automatisch durch as AVS aktualisiert. +AddressIsLinkedTip: Verlinkte Postaddresse: Für diesen Benutzer ist keine individuelle Postadresse gespeichert, die Adresse wurde stattdessen aus der Firmenzugehörigkeit abgeleitet. ClusterVolatileQuickActionsEnabled: Schnellzugriffsmenü aktiv AvsNoLicence: Keine Fahrberechtigung AvsLicenceVorfeld: Vorfeld Fahrberechtigung AvsLicenceRollfeld: Rollfeld Fahrberechtigung +AvsNoLicenceGuest: Keine Fahrberechtigung (Gast, Fahrberechtigungserwerb nicht möglich) PaginationSize: Einträge pro Seite PaginationPage: Angzeigte Seite -PaginationError: Paginierung Parameter dürfen nicht negativ sein \ No newline at end of file +PaginationError: Paginierung Parameter dürfen nicht negativ sein + +NullDeletes: Zum Löschen NULL eingeben. +SortPriority: Sortierungspriorität \ No newline at end of file diff --git a/messages/uniworx/misc/en-eu.msg b/messages/uniworx/misc/en-eu.msg index ed8bda4db..da5d1efab 100644 --- a/messages/uniworx/misc/en-eu.msg +++ b/messages/uniworx/misc/en-eu.msg @@ -4,24 +4,31 @@ #messages or constructors that are used all over the Code -Logo: Uni2work -EmailInvitationWarning: This address could not be matched to any Uni2work user. An invitation will be sent via email. +Logo: FRADrive +EmailInvitationWarning: This address could not be matched to any FRADrive user. An invitation will be sent via email. BoolIrrelevant: — FieldPrimary: Major FieldSecondary: Minor MultiEmailFieldTip: Multiple emails addresses may be specified (comma-separated) MultiSelectTip: Multiple selection and desection via Ctrl-Click WeekDay: Day of the week +Hours: Hours LdapIdentificationOrEmail: Fraport AG-Kennung / email address Months num: #{num} #{pluralEN num "Month" "Months"} Days num: #{num} #{pluralEN num "Day" "Days"} +NoAutomaticUpdateTip: This particular value receives no automatic AVS updates, since it has been edited manually. +AddressIsLinkedTip: Linked postal address: No individual postal address is stored for this user, instead a postal address was inferred from the user's company association. ClusterVolatileQuickActionsEnabled: Quick actions enabled AvsNoLicence: No driving licence AvsLicenceVorfeld: Apron driving licence AvsLicenceRollfeld: Maneuvering area driving licence +AvsNoLicenceGuest: No driving licence (Guest account, cannot acquire a diriving licence) PaginationSize: Rows per Page PaginationPage: Page to show -PaginationError: Pagination parameter must not be negative \ No newline at end of file +PaginationError: Pagination parameter must not be negative + +NullDeletes: Enter NULL to delete. +SortPriority: Sort order priority \ No newline at end of file diff --git a/messages/uniworx/utils/handler_form/occurrences/de-de-formal.msg b/messages/uniworx/utils/handler_form/occurrences/de-de-formal.msg index e70c0a30d..24119b496 100644 --- a/messages/uniworx/utils/handler_form/occurrences/de-de-formal.msg +++ b/messages/uniworx/utils/handler_form/occurrences/de-de-formal.msg @@ -20,3 +20,7 @@ ExceptionNoOccurAt: Termin ExceptionKind: Termin ... ExceptionKindOccur: Findet statt ExceptionKindNoOccur: Findet nicht statt +DayNext: Folgetag +DayPrev: Vortag +WeekNext: Nächste Woche +WeekPrev: Vorherige Woche diff --git a/messages/uniworx/utils/handler_form/occurrences/en-eu.msg b/messages/uniworx/utils/handler_form/occurrences/en-eu.msg index 1c325ea7f..62f629add 100644 --- a/messages/uniworx/utils/handler_form/occurrences/en-eu.msg +++ b/messages/uniworx/utils/handler_form/occurrences/en-eu.msg @@ -20,3 +20,7 @@ ExceptionNoOccurAt: Event ExceptionKind: Event ... ExceptionKindOccur: Does occur ExceptionKindNoOccur: Does not occur +DayNext: Next day +DayPrev: Previous day +WeekNext: Next week +WeekPrev: Previous week \ No newline at end of file diff --git a/messages/uniworx/utils/navigation/menu/de-de-formal.msg b/messages/uniworx/utils/navigation/menu/de-de-formal.msg index eab4f204e..ae3990d41 100644 --- a/messages/uniworx/utils/navigation/menu/de-de-formal.msg +++ b/messages/uniworx/utils/navigation/menu/de-de-formal.msg @@ -97,6 +97,7 @@ MenuExamOfficeUsers: Benutzer:innen MenuLecturerInvite: Funktionäre hinzufügen MenuSchoolList: Bereiche MenuSchoolNew: Neuen Bereich anlegen +MenuSchoolDay ssh@SchoolId d@Text: #{unSchoolKey ssh} #{d} Tagesansicht MenuExternalExamGrades: Prüfungsleistungen MenuExternalExamUsers: Teilnehmer:innen MenuExternalExamEdit: Bearbeiten @@ -143,12 +144,18 @@ MenuSap: SAP Schnittstelle MenuAvs: AVS Schnittstelle MenuAvsSynchError: AVS Problemübersicht MenuLdap: LDAP Schnittstelle -MenuApc: Druckerei +MenuApc: Druck MenuPrintSend: Manueller Briefversand MenuPrintDownload: Brief herunterladen MenuPrintLog: LPR Schnittstelle MenuPrintAck: Druckbestätigung +MenuCommCenter: Benachrichtigungen +MenuMailCenter: E‑Mails +MenuMailHtml !ident-ok: Html +MenuMailPlain !ident-ok: Text +MenuMailAttachment: Anhang + MenuApiDocs: API-Dokumentation (Englisch) MenuSwagger !ident-ok: OpenAPI 2.0 (Swagger) diff --git a/messages/uniworx/utils/navigation/menu/en-eu.msg b/messages/uniworx/utils/navigation/menu/en-eu.msg index 526c6d871..c8775ef4e 100644 --- a/messages/uniworx/utils/navigation/menu/en-eu.msg +++ b/messages/uniworx/utils/navigation/menu/en-eu.msg @@ -97,6 +97,7 @@ MenuExamOfficeUsers: Users MenuLecturerInvite: Add functionaries MenuSchoolList: Departments MenuSchoolNew: Create new department +MenuSchoolDay ssh d: #{unSchoolKey ssh} #{d} Day MenuExternalExamGrades: Exam results MenuExternalExamUsers: Participants MenuExternalExamEdit: Edit @@ -143,12 +144,18 @@ MenuSap: SAP Interface MenuAvs: AVS Interface MenuAvsSynchError: AVS Problem Overview MenuLdap: LDAP Interface -MenuApc: Printing +MenuApc: Print MenuPrintSend: Send Letter MenuPrintDownload: Download Letter MenuPrintLog: LPR Interface MenuPrintAck: Acknowledge Printing +MenuCommCenter: Notifications +MenuMailCenter: Email +MenuMailHtml: Html +MenuMailPlain: Text +MenuMailAttachment: Attachment + MenuApiDocs: API documentation MenuSwagger: OpenAPI 2.0 (Swagger) diff --git a/messages/uniworx/utils/table_column/de-de-formal.msg b/messages/uniworx/utils/table_column/de-de-formal.msg index 0a67481af..ee6890725 100644 --- a/messages/uniworx/utils/table_column/de-de-formal.msg +++ b/messages/uniworx/utils/table_column/de-de-formal.msg @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Gregor Kleen ,Sarah Vaupel ,Steffen Jost ,Winnie Ros +# SPDX-FileCopyrightText: 2022-24 Gregor Kleen ,Sarah Vaupel ,Steffen Jost ,Winnie Ros , Steffen Jost # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -48,11 +48,11 @@ TableNotPassed: Nicht bestanden TableTutorialTutors: Ausbilder TableTutorialName: Bezeichnung TableTutorialType: Art -TableTutorialRoom: Regulärer Raum +TableTutorialRoom: Raum TableTutorialRoomHidden: Raum nur für Teilnehmer TableTutorialRoomIsUnset !ident-ok: — TableTutorialRoomIsHidden: Raum wird nur Teilnehmern angezeigt -TableTutorialTime: Zeit +TableTutorialOccurrence: Termin TableTutorialDeregisterUntil: Abmeldungen bis TableTutorialFirstDay: Starttag TableActionsHead: Aktionen @@ -73,15 +73,20 @@ TableDiffDaysTooltip: Zeitspanne nach ISO 8601. Beispiel: "P2Y3M4D" ist eine Zei TableExamOfficeLabel: Label-Name TableExamOfficeLabelStatus: Label-Farbe TableExamOfficeLabelPriority: Label-Priorität +TableQualification: Qualifikation TableQualifications: Qualifikationen TableCompany: Firma TableCompanyFilter: Firma oder Nummer TableCompanyShort: Firmenkürzel TableCompanies: Firmen +TablePrimeCompany: Primäre Firma +TableBookingCompany: Buchende Firma TableCompanyNo: Firmennummer TableCompanyNos: Firmennummern TableCompanyUser: Firmenangehöriger TableCompanyNrUsers: Firmenangehörige +TableCompanyNrSecondaryUsers: Sekundäre Firmenangehörige +TableCompanyReason: Notiz TableCompanyNrSupers: Ansprechpartner TableCompanyNrEmpSupervised: Firmenangehörige mit Ansprechpartner TableCompanyNrEmpRerouted: Firmenangehörige mit Umleitung @@ -91,8 +96,12 @@ TableCompanyNrSupersDefault: Standard Ansprechpartner TableCompanyNrForeignSupers: Firmenfremde Ansprechpartner TableCompanyNrRerouteDefault: Standard Umleitungen TableCompanyNrRerouteActive: Aktive Umleitungen +TableRerouteActive: Umleitung TableCompanyPostalPreference: Benachrichtigungspräferenz neue Firmenangehörige TableSupervisor: Ansprechpartner +TableSupervisorActive: Aktiver Ansprechpartner +TableSupervisee: Ansprechpartner für +TableReason: Begründung TableCreationTime: Erstellungszeit TableJob !ident-ok: Job TableJobContent !ident-ok: Parameter @@ -100,10 +109,13 @@ TableJobLockTime: Bearbeitung seit TableJobLockInstance: Bearbeiter TableJobCreationInstance: Ersteller ActJobDelete: Job entfernen +ActJobDeleteForce n@Int: Auch vor #{pluralDEnN n "Minute"} gesperrte Jobs entfernen TableJobActDeleteFeedback n@Int m@Int: #{n}/#{m} Jobs entfernt TableFilterComma: Es können mehrere alternative Suchkriterien mit Komma getrennt angegeben werden, wovon mindestens eines erfüllt werden muss. TableFilterCommaPlus: Mehrere alternative Suchkriterien mit Komma trennen. Mindestens ein Suchkriterium muss erfüllt werden, zusätzlich zu allen Suchkriterien mit vorangestelltem Plus-Symbol. +TableFilterCommaPlusShort: Unterstützt mehrere Kriterien mit Komma-Plus, siehe oben. TableFilterCommaName: Mehrere Namen mit Komma trennen. TableFilterCommaNameNr: Mehrere Namen oder Nummern mit Komma trennen. Nummern werden nur exakt gesucht. TableUserEdit: Benutzer bearbeiten -TableRows: Zeilen \ No newline at end of file +TableRows: Zeilen +TableUserParkingToken: Parkmarke \ No newline at end of file diff --git a/messages/uniworx/utils/table_column/en-eu.msg b/messages/uniworx/utils/table_column/en-eu.msg index e7ae23a14..0ecdd9a0a 100644 --- a/messages/uniworx/utils/table_column/en-eu.msg +++ b/messages/uniworx/utils/table_column/en-eu.msg @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Sarah Vaupel ,Steffen Jost ,Winnie Ros +# SPDX-FileCopyrightText: 2022-24 Sarah Vaupel ,Steffen Jost ,Winnie Ros , Steffen Jost # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -48,14 +48,14 @@ TableNotPassed: Failed TableTutorialTutors: Instructors TableTutorialName: Name TableTutorialType: Type -TableTutorialRoom: Regular room +TableTutorialRoom: Room TableTutorialRoomHidden: Room only for participants TableTutorialRoomIsUnset: — TableTutorialRoomIsHidden: Room is only displayed to participants TableTutorialDeregisterUntil: Deregister until TableTutorialFirstDay: Start date TableActionsHead: Actions -TableTutorialTime: Time +TableTutorialOccurrence: Session TableNoFilter: No restriction TableUserMatriculation: AVS number TableColumnStudyFeatures: Features of study @@ -73,15 +73,20 @@ TableDiffDaysTooltip: Duration given according to ISO 8601. Example: "P2Y3M4D" i TableExamOfficeLabel: Label name TableExamOfficeLabelStatus: Label colour TableExamOfficeLabelPriority: Label priority +TableQualification: Qualification TableQualifications: Qualifications TableCompany: Company TableCompanyFilter: Company/Nr TableCompanyShort: Company shorthand TableCompanies: Companies +TablePrimeCompany: Primary company +TableBookingCompany: Booking company TableCompanyNo: Company number TableCompanyNos: Company numbers TableCompanyUser: Associate TableCompanyNrUsers: Associates +TableCompanyNrSecondaryUsers: Secondary Associates +TableCompanyReason: Note TableCompanyNrSupers: Supervisors TableCompanyNrEmpSupervised: Supervised employees TableCompanyNrEmpRerouted: Employees having reroute @@ -91,8 +96,12 @@ TableCompanyNrSupersDefault: Default supervisors TableCompanyNrForeignSupers: External Supervisors TableCompanyNrRerouteDefault: Default reroutes TableCompanyNrRerouteActive: Active reroutes +TableRerouteActive: Reroute TableCompanyPostalPreference: Default notification preference TableSupervisor: Supervisor +TableSupervisorActive: Active supervisor +TableSupervisee: Supervisor for +TableReason: Reason TableCreationTime: Creation TableJob !ident-ok: Job TableJobContent !ident-ok: Parameters @@ -100,10 +109,13 @@ TableJobLockTime: Lock time TableJobLockInstance: Worker TableJobCreationInstance: Creator ActJobDelete: Delete job +ActJobDeleteForce n: Also delete jobs locked #{pluralENsN n "minute"} ago TableJobActDeleteFeedback n@Int m@Int: #{n}/#{m} queued jobs deleted TableFilterComma: Separate multiple alternative filter criteria by comma, at least one of which must be fulfilled. TableFilterCommaPlus: Separate multiple alternative filter criteria by comma, at least one of which must be fulfilled in addition to all criteria preceded by a plus symbol. +TableFilterCommaPlusShort: Support multiple criteria with comma/plus, see above. TableFilterCommaName: Separate names by comma. TableFilterCommaNameNr: Separate names and numbers by comma. Numbers have to match exact. TableUserEdit: Edit user -TableRows: Rows \ No newline at end of file +TableRows: Rows +TableUserParkingToken: Parking token \ No newline at end of file diff --git a/messages/uniworx/utils/utils/de-de-formal.msg b/messages/uniworx/utils/utils/de-de-formal.msg index 5ff122fb1..4b9e83764 100644 --- a/messages/uniworx/utils/utils/de-de-formal.msg +++ b/messages/uniworx/utils/utils/de-de-formal.msg @@ -25,6 +25,7 @@ RGTutorialParticipants tutn@TutorialName: Kursteilnehmer:innen (#{tutn}) RGExamRegistered examn@ExamName: Angemeldet zur Prüfung „#{examn}“ RGSheetSubmittor shn@SheetName: Abgebende für das Übungsblatt „#{shn}“ CommSubject: Betreff +CommContent: Inhalt CommAttachments: Anhänge CommAttachmentsTip: Im Allgemeinen ist es vorzuziehen Dateien, die Sie mit den Empfängern teilen möchten, als Material hochzuladen (und ggf. in der Nachricht zu verlinken). So ist die Datei für die Empfänger dauerhaft abrufbar und auch Personen, die sich z.B. erst später zur Kursart anmelden, haben Zugriff auf die Datei. CommSuccess n@Int: Nachricht wurde an #{n} Empfänger versandt @@ -82,6 +83,7 @@ MultiUserFieldInvitationExplanationAlways: Es wird an alle Adressen, die Sie hie AmbiguousEmail: E-Mail-Adresse nicht eindeutig InvalidEmailAddress: E-Mail-Adresse ist ungültig InvalidEmailAddressWith e@Text: E-Mail-Adresse #{show e} ist ungültig +MailFileAttachment: Dateianhang UtilExamResultGrade: Note UtilExamResultPass: Bestanden/Nicht Bestanden UtilExamResultNoShow: Nicht erschienen @@ -89,6 +91,7 @@ UtilExamResultVoided: Entwertet CourseOption tid@TermId ssh@SchoolId csh@CourseShorthand coursen@CourseName !ident-ok: #{tid} - #{ssh} - #{csh}: #{coursen} RoomReferenceNone !ident-ok: — RoomReferenceSimple !ident-ok: Text +RoomReferenceSimpleAt r@Text: in Raum #{r} RoomReferenceLink: Link & Anweisungen RoomReferenceSimpleText: Raum RoomReferenceSimpleTextPlaceholder: Raum @@ -96,6 +99,7 @@ RoomReferenceLinkLink !ident-ok: Link RoomReferenceLinkLinkPlaceholder !ident-ok: URL RoomReferenceLinkInstructions: Anweisungen RoomReferenceLinkInstructionsPlaceholder: Anweisungen +UtilNoneSet: Keine angegeben UtilEmptyChoice: Auswahl war leer UtilEmptyNoChangeTip: Eine leere Eingabe belässt den vorherigen Wert unverändert. MultiNoSelection: Keine Auswahl diff --git a/messages/uniworx/utils/utils/en-eu.msg b/messages/uniworx/utils/utils/en-eu.msg index f65004cd1..c4c694c69 100644 --- a/messages/uniworx/utils/utils/en-eu.msg +++ b/messages/uniworx/utils/utils/en-eu.msg @@ -25,6 +25,7 @@ RGTutorialParticipants tutn: Course participants (#{tutn}) RGExamRegistered examn: Registered for exam “#{examn}” RGSheetSubmittor shn: Submitted for exercise sheet “#{shn}” CommSubject: Subject +CommContent: Content CommAttachments: Attachments CommAttachmentsTip: In general it is preferable to upload files as course type material instead of sending them as attachments. You can then link to the material from the message. The file is then permanently accessable to the recipients and to persons that, for example, register for the Course type at a later date. CommSuccess n: Message was sent to #{n} #{pluralEN n "recipient" "recipients"} @@ -82,6 +83,7 @@ MultiUserFieldInvitationExplanationAlways: An invitation will be sent via email AmbiguousEmail: Email address is ambiguous InvalidEmailAddress: Email address is invalid InvalidEmailAddressWith e: Email asdress #{show e} is invalid +MailFileAttachment: Attached file UtilExamResultGrade: Grade UtilExamResultPass: Passed/Failed UtilExamResultNoShow: Not present @@ -89,6 +91,7 @@ UtilExamResultVoided: Voided CourseOption tid ssh csh coursen: #{tid} - #{ssh} - #{csh}: #{coursen} RoomReferenceNone: — RoomReferenceSimple: Text +RoomReferenceSimpleAt r: at room #{r} RoomReferenceLink: Link & Instructions RoomReferenceSimpleText: Room RoomReferenceSimpleTextPlaceholder: Room @@ -96,6 +99,7 @@ RoomReferenceLinkLink: Link RoomReferenceLinkLinkPlaceholder: URL RoomReferenceLinkInstructions: Instructions RoomReferenceLinkInstructionsPlaceholder: Instructions +UtilNoneSet: None set UtilEmptyChoice: Empty selection UtilEmptyNoChangeTip: Existing values remain unchanged if this field is left empty. MultiNoSelection: No selection diff --git a/models/audit.model b/models/audit.model index 3cd567a13..8a372cd3b 100644 --- a/models/audit.model +++ b/models/audit.model @@ -1,4 +1,4 @@ --- SPDX-FileCopyrightText: 2022-23 Gregor Kleen ,Steffen Jost +-- SPDX-FileCopyrightText: 2022-24 Gregor Kleen ,Steffen Jost -- -- SPDX-License-Identifier: AGPL-3.0-or-later @@ -8,7 +8,7 @@ TransactionLog instance InstanceId initiator UserId Maybe -- User associated with performing this action remote IP Maybe -- Remote party that triggered this action via HTTP - info Value -- JSON-encoded `Transaction` + info Value -- JSON-encoded `Transaction`. Value allows full backwards compatibility deriving Eq Read Show Generic InterfaceLog @@ -26,6 +26,13 @@ InterfaceHealth interface Text subtype Text Maybe write Bool Maybe - hours Int + hours Int -- negative number: never expires, i.e. if the last entry is a success, this remains indefinitely UniqueInterfaceHealth interface subtype write !force -- Note that nullable fields must be either empty or unique deriving Eq Read Show Generic + +ProblemLog + time UTCTime default=now() + info Value -- generic JSON Value allows maximum backwards compatibility + solved UTCTime Maybe + solver UserId Maybe -- User who marked this problem as done + deriving Eq Read Show Generic \ No newline at end of file diff --git a/models/avs.model b/models/avs.model index 7a8a59cc0..ee4dd9a19 100644 --- a/models/avs.model +++ b/models/avs.model @@ -1,4 +1,4 @@ --- SPDX-FileCopyrightText: 2022 Steffen Jost +-- SPDX-FileCopyrightText: 2022-24 Steffen Jost -- -- SPDX-License-Identifier: AGPL-3.0-or-later @@ -16,27 +16,19 @@ UserAvs personId AvsPersonId -- unique identifier for user throughout avs; newtype for Int user UserId - noPerson Int default=0 -- only needed for manual communication with personnel from Ausweisverwaltungsstelle + noPerson Int default=0 -- only needed for manual communication with personnel from Ausweisverwaltungsstelle, redundant since needed for filtering lastSynch UTCTime default=now() lastSynchError Text Maybe + lastPersonInfo AvsPersonInfo Maybe -- just to discern field changes + lastFirmInfo AvsFirmInfo Maybe -- just to discern field changes + lastCardNo AvsFullCardNo Maybe -- just to discern changes UniqueUserAvsUser user UniqueUserAvsId personId deriving Generic Show --- Multiple UserAvsCards per UserAvs is possible and not too uncommon. --- Purpose of saving cards is to detect external changes in qualifications and postal addresses --- TODO: This table will be deleted if AVS CR3 SCF-165 is implemented -UserAvsCard - personId AvsPersonId - cardNo AvsFullCardNo - card AvsDataPersonCard - lastSynch UTCTime - -- UniqueAvsCard cardNo -- Note: cardNo is not unique; invalid cardNo may be reissued to different persons - deriving Generic - AvsSync user UserId -- Note: we need to lookup UserAvs Entity anyway, so no benefit from storing AvsPersonId here creationTime UTCTime - pause Day Maybe + pause Day Maybe -- Don't synch if last synch after this day, otherwise synch UniqueAvsSyncUser user - deriving Generic \ No newline at end of file + deriving Generic Show \ No newline at end of file diff --git a/models/company.model b/models/company.model index c022ad5f1..0d3d07ce9 100644 --- a/models/company.model +++ b/models/company.model @@ -1,25 +1,18 @@ --- SPDX-FileCopyrightText: 2022 Sarah Vaupel ,Steffen Jost +-- SPDX-FileCopyrightText: 2022-24 Sarah Vaupel ,Steffen Jost -- -- SPDX-License-Identifier: AGPL-3.0-or-later -- Description of companies associated with users Company - name CompanyName -- == (CI Text) - shorthand CompanyShorthand -- == (CI Text) and CompanyKey :: CompanyShorthand -> CompanyId FUTURE TODO: a shorthand will become available through the AVS interface in the future - avsId Int default=0 -- primary key from avs - prefersPostal Bool default=false -- new company users prefers letters by post instead of email - postAddress StoredMarkup Maybe -- default company postal address - email UserEmail Maybe -- Case-insensitive generic company eMail address - UniqueCompanyName name - UniqueCompanyShorthand shorthand - -- UniqueCompanyAvsId avsId -- should be the case, unclear if enforcing works here, since we cannot query avs by company id - Primary shorthand -- newtype Key Company = CompanyKey { unCompanyKey :: CompanyShorthand } + name CompanyName -- == (CI Text) -- NOTE: Fraport department name may carry additional information; use the Shorthand with respect to UserCompanyDepartment + shorthand CompanyShorthand -- == (CI Text) and CompanyKey :: CompanyShorthand -> CompanyId A change to AvsId as primary key is too much work and not strictly necessary due to Uniqueness + avsId Int default=0 -- primary key from avs, use negative numbers for non-AVS companies + prefersPostal Bool default=true -- new company users prefers letters by post instead of email + postAddress StoredMarkup Maybe -- default company postal address, including company name + email UserEmail Maybe -- Case-insensitive generic company eMail address + -- UniqueCompanyName name -- Should be Unique in AVS, but we do not yet need to enforce it + -- UniqueCompanyShorthand shorthand -- unnecessary, since it is the primary key already + UniqueCompanyAvsId avsId -- Should be the key, is not for historical reasons and for convenience in URLs and columns + Primary shorthand -- newtype Key Company = CompanyKey { unCompanyKey :: CompanyShorthand } deriving Ord Eq Show Generic Binary - --- TODO: a way to populate this table (manually) -CompanySynonym - synonym CompanyName - canonical CompanyShorthand OnDeleteCascade OnUpdateCascade - UniqueCompanySynonym synonym - deriving Ord Eq Show Generic diff --git a/models/courses.model b/models/courses.model index ded2013dd..594bbf48e 100644 --- a/models/courses.model +++ b/models/courses.model @@ -28,13 +28,12 @@ Course -- Information about a single course; contained info is always visible TermSchoolCourseName term school name -- name must be unique within school and semester deriving Generic CourseEvent - type (CI Text) - course CourseId OnDeleteCascade OnUpdateCascade - room RoomReference Maybe - roomHidden Bool default=false - time Occurrences - note StoredMarkup Maybe - lastChanged UTCTime default=now() + type (CI Text) + course CourseId OnDeleteCascade OnUpdateCascade + roomHidden Bool default=false + time (JSONB Occurrences) + note StoredMarkup Maybe + lastChanged UTCTime default=now() deriving Generic CourseAppInstructionFile diff --git a/models/jobs.model b/models/jobs.model index 98aa8c3b8..bc24e931f 100644 --- a/models/jobs.model +++ b/models/jobs.model @@ -20,11 +20,11 @@ CronLastExec time UTCTime -- When was the job executed instance InstanceId -- Which uni2work-instance did the work UniqueCronLastExec job - deriving Generic + deriving Generic Show TokenBucket ident TokenBucketIdent lastValue Int64 lastAccess UTCTime Primary ident - deriving Generic \ No newline at end of file + deriving Generic Show \ No newline at end of file diff --git a/models/lms.model b/models/lms.model index 9e96df730..50b686fcf 100644 --- a/models/lms.model +++ b/models/lms.model @@ -13,16 +13,18 @@ Qualification refreshWithin CalendarDiffDays Maybe -- notify users about renewal within this number of month/days before expiry; to be used with addGregorianDurationClip refreshReminder CalendarDiffDays Maybe -- send a second notification about renewal within this number of month/days before expiry elearningStart Bool -- automatically schedule e-refresher - -- elearningOnly Bool -- successful E-learing automatically increases validity. NO! + elearningRenews Bool default=true -- successful e-learing automatically increases validity automatically by validDuration + elearningLimit Int Maybe -- limit of e-learning attempts, currently only for informative purposes, as it is enforced by LMS only + lmsReuses QualificationId Maybe -- if set, lms is also included within the given qualification's lms, but only for direct routes. AuditDuration is used from this Qualification instead. expiryNotification Bool default=true -- should expiryNotification be generated for this qualification? avsLicence AvsLicence Maybe -- if set, valid QualificationUsers are synchronized to AVS as a driving licence - sapId Text Maybe -- if set, valid QualificationUsers with userCompanyPersonalNumber are transmitted via SAP interface under this id + sapId Text Maybe -- if set, valid QualificationUsers with userCompanyPersonalNumber are transmitted via SAP interface under this id SchoolQualificationShort school shorthand -- must be unique per school and shorthand SchoolQualificationName school name -- must be unique per school and name - -- across all schools, only one qualification may be a driving licence: - UniqueQualificationAvsLicence avsLicence !force -- either empty or unique + -- across all schools, only one qualification may be a driving licence -- NO LONGER TRUE + -- UniqueQualificationAvsLicence avsLicence !force -- either empty or unique -- NOTE: two NULL values are not equal for the purpose of Uniqueness constraints! - deriving Eq Generic + deriving Show Eq Generic Binary -- TODOs: -- - Enstehen Kosten, wenn Teilnehmer für KnowHow eingereiht werden, aber nicht am Kurs teilnehmen? @@ -40,19 +42,20 @@ Qualification -- - PinReset==1 mit bestehendem Passwort kann problemlos erneut gesendet werden -- - Flag "interner Mitarbeiter" wird von Know-How ignoriert / nicht ausgewertet (legacy) -QualificationPrecondition -- NOTE: this can only be enforced through a background job adding or removing qualifications - qualification QualificationId OnDeleteCascade OnUpdateCascade -- AND: not unique, ie. qualification can have multiple required preconditions - required [QualificationId] -- OR : alternatives, any one will suffice - continuous Bool -- expiring precondition blocks qualification - deriving Generic +-- QualificationPrecondition -- NOTE: this can only be enforced through a background job adding or removing qualifications +-- qualification QualificationId OnDeleteCascade OnUpdateCascade -- AND: not unique, ie. qualification can have multiple required preconditions +-- required [QualificationId] -- OR : alternatives, any one will suffice -- we don't want array, since we have recursive CTEs +-- continuous Bool -- expiring precondition blocks qualification +-- deriving Generic Show -- Maybe an alternative for online qualification validity checking, transitivity through recursive CTEs? (already available in our version) --- QualificationRequirement --- qualification QualificationId OnDeleteCascade OnUpdateCascade --- requirement QualificationId OnDeleteCascade OnUpdateCascade --- group Text -- OR: several requirements within the same group are considered equivalent --- UniqueQualificationRequirement qualification requirement --- +QualificationRequirement + qualification QualificationId OnDeleteCascade OnUpdateCascade + requirement QualificationId OnDeleteCascade OnUpdateCascade + group Int -- OR: several requirements within the same group are considered equivalent; no order between groups + note Text -- for humans only, no semantical effect + UniqueQualificationRequirement qualification requirement + deriving Generic Show -- TODO: connect Qualification with Exams! @@ -60,7 +63,7 @@ QualificationEdit user UserId time UTCTime qualification QualificationId OnDeleteCascade OnUpdateCascade - deriving Generic + deriving Generic Show QualificationUser user UserId OnDeleteCascade OnUpdateCascade @@ -69,11 +72,11 @@ QualificationUser lastRefresh Day -- lastRefresh > validUntil possible, if Qualification^elearningOnly == False firstHeld Day -- first time the qualification was earned, should never change scheduleRenewal Bool default=true -- if false, no automatic renewal is scheduled and the qualification expires - lastNotified UTCTime default=now() -- last notficiation about being invalid + lastNotified UTCTime default=now() -- last notficiation about actual licence validity changes (does not entail e-learning notifications) -- Reasons and temporary revocations are implemented through QualificationUserBlock -- TODO: adjust SAP interface to transmit end dates UniqueQualificationUser qualification user - deriving Generic + deriving Generic Show QualificationUserBlock qualificationUser QualificationUserId OnDeleteCascade OnUpdateCascade @@ -130,7 +133,7 @@ LmsUser -- Primary ident -- newtype Key LmsUserId = LmsUserKey { unLmsUser :: Text } -- change LmsIdent -> Text. Do we want this? No. UniqueLmsIdent ident -- idents must be unique accross all qualifications, since idents are global within LMS! UniqueLmsQualificationUser qualification user -- each user may be enrolled at most once per course - deriving Generic + deriving Generic Show -- LmsUserStatus -- lmsUser LmsUserId OnDeleteCascade OnUpdateCascade @@ -148,7 +151,7 @@ LmsReport lock Bool -- (0|1) timestamp UTCTime default=now() UniqueLmsReport qualification ident -- required by DBTable - deriving Generic + deriving Generic Show -- LmsAudit removed by commit 71cde92a -- due to frequent transmit errors, a separate lms tranmission log is necessary again @@ -160,4 +163,4 @@ LmsReportLog lock Bool -- (0|1) timestamp UTCTime default=now() missing Bool default=false - deriving Generic \ No newline at end of file + deriving Generic Show \ No newline at end of file diff --git a/models/print.model b/models/print.model index ee22cf922..94d4a3dc8 100644 --- a/models/print.model +++ b/models/print.model @@ -10,22 +10,23 @@ PrintJob created UTCTime acknowledged UTCTime Maybe recipient UserId Maybe OnDeleteSetNull OnUpdateCascade -- optional as some letters may contain just an address + affected UserId Maybe OnDeleteSetNull OnUpdateCascade -- subject of the letter sender UserId Maybe OnDeleteSetNull OnUpdateCascade -- senders and associations are optional course CourseId Maybe OnDeleteCascade OnUpdateCascade qualification QualificationId Maybe OnDeleteCascade OnUpdateCascade lmsUser LmsIdent Maybe OnDeleteSetNull OnUpdateCascade -- allows tracking if recipient has been notified; must be unique -- UniquePrintJobLmsUser lmsUser -- Note that in fact multiple print jobs per LMS user are possible! -- UniquePrintJobApcIdent apcIdent -- TODO: not yet enforced, since LmsIdent is currently used - deriving Generic + deriving Generic Show PrintAcknowledge -- just to store acknowledging requests to be evaluated by a background job later on apcIdent Text timestamp UTCTime default=now() processed Bool - deriving Generic + deriving Generic Show PrintAckIdAlias needle Text replacement Text priority Int - deriving Generic \ No newline at end of file + deriving Generic Show \ No newline at end of file diff --git a/models/tutorials.model b/models/tutorials.model index be27d6a87..c1e237344 100644 --- a/models/tutorials.model +++ b/models/tutorials.model @@ -6,10 +6,9 @@ Tutorial json name TutorialName course CourseId OnDeleteCascade OnUpdateCascade type (CI Text) -- "Tutorium", "Zentralübung", ... - capacity Int Maybe -- limit for enrolment in this tutorial - room RoomReference Maybe + capacity Int Maybe -- limit for enrolment in this tutorial roomHidden Bool default=false - time Occurrences + time (JSONB Occurrences) regGroup (CI Text) Maybe -- each participant may register for one tutorial per regGroup registerFrom UTCTime Maybe registerTo UTCTime Maybe @@ -25,8 +24,19 @@ Tutor UniqueTutor tutorial user deriving Generic TutorialParticipant - tutorial TutorialId OnDeleteCascade OnUpdateCascade - user UserId + tutorial TutorialId OnDeleteCascade OnUpdateCascade + user UserId + company CompanyId Maybe + drivingPermit UserDrivingPermit Maybe + eyeExam UserEyeExam Maybe + note Text Maybe UniqueTutorialParticipant tutorial user - deriving Eq Ord Show - deriving Generic \ No newline at end of file + deriving Eq Ord Show Generic +TutorialParticipantDay + tutorial TutorialId OnDeleteCascade OnUpdateCascade + user UserId OnDeleteCascade OnUpdateCascade + day Day + attendance Bool default=true + note Text Maybe + UniqueTutorialParticipantDay tutorial user day + deriving Show Generic \ No newline at end of file diff --git a/models/users.model b/models/users.model index b23fe85b2..96761a200 100644 --- a/models/users.model +++ b/models/users.model @@ -1,4 +1,4 @@ --- SPDX-FileCopyrightText: 2022 Gregor Kleen ,Sarah Vaupel ,Steffen Jost ,Steffen Jost +-- SPDX-FileCopyrightText: 2022-24 Gregor Kleen ,Sarah Vaupel ,Steffen Jost ,Steffen Jost -- -- SPDX-License-Identifier: AGPL-3.0-or-later @@ -14,14 +14,14 @@ User json -- Each Uni2work user has a corresponding row in this table; created upon first login. surname UserSurname -- Display user names always through 'nameWidget displayName surname' displayName UserDisplayName - displayEmail UserEmail - email UserEmail -- Case-insensitive eMail address, used for sending TODO: make this nullable - ident UserIdent -- Case-insensitive user-identifier + displayEmail UserEmail -- Case-insensitive eMail address, used for sending; leave empty for using auto-update CompanyEmail via UserCompany + email UserEmail -- Case-insensitive eMail address, used for identification and fallback for sending. Defaults to "AVSNO:dddddddd" if unknown + ident UserIdent -- Case-insensitive user-identifier. Defaults to "AVSID:dddddddd" if unknown authentication AuthenticationMode -- 'AuthLDAP' or ('AuthPWHash'+password-hash) lastAuthentication UTCTime Maybe -- last login date created UTCTime default=now() lastLdapSynchronisation UTCTime Maybe - ldapPrimaryKey UserEduPersonPrincipalName Maybe + ldapPrimaryKey UserEduPersonPrincipalName Maybe -- Fraport Personnel Number or Email-Prefix for @fraport.de work here tokensIssuedAfter UTCTime Maybe -- do not accept bearer tokens issued before this time (accept all tokens if null) matrikelnummer UserMatriculation Maybe -- usually a number; AVS Personalnummer; nicht Fraport Personalnummer! firstName Text -- For export in tables, pre-split firstName from displayName @@ -44,9 +44,9 @@ User json -- Each Uni2work user has a corresponding row in this table; create mobile Text Maybe companyPersonalNumber Text Maybe -- Company will become a new table, but if company=fraport, some information is received via LDAP companyDepartment Text Maybe -- thus we store such information for ease of reference directly, if available - pinPassword Text Maybe -- used to encrypt pins within emails - postAddress StoredMarkup Maybe - postLastUpdate UTCTime Maybe -- record postal address updates + pinPassword Text Maybe -- used to encrypt pins within emails, defaults to cardno.version + postAddress StoredMarkup Maybe -- including company name, if any, but excluding username; leave empty for using auto-update CompanyPostAddress via UserCompany + postLastUpdate UTCTime Maybe -- record postal address updates prefersPostal Bool default=false -- user prefers letters by post instead of email examOfficeGetSynced Bool default=true -- whether synced status should be displayed for exam results by default examOfficeGetLabels Bool default=true -- whether labels should be displayed for exam results by default @@ -61,42 +61,52 @@ UserFunction -- Administratively assigned functions (lecturer, admin, evaluation function SchoolFunction UniqueUserFunction user school function deriving Generic -UserSystemFunction +UserSystemFunction Show user UserId function SystemFunction -- Defined in Model.Types.User manual Bool -- Inserted manually by Admin or automatic from LDAP isOptOut Bool -- User has currently deactivate the role for themselves UniqueUserSystemFunction user function - deriving Generic + deriving Generic Show UserExamOffice user UserId field StudyTermsId UniqueUserExamOffice user field - deriving Generic + deriving Generic Show UserSchool -- Managed by users themselves, encodes "schools of interest" user UserId school SchoolId isOptOut Bool -- true if this a marker, that the user manually deleted this entry; it should not be recreated automatically UniqueUserSchool user school - deriving Generic + deriving Generic Show UserGroupMember group UserGroupName user UserId primary Checkmark nullable UniquePrimaryUserGroupMember group primary !force UniqueUserGroupMember group user - deriving Generic + deriving Generic Show UserCompany user UserId company CompanyId OnDeleteCascade OnUpdateCascade supervisor Bool default=false -- should this user be made supervisor for all _new_ users associated with this company? supervisorReroute Bool default=false -- if supervisor is true, should this supervisor receive email for _new_ company users? + priority Int default=0 -- higher number, higher priority; default=1 for Haskell-Code + useCompanyAddress Bool default=true -- if true, CompanyPostalAddress and CompanyEmail are used if UserPostalAddress/UserDisplayEmail are Nothing, respects priority + reason Text Maybe -- miscellaneous note, e.g. Superior UniqueUserCompany user company -- a user may belong to multiple companies, but to each one only once - deriving Generic + deriving Generic Show UserSupervisor - supervisor UserId -- multiple supervisor per trainee possible + supervisor UserId -- multiple supervisor per trainee possible user UserId - rerouteNotifications Bool -- User can be his own supervisor to receive notifications as well - UniqueUserSupervisor supervisor user -- each supervisor/user combination is unique (same supervisor can superviser the same user only once) - deriving Generic - + rerouteNotifications Bool -- User can be his own supervisor to receive notifications as well + company CompanyId Maybe OnDeleteCascade OnUpdateCascade -- this supervisor was company default supervisor at time of entry + reason Text Maybe -- miscellaneous reason, e.g. Winterservice supervisision + UniqueUserSupervisor supervisor user -- each supervisor/user combination is unique (same supervisor can superviser the same user only once) + deriving Generic Show +UserDay + user UserId OnDeleteCascade OnUpdateCascade + day Day + parkingToken Bool default=false + UniqueUserDay user day + deriving Generic Show diff --git a/package-lock.json b/package-lock.json index 20fe65044..666b7a4a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "fradrive", - "version": "27.4.59", + "version": "27.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fradrive", - "version": "27.4.59", + "version": "27.5.1", "license": "AGPL-3.0-or-later", "dependencies": { "@babel/runtime": "^7.25.6", diff --git a/package.json b/package.json index e7cd70b03..01bb898e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fradrive", - "version": "27.4.59", + "version": "27.5.1", "description": "", "keywords": [], "author": "", diff --git a/package.yaml b/package.yaml index c46471367..4f6bb991a 100644 --- a/package.yaml +++ b/package.yaml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later name: uniworx -version: 27.4.59 +version: 27.5.1 dependencies: - base - yesod @@ -260,7 +260,7 @@ ghc-options: - -fno-warn-unrecognised-pragmas - -fno-warn-partial-type-signatures - -fno-max-relevant-binds - - -j + - -j5 - -freduction-depth=0 - -fprof-auto-calls - -g diff --git a/routes b/routes index db80030ec..7268cd33c 100644 --- a/routes +++ b/routes @@ -1,4 +1,4 @@ --- SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel , Gregor Kleen , Sarah Vaupel , Steffen Jost , Wolfgang Witt +-- SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel , Gregor Kleen , Sarah Vaupel , Steffen Jost , Wolfgang Witt , Steffeb Jost -- -- SPDX-License-Identifier: AGPL-3.0-or-later @@ -53,9 +53,9 @@ / NewsR GET !free /users UsersR GET POST -- no tags, i.e. admins only -/users/#CryptoUUIDUser AdminUserR GET POST -/users/#CryptoUUIDUser/delete AdminUserDeleteR POST -/users/#CryptoUUIDUser/hijack AdminHijackUserR GET POST !adminANDno-escalation +/users/#CryptoUUIDUser AdminUserR GET POST +/users/#CryptoUUIDUser/delete AdminUserDeleteR POST +/users/#CryptoUUIDUser/hijack AdminHijackUserR GET POST !adminANDno-escalation /users/#CryptoUUIDUser/notifications UserNotificationR GET POST !self /users/#CryptoUUIDUser/password UserPasswordR GET POST !selfANDis-pw-hash !/users/functionary-invite/new AdminNewFunctionaryInviteR GET POST @@ -69,14 +69,21 @@ /admin/crontab AdminCrontabR GET /admin/crontab/jobs AdminJobsR GET POST /admin/avs AdminAvsR GET POST -/admin/avs/#CryptoUUIDUser AdminAvsUserR GET +/admin/avs/#CryptoUUIDUser AdminAvsUserR GET POST /admin/ldap AdminLdapR GET POST -/admin/problems AdminProblemsR GET -/admin/problems/no-contact ProblemUnreachableR GET +/admin/problems AdminProblemsR GET POST +/admin/problems/no-contact ProblemUnreachableR GET POST /admin/problems/no-avs-id ProblemWithoutAvsId GET /admin/problems/r-without-f ProblemFbutNoR GET /admin/problems/avs ProblemAvsSynchR GET POST /admin/problems/avs/errors ProblemAvsErrorR GET +/admin/config/interfaces ConfigInterfacesR GET POST + +/comm CommCenterR GET +/comm/email MailCenterR GET POST +/comm/email/html/#CryptoUUIDSentMail MailHtmlR GET +/comm/email/plain/#CryptoUUIDSentMail MailPlainR GET +/comm/email/attachment/#CryptoUUIDSentMail/#Text MailAttachmentR GET /print PrintCenterR GET POST !system-printer /print/acknowledge/#Day/#Int/#Int PrintAckR GET POST !system-printer @@ -147,11 +154,11 @@ !/term/#TermId/#SchoolId TermSchoolCourseListR GET !free -/school SchoolListR GET +/school SchoolListR GET !free !/school/new SchoolNewR GET POST /school/#SchoolId SchoolR: - / SchoolEditR GET POST - + /edit SchoolEditR GET POST + /day/#Day SchoolDayR GET POST /participants ParticipantsListR GET !evaluation /participants/#TermId/#SchoolId ParticipantsR GET !evaluation diff --git a/shell.nix b/shell.nix index a2866bb44..25f83e616 100644 --- a/shell.nix +++ b/shell.nix @@ -289,13 +289,14 @@ in pkgs.mkShell { # busybox # for print services, but interferes with build commands in develop-shell htop pdftk # pdftk just for testing pdf-passwords + roboto roboto-mono # texlive.combined.scheme-full # works # texlive.combined.scheme-medium # texlive.combined.scheme-small (texlive.combine { inherit (texlive) scheme-basic babel-german babel-english booktabs textpos - enumitem eurosym koma-script parskip xcolor dejavu + enumitem eurosym koma-script parskip xcolor roboto xkeyval luatexbase lualatex-math unicode-math selnolig # required for LuaTeX ; }) diff --git a/src/Application.hs b/src/Application.hs index 4b60ecb39..80662f3ed 100644 --- a/src/Application.hs +++ b/src/Application.hs @@ -1,4 +1,4 @@ --- SPDX-FileCopyrightText: 2022 Gregor Kleen ,Sarah Vaupel ,Sarah Vaupel ,Steffen Jost +-- SPDX-FileCopyrightText: 2022-24 Gregor Kleen ,Sarah Vaupel ,Sarah Vaupel ,Steffen Jost ,Steffen Jost -- -- SPDX-License-Identifier: AGPL-3.0-or-later @@ -115,16 +115,11 @@ import GHC.RTS.Flags (getRTSFlags) import qualified Prometheus -import qualified Data.IntervalMap.Strict as IntervalMap - import qualified Utils.Pool as Custom -import Utils.Postgresql -import Handler.Utils.Memcached (manageMemcachedLocalInvalidations) - import qualified System.Clock as Clock -import Utils.Avs +import Utils.Avs (mkAvsQuery) -- Import all relevant handler modules here. -- (HPack takes care to add new modules to our cabal file nowadays.) @@ -137,6 +132,7 @@ import Handler.Users.Add import Handler.Admin import Handler.Term import Handler.School +import Handler.School.DayTasks import Handler.Course import Handler.Sheet import Handler.Submission @@ -157,6 +153,8 @@ import Handler.Upload import Handler.Qualification import Handler.LMS import Handler.SAP +import Handler.CommCenter +import Handler.MailCenter import Handler.PrintCenter import Handler.ApiDocs import Handler.Swagger @@ -216,18 +214,6 @@ makeFoundation appSettings''@AppSettings{..} = do appJobState <- liftIO newEmptyTMVarIO appHealthReport <- liftIO $ newTVarIO Set.empty - appFileSourceARC <- for appFileSourceARCConf $ \ARCConf{..} -> do - ah <- initARCHandle arccMaximumGhost arccMaximumWeight - void . Prometheus.register $ arcMetrics ARCFileSource ah - return ah - appFileSourcePrewarm <- for appFileSourcePrewarmConf $ \PrewarmCacheConf{..} -> do - lh <- initLRUHandle precMaximumWeight - void . Prometheus.register $ lruMetrics LRUFileSourcePrewarm lh - return lh - appFileInjectInhibit <- liftIO $ newTVarIO IntervalMap.empty - for_ (guardOnM (isn't _JobsOffload appJobMode) appInjectFiles) $ \_ -> - void . Prometheus.register $ injectInhibitMetrics appFileInjectInhibit - appStartTime <- liftIO getCurrentTime -- We need a log function to create a connection pool. We need a connection -- pool to create our foundation. And we need our foundation to get a @@ -236,7 +222,7 @@ makeFoundation appSettings''@AppSettings{..} = do -- from there, and then create the real foundation. let mkFoundation :: _ -> (forall m. MonadIO m => Custom.Pool' m DBConnLabel DBConnUseState SqlBackend) -> _ - mkFoundation appSettings' appConnPool appSmtpPool appLdapPool appCryptoIDKey appSessionStore appSecretBoxKey appWidgetMemcached appJSONWebKeySet appClusterID appMemcached appMemcachedLocal appUploadCache appVerpSecret appAuthKey appPersonalisedSheetFilesSeedKey appVolatileClusterSettingsCache appAvsQuery = UniWorX{..} + mkFoundation appSettings' appConnPool appSmtpPool appLdapPool appCryptoIDKey appSessionStore appSecretBoxKey appWidgetMemcached appJSONWebKeySet appClusterID appMemcached appUploadCache appVerpSecret appAuthKey appPersonalisedSheetFilesSeedKey appVolatileClusterSettingsCache appAvsQuery = UniWorX{..} tempFoundation = mkFoundation (error "appSettings' forced in tempFoundation") (error "connPool forced in tempFoundation") @@ -249,7 +235,6 @@ makeFoundation appSettings''@AppSettings{..} = do (error "JSONWebKeySet forced in tempFoundation") (error "ClusterID forced in tempFoundation") (error "memcached forced in tempFoundation") - (error "memcachedLocal forced in tempFoundation") (error "MinioConn forced in tempFoundation") (error "VerpSecret forced in tempFoundation") (error "AuthKey forced in tempFoundation") @@ -334,12 +319,6 @@ makeFoundation appSettings''@AppSettings{..} = do $logWarnS "setup" "Clearing memcached" liftIO $ Memcached.flushAll memcachedConn return AppMemcached{..} - appMemcachedLocal <- for appMemcachedLocalConf $ \ARCConf{..} -> do - memcachedLocalARC <- initARCHandle arccMaximumGhost arccMaximumWeight - void . Prometheus.register $ arcMetrics ARCMemcachedLocal memcachedLocalARC - memcachedLocalInvalidationQueue <- newTVarIO mempty - memcachedLocalHandleInvalidations <- allocateLinkedAsync . managePostgresqlChannel appDatabaseConf ChannelMemcachedLocalInvalidation $ manageMemcachedLocalInvalidations memcachedLocalARC memcachedLocalInvalidationQueue - return AppMemcachedLocal{..} appSessionStore <- mkSessionStore appSettings'' sqlPool `customRunSqlPool` sqlPool @@ -352,15 +331,15 @@ makeFoundation appSettings''@AppSettings{..} = do handleIf isBucketExists (const $ return ()) $ Minio.makeBucket appUploadTmpBucket Nothing return conn - appAvsQuery <- case appAvsConf of + appAvsQuery <- case appAvsConf of Nothing -> do $logErrorS "avsPrepare" "appAvsConfig is empty, i.e. invalid AVS configuration settings." return Nothing - -- error "AvsConfig is empty, i.e. invalid AVS configuration settings." - - Just avsConf -> do + -- error "AvsConfig is empty, i.e. invalid AVS configuration settings." + + Just avsConf -> do manager <- newManagerSettings $ mkManagerSettings (def { settingDisableCertificateValidation = True }) Nothing - let avsServer = BaseUrl + let avsServer = BaseUrl { baseUrlScheme = Https , baseUrlHost = avsHost avsConf , baseUrlPort = avsPort avsConf @@ -377,7 +356,7 @@ makeFoundation appSettings''@AppSettings{..} = do $logDebugS "Runtime configuration" $ tshowCrop appSettings' - let foundation = mkFoundation appSettings' sqlPool smtpPool ldapPool appCryptoIDKey appSessionStore appSecretBoxKey appWidgetMemcached appJSONWebKeySet appClusterID appMemcached appMemcachedLocal appUploadCache appVerpSecret appAuthKey appPersonalisedSheetFilesSeedKey appVolatileClusterSettingsCache appAvsQuery + let foundation = mkFoundation appSettings' sqlPool smtpPool ldapPool appCryptoIDKey appSessionStore appSecretBoxKey appWidgetMemcached appJSONWebKeySet appClusterID appMemcached appUploadCache appVerpSecret appAuthKey appPersonalisedSheetFilesSeedKey appVolatileClusterSettingsCache appAvsQuery -- Return the foundation $logInfoS "setup" "*** DONE ***" @@ -657,7 +636,7 @@ appMain = runResourceT $ do notifyWatchdog = forever' Nothing $ \pResults -> do let delay = floor $ wInterval % 4 d <- liftIO $ newDelay delay - + $logDebugS "Notify" $ "Waiting up to " <> tshow delay <> "µs..." mResults <- atomically $ asum [ pResults <$ waitDelay d @@ -746,8 +725,8 @@ shutdownApp app = do -- | Run a handler handler, handler' :: Handler a -> IO a -handler h = runResourceT $ getAppDevSettings >>= makeFoundation >>= liftIO . flip unsafeHandler h -handler' h = runResourceT $ getAppSettings >>= makeFoundation >>= liftIO . flip unsafeHandler h +handler h = runResourceT $ getAppDevSettings >>= makeFoundation >>= liftIO . flip unsafeHandler h +handler' h = runResourceT $ getAppSettings >>= makeFoundation >>= liftIO . flip unsafeHandler h -- | Run DB queries db, db' :: DB a -> IO a diff --git a/src/Audit.hs b/src/Audit.hs index 40c4a4206..8bff261b3 100644 --- a/src/Audit.hs +++ b/src/Audit.hs @@ -1,7 +1,9 @@ --- SPDX-FileCopyrightText: 2023 Gregor Kleen ,Steffen Jost +-- SPDX-FileCopyrightText: 2023-24 Gregor Kleen ,Steffen Jost -- -- SPDX-License-Identifier: AGPL-3.0-or-later +{-# LANGUAGE TypeApplications #-} + module Audit ( module Audit.Types , AuditException(..) @@ -9,6 +11,7 @@ module Audit , AuditRemoteException(..) , getRemote , logInterface, logInterface' + , reportAdminProblem ) where @@ -16,6 +19,8 @@ import Import.NoModel import Settings import Model import Database.Persist.Sql +import qualified Database.Esqueleto.Experimental as E -- needs TypeApplications Lang-Pragma +import qualified Database.Esqueleto.Utils as E import Audit.Types import qualified Data.Text as Text @@ -128,7 +133,7 @@ logInterface :: ( AuthId (HandlerSite m) ~ Key User -> Text -- ^ Any additional information -> ReaderT (YesodPersistBackend (HandlerSite m)) m () -- ^ Log a transaction using information available from `HandlerT`, also calls `audit` -logInterface interfaceLogInterface interfaceLogSubtype interfaceLogSuccess interfaceLogRows interfaceLogInfo = do +logInterface interfaceLogInterface interfaceLogSubtype interfaceLogSuccess interfaceLogRows interfaceLogInfo = do interfaceLogWrite <- (methodGet /=) . Wai.requestMethod . reqWaiRequest <$> getRequest logInterface' interfaceLogInterface interfaceLogSubtype interfaceLogWrite interfaceLogSuccess interfaceLogRows interfaceLogInfo @@ -152,7 +157,7 @@ logInterface' :: ( AuthId (HandlerSite m) ~ Key User -- ^ Log a transaction using information available from `HandlerT`, also calls `audit` logInterface' (Text.strip -> interfaceLogInterface) (Text.strip -> interfaceLogSubtype) interfaceLogWrite interfaceLogSuccess interfaceLogRows (Text.strip -> interfaceLogInfo) = do interfaceLogTime <- liftIO getCurrentTime - -- deleteBy $ UniqueInterfaceSubtypeWrite interfaceLogInterface interfaceLogSubtype interfaceLogWrite -- always replace: deleteBy & insert seems to be safest and fastest + -- deleteBy $ UniqueInterfaceSubtypeWrite interfaceLogInterface interfaceLogSubtype interfaceLogWrite -- deleteBy & insert would be justified here, leading to a new Row-ID, since the two rows are not truly connected. -- insert_ InterfaceLog{..} void $ upsertBy (UniqueInterfaceSubtypeWrite interfaceLogInterface interfaceLogSubtype interfaceLogWrite) ( InterfaceLog{..} ) @@ -169,3 +174,28 @@ logInterface' (Text.strip -> interfaceLogInterface) (Text.strip -> interfaceLogS , transactionInterfaceInfo = interfaceLogInfo , transactionInterfaceSuccess = Just interfaceLogSuccess } + +reportAdminProblem :: ( IsSqlBackend (YesodPersistBackend (HandlerSite m)) + , SqlBackendCanWrite (YesodPersistBackend (HandlerSite m)) + , MonadHandler m + -- , HasCallStack + ) + => AdminProblem -- ^ Problem to record + -> ReaderT (YesodPersistBackend (HandlerSite m)) m () +-- ^ Log a problem that needs interventions by admins, provided this problem has not already been reported and is still unsolved +-- +-- - `problemLogTime` is now +-- - `problemSolver` is Nothing, we do not record the person who caused it +reportAdminProblem problem = do + let problemLogSolved = Nothing + problemLogSolver = Nothing + problemLogInfo = toJSON problem + problemLogTime <- liftIO getCurrentTime + isKnown <- E.selectExists $ do + pl <- E.from $ E.table @ProblemLog + E.where_ $ E.isNothing (pl E.^. ProblemLogSolved) + E.&&. E.val problemLogInfo E.==. pl E.^. ProblemLogInfo + unless isKnown $ insert_ ProblemLog{..} + $logWarnS "Problem" $ Text.filter (/= '\n') $ tshow problem -- <> " - " <> pack (prettyCallStack callStack) + + diff --git a/src/Audit/Types.hs b/src/Audit/Types.hs index 976171ec4..f20aaed95 100644 --- a/src/Audit/Types.hs +++ b/src/Audit/Types.hs @@ -1,15 +1,18 @@ --- SPDX-FileCopyrightText: 2022-23 Gregor Kleen ,Sarah Vaupel ,Steffen Jost +-- SPDX-FileCopyrightText: 2022-24 Gregor Kleen ,Sarah Vaupel ,Steffen Jost -- -- SPDX-License-Identifier: AGPL-3.0-or-later module Audit.Types ( Transaction(..) + , AdminProblem(..) + , decodeAdminProblem ) where import ClassyPrelude.Yesod hiding (derivePersistFieldJSON) import Model.Types.TH.JSON import Model +import Data.Aeson import Data.Aeson.TH import Utils.PathPiece @@ -182,7 +185,7 @@ data Transaction } | TransactionLmsStart { transactionQualification :: QualificationId - , transactionLmsIdent :: LmsIdent + , transactionLmsIdent :: LmsIdent , transactionLmsUser :: UserId , transactionLmsUserKey :: LmsUserId } @@ -213,7 +216,7 @@ data Transaction | TransactionQualificationUserEdit -- Note that a renewal always entails unblocking as well! { transactionUser :: UserId -- qualification holder that is updated , transactionQualificationUser :: QualificationUserId -- not really necessary, maybe remove? - , transactionQualification :: QualificationId + , transactionQualification :: QualificationId , transactionQualificationValidUntil :: Day , transactionQualificationScheduleRenewal :: Maybe Bool -- Maybe, because some update may leave it unchanged (also avoids DB Migration) , transactionNote :: Maybe Text @@ -251,4 +254,63 @@ deriveJSON defaultOptions , sumEncoding = TaggedObject "transaction" "data" } ''Transaction -derivePersistFieldJSON ''Transaction \ No newline at end of file +derivePersistFieldJSON ''Transaction + + + +-- Datatype for raising admin awareness to certain problems +-- Database stores generic Value in table ProblemLog, such that changes do not disturb old entries +-- Note that there is no RenderMessage instance, instead see @Handler.Admin.adminProblemCell dealing with special cases instead +-- Note: Adjust MsgAdminProblemInfoTooltip as well +data AdminProblem + = AdminProblemNewCompany -- new company was noticed, presumably without supervisors + { adminProblemCompany :: CompanyId + } + | AdminProblemSupervisorNewCompany + { adminProblemUser :: UserId -- a default supervisor has changed company + , adminProblemCompany :: CompanyId -- old company where the user had default supervisor rights + , adminProblemCompanyNew :: CompanyId -- new company of the user + , adminProblemSupervisorReroute :: Bool -- reroute included? + } + | AdminProblemSupervisorLeftCompany + { adminProblemUser :: UserId -- user who had a supervisor but no longer has, due to supervisor change + , adminProblemCompany :: CompanyId -- old company + , adminProblemSupervisorReroute :: Bool -- reroute included? + } + | AdminProblemCompanySuperiorChange -- a company received a new superior user through AVS + { adminProblemUser :: UserId -- new superior user + , adminProblemCompany :: CompanyId -- affected company + , adminProblemUserOld :: Maybe UserId -- previous superior + } + | AdminProblemCompanySuperiorNotFound -- a company received a new superior user through AVS, but user could not be created from email + { adminProblemEmail :: Maybe Text -- new superior user's email, not found in LDAP + , adminProblemCompany :: CompanyId -- affected company + , adminProblemUserOld :: Maybe UserId -- previous superior + } + | AdminProblemNewlyUnsupervised + { adminProblemUser :: UserId -- user who had a supervisor but no longer has, due to user company change + , adminProblemCompanyOld :: Maybe CompanyId -- old company + , adminProblemCompanyNew :: CompanyId -- new company of the user + } + | AdminProblemUnknown -- miscellanous problem, just displaying text + { adminProblemText :: Text + } + deriving (Eq, Ord, Read, Show, Generic) + +-- Columns shown in problem table: adminProblemCompany, adminProblemUser +-- For display: add clause to Handler.Admin.adminProblemCell + +deriveJSON defaultOptions + { constructorTagModifier = camelToPathPiece' 2 + , fieldLabelModifier = camelToPathPiece' 2 + , tagSingleConstructors = True + , sumEncoding = TaggedObject "problem" "data" + , rejectUnknownFields = False + } ''AdminProblem + +derivePersistFieldJSON ''AdminProblem + +decodeAdminProblem :: Value -> AdminProblem +decodeAdminProblem v = case fromJSON v of + Error msg -> AdminProblemUnknown $ pack msg + Success p -> p diff --git a/src/Auth/Dummy.hs b/src/Auth/Dummy.hs index 06bf4985e..9360d9da6 100644 --- a/src/Auth/Dummy.hs +++ b/src/Auth/Dummy.hs @@ -34,7 +34,7 @@ dummyForm = do mr <- getMessageRender wreq (ciField & addDatalist userList) (fslpI MsgDummyIdent (mr MsgDummyIdentPlaceholder) & addAttr "autocomplete" "username" & addName PostLoginDummy) Nothing where - userList = fmap mkOptionList . runDB $ withReaderT projectBackend (map toOption <$> selectList [] [Asc UserIdent] :: ReaderT SqlBackend _ [Option UserIdent]) + userList = fmap mkOptionList . runDB $ withReaderT projectBackend (map toOption <$> selectList [UserId <=. UserKey 12] [Asc UserIdent] :: ReaderT SqlBackend _ [Option UserIdent]) toOption (Entity _ User{..}) = Option userDisplayName userIdent (CI.original userIdent) apDummy :: Text diff --git a/src/CryptoID.hs b/src/CryptoID.hs index 9d724bbee..9c4fdfaa1 100644 --- a/src/CryptoID.hs +++ b/src/CryptoID.hs @@ -59,6 +59,7 @@ decCryptoIDs [ ''SubmissionId , ''MaterialFileId , ''PrintJobId , ''QualificationId + , ''SentMailId ] decCryptoIDKeySize diff --git a/src/Data/CaseInsensitive/Instances.hs b/src/Data/CaseInsensitive/Instances.hs index b7c3dfa59..d373b942a 100644 --- a/src/Data/CaseInsensitive/Instances.hs +++ b/src/Data/CaseInsensitive/Instances.hs @@ -128,4 +128,4 @@ instance Swagger.ToSchema s => Swagger.ToSchema (CI s) where instance (CI.FoldCase s, Binary s) => Binary (CI s) where get = CI.mk <$> Binary.get - put = Binary.put . CI.original + put = Binary.put . CI.original \ No newline at end of file diff --git a/src/Database/Esqueleto/Utils.hs b/src/Database/Esqueleto/Utils.hs index 127e0ed88..152d506ae 100644 --- a/src/Database/Esqueleto/Utils.hs +++ b/src/Database/Esqueleto/Utils.hs @@ -15,6 +15,7 @@ module Database.Esqueleto.Utils , (=?.), (?=.) , (=~.), (~=.) , (>~.), (<~.) + , (~.), (~*.), (!~.), (!~*.) , or, and , any, all , not__, parens @@ -26,12 +27,14 @@ module Database.Esqueleto.Utils , mkContainsFilterWithSet, mkContainsFilterWithComma, mkContainsFilterWithCommaPlus , mkDayFilter, mkDayFilterFrom, mkDayFilterTo , mkExistsFilter, mkExistsFilterWithComma + -- , mkRegExFilterWith , anyFilter, allFilter , ascNullsFirst, descNullsLast , orderByList , orderByOrd, orderByEnum , strip, lower, ciEq , selectExists, selectNotExists + , filterExists , SqlHashable , sha256 , isTrue, isFalse @@ -41,16 +44,18 @@ module Database.Esqueleto.Utils , greatest, least , abs , SqlProject(..) - , (->.), (->>.), (#>>.) + , (->.), (->>.), (->>>.), (#>>.) , fromSqlKey , unKey , subSelectCountDistinct , selectCountRows, selectCountDistinct - , selectMaybe - , num2text + , str2text, str2text' + , num2text --, text2num , day, day', dayMaybe, interval, diffDays, diffTimes , exprLift , explicitUnsafeCoerceSqlExprValue + , psqlVersion_ + , truncateTable , module Database.Esqueleto.Utils.TH ) where @@ -61,12 +66,16 @@ import qualified Data.Set as Set import qualified Data.List as List import qualified Data.Foldable as F import Data.List.NonEmpty (NonEmpty(..)) +import qualified Database.Persist as P +import qualified Database.Persist.EntityDef.Internal as P (entityDB) import qualified Database.Esqueleto.Legacy as E import qualified Database.Esqueleto.Experimental as Ex import qualified Database.Esqueleto.PostgreSQL as E import qualified Database.Esqueleto.Internal.Internal as E import Database.Esqueleto.Utils.TH +-- import qualified Database.Persist.Postgresql as P + import qualified Data.Text as Text import qualified Data.Text.Lazy as Lazy (Text) import qualified Data.ByteString.Lazy as Lazy (ByteString) @@ -156,6 +165,24 @@ infixl 4 <~. (<~.) :: PersistField typ => E.SqlExpr (E.Value typ) -> E.SqlExpr (E.Value (Maybe typ)) -> E.SqlExpr (E.Value Bool) (<~.) a b = E.isNothing b E.||. (E.just a E.<. b) +infixr 2 ~., ~*., !~., !~*. + +-- | PostgreSQL regular expression match, case sensitive. Works, but may throw SQL error for unblanced parenthesis, etc. Not suitable for dbTable filters +(~.) :: E.SqlString s => E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value Bool) +(~.) = E.unsafeSqlBinOp " ~ " + +-- | PostgreSQL regular expression match, case insensitive. Works, but may throw SQL errors +(~*.) :: E.SqlString s => E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value Bool) +(~*.) = E.unsafeSqlBinOp " ~* " + +-- | PostgreSQL regular expression does not match, case sensitive. Works, but may throw SQL error for unblanced parenthesis, etc. Not suitable for dbTable filters +(!~.) :: E.SqlString s => E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value Bool) +(!~.) = E.unsafeSqlBinOp " !~ " + +-- | PostgreSQL regular expression does not match, case insensitive. Works, but may throw SQL errors +(!~*.) :: E.SqlString s => E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value Bool) +(!~*.) = E.unsafeSqlBinOp " !~* " + -- | Negation of `isNothing` which is missing isJust :: PersistField typ => E.SqlExpr (E.Value (Maybe typ)) -> E.SqlExpr (E.Value Bool) @@ -322,7 +349,7 @@ mkExactFilterLastWith :: (PersistField b) -> Last a -- ^ needle -> E.SqlExpr (E.Value Bool) mkExactFilterLastWith cast lenslike row criterias - | Last (Just crit) <- criterias = lenslike row E.==. E.val (cast crit) + | Last (Just crit) <- criterias = lenslike row E.==. E.val (cast crit) | otherwise = true -- | like `mkExactFilterLast` but deals with Nothing being a filter criterion as well @@ -351,7 +378,7 @@ mkExactFilterMaybeLast' lensexists lenslike row criterias -- | generic filter creation for dbTable -- Given a lens-like function, make filter searching for needles in String-like elements -- (Keep Set here to ensure that there are no duplicates) -mkContainsFilter :: E.SqlString a +mkContainsFilter :: (E.SqlString a, Ord a) => (t -> E.SqlExpr (E.Value a)) -- ^ getter from query to searched element -> t -- ^ query row -> Set.Set a -- ^ needle collection @@ -359,7 +386,7 @@ mkContainsFilter :: E.SqlString a mkContainsFilter = mkContainsFilterWith id -- | like `mkContainsFilter` but allows for conversion; convenient in conjunction with `anyFilter` and `allFilter` -mkContainsFilterWith :: E.SqlString b +mkContainsFilterWith :: (E.SqlString b, Ord a) => (a -> b) -> (t -> E.SqlExpr (E.Value b)) -- ^ getter from query to searched element -> t -- ^ query row @@ -367,7 +394,7 @@ mkContainsFilterWith :: E.SqlString b -> E.SqlExpr (E.Value Bool) mkContainsFilterWith cast lenslike row criterias | Set.null criterias = true - | otherwise = any (hasInfix $ lenslike row) (E.val . cast <$> Set.toList criterias) + | otherwise = any (hasInfix (lenslike row) . E.val . cast) criterias -- | like `mkContainsFilterWith` but allows conversion to produce multiple needles mkContainsFilterWithSet :: (E.SqlString b, Ord b, Ord a) @@ -378,7 +405,7 @@ mkContainsFilterWithSet :: (E.SqlString b, Ord b, Ord a) -> E.SqlExpr (E.Value Bool) mkContainsFilterWithSet cast lenslike row criterias | Set.null criterias = true - | otherwise = any (hasInfix $ lenslike row) (E.val <$> Set.toList (foldMap cast criterias)) + | otherwise = any (hasInfix (lenslike row) . E.val) (foldMap cast criterias) -- | like `mkContainsFilterWithSet` but fixed to comma separated Texts mkContainsFilterWithComma :: (E.SqlString b, Ord b) @@ -389,7 +416,7 @@ mkContainsFilterWithComma :: (E.SqlString b, Ord b) -> E.SqlExpr (E.Value Bool) mkContainsFilterWithComma cast lenslike row (foldMap commaSeparatedText -> criterias) | Set.null criterias = true - | otherwise = any (hasInfix $ lenslike row) (E.val . cast <$> Set.toList criterias) + | otherwise = any (hasInfix (lenslike row) . E.val . cast) criterias -- | like `mkContainsFilterWithComma` but enforced the existence of all Texts prefixed with + mkContainsFilterWithCommaPlus :: (E.SqlString b, Ord b) @@ -403,10 +430,22 @@ mkContainsFilterWithCommaPlus cast lenslike row (foldMap commaSeparatedText -> c | Set.null compulsories = cond_optional | Set.null alternatives = cond_compulsory | otherwise = cond_compulsory E.&&. cond_optional - where + where (Set.mapMonotonic (Text.stripStart . Text.drop 1) -> compulsories, alternatives) = Set.partition (Text.isPrefixOf "+") criterias - cond_compulsory = all (hasInfix $ lenslike row) (E.val . cast <$> Set.toList compulsories) - cond_optional = any (hasInfix $ lenslike row) (E.val . cast <$> Set.toList alternatives) + cond_compulsory = all (hasInfix (lenslike row) . E.val . cast) compulsories + cond_optional = any (hasInfix (lenslike row) . E.val . cast) alternatives + +-- like `mkContainsFilterWith` but allows regular expression criterias +-- This works, but throws SQL errors for unbalanced parenthesis and similar invalid regex expressions +-- mkRegExFilterWith :: (E.SqlString b, Ord a) +-- => (a -> b) +-- -> (t -> E.SqlExpr (E.Value b)) -- ^ getter from query to searched element +-- -> t -- ^ query row +-- -> Set.Set a -- ^ needle collection +-- -> E.SqlExpr (E.Value Bool) +-- mkRegExFilterWith cast lenslike row criterias +-- | Set.null criterias = true +-- | otherwise = any ((~.) (lenslike row) . E.val . cast) criterias mkDayFilter :: (t -> E.SqlExpr (E.Value UTCTime)) -- ^ getter from query to searched element -> t -- ^ query row @@ -451,7 +490,7 @@ mkExistsFilterWithComma :: PathPiece a -> E.SqlExpr (E.Value Bool) mkExistsFilterWithComma cast query row (foldMap commaSeparatedText -> criterias) | Set.null criterias = true - | otherwise = any (E.exists . query row) (cast <$> Set.toList criterias) + | otherwise = any (E.exists . query row . cast) criterias -- | Combine several filters, using logical or @@ -510,6 +549,13 @@ selectExists query = do _other -> error "SELECT EXISTS ... returned zero or more than one rows" selectNotExists = fmap not . selectExists +filterExists :: (MonadIO m, PersistEntity val, MonoFoldable mono, PersistField (Element mono)) + => EntityField val (Element mono) -> mono -> E.SqlReadT m [Element mono] +filterExists prj vs = fmap (fmap Ex.unValue) <$> Ex.select $ do + ent <- Ex.from Ex.table + Ex.where_ $ ent Ex.^. prj `Ex.in_` vals vs + return $ ent Ex.^. prj + class SqlHashable a instance SqlHashable Text @@ -603,7 +649,7 @@ max, min :: PersistField a max a b = bool a b $ b E.>. a min a b = bool a b $ b E.<. a --- these alternatives for max/min ought to be more efficient; note that NULL is avoided by PostgreSQL greatest/least +-- these alternatives for max/min ought to be more efficient; note that NULL is avoided by PostgreSQL greatest/least; for Bool: t > f greatest :: PersistField a => E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value a) greatest a b = E.unsafeSqlFunction "GREATEST" $ E.toArgList (a,b) @@ -642,9 +688,16 @@ infixl 8 ->. infixl 8 ->>. +-- Unsafe variant, see Database.Esqueleto.PostgreSQL.JSON for a safe version! (->>.) :: E.SqlExpr (E.Value a) -> Text -> E.SqlExpr (E.Value Text) (->>.) expr t = E.unsafeSqlBinOp "->>" expr $ E.val t +infixl 8 ->>>. + +-- Unsafe variant to obtain a DB key from a JSON field. Use with caution! +(->>>.) :: (PersistField (Key entity)) => E.SqlExpr (E.Value a) -> Text -> E.SqlExpr (E.Value (Maybe (Key entity))) +(->>>.) expr t = E.unsafeSqlCastAs "int" $ E.unsafeSqlBinOp "->>" expr $ E.val t + infixl 8 #>>. (#>>.) :: E.SqlExpr (E.Value a) -> Text -> E.SqlExpr (E.Value (Maybe Text)) @@ -663,7 +716,7 @@ unKey = E.veryUnsafeCoerceSqlExprValue -- | distinct version of `Database.Esqueleto.subSelectCount` subSelectCountDistinct :: (Num a, PersistField a) => Ex.SqlQuery (Ex.SqlExpr (Ex.Value typ)) -> Ex.SqlExpr (Ex.Value a) subSelectCountDistinct query = Ex.subSelectUnsafe (Ex.countDistinct <$> query) - + -- PersistField a => SqlQuery (SqlExpr (Value a)) -> SqlExpr (Value a) -- countDistinct :: Num a => SqlExpr (Value typ) -> SqlExpr (Value a) @@ -685,13 +738,25 @@ selectCountDistinct q = do _other -> error "E.countDistinct did not return exactly one result" -selectMaybe :: (E.SqlSelect a r, MonadIO m) => E.SqlQuery a -> E.SqlReadT m (Maybe r) -selectMaybe = fmap listToMaybe . E.select . (<* E.limit 1) +-- DEPRECATED: use Database.Esqueleto.selectOne instead +-- selectMaybe :: (E.SqlSelect a r, MonadIO m) => E.SqlQuery a -> E.SqlReadT m (Maybe r) +-- selectMaybe = fmap listToMaybe . E.select . (<* E.limit 1) + +-- | convert something that is like a text to text +str2text :: E.SqlString a => E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value Text) +str2text = E.unsafeSqlCastAs "text" + +str2text' :: E.SqlString a => E.SqlExpr (E.Value (Maybe a)) -> E.SqlExpr (E.Value (Maybe Text)) +str2text' = E.unsafeSqlCastAs "text" -- | cast numeric type to text, which is safe and allows for an inefficient but safe comparison of numbers stored as text and numbers num2text :: Num n => E.SqlExpr (E.Value n) -> E.SqlExpr (E.Value Text) num2text = E.unsafeSqlCastAs "text" +-- unsafe, use with care! +-- text2num :: E.SqlExpr (E.Value Text) -> E.SqlExpr (E.Value n) +-- text2num = E.unsafeSqlCastAs "int" + day :: E.SqlExpr (E.Value UTCTime) -> E.SqlExpr (E.Value Day) day = E.unsafeSqlCastAs "date" @@ -703,9 +768,9 @@ dayMaybe :: E.SqlExpr (E.Value (Maybe UTCTime)) -> E.SqlExpr (E.Value (Maybe Day dayMaybe = E.unsafeSqlCastAs "date" interval :: CalendarDiffDays -> E.SqlExpr (E.Value Day) -- E.+=. requires both types to be the same, so we use Day --- interval _ = E.unsafeSqlCastAs "interval" $ E.unsafeSqlValue "'P2Y'" -- tested working example +-- interval _ = E.unsafeSqlCastAs "interval" $ E.unsafeSqlValue "'P2Y'" -- tested working example interval = E.unsafeSqlCastAs "interval". E.unsafeSqlValue . wrapSqlString . Text.Builder.fromString . iso8601Show - where + where singleQuote = Text.Builder.singleton '\'' wrapSqlString b = singleQuote <> b <> singleQuote @@ -750,3 +815,16 @@ instance (PersistField a1, PersistField a2, PersistField b, Finite a1, Finite a2 ] (E.else_ $ E.else_ $ E.veryUnsafeCoerceSqlExprValue (E.nothing :: E.SqlExpr (E.Value (Maybe ())))) +psqlVersion_ :: E.SqlExpr (E.Value Text) +psqlVersion_ = E.unsafeSqlFunction "VERSION" () + +-- Suspected to cause trouble. Needs more testing! +-- truncateTable :: (MonadIO m, BackendCompatible SqlBackend backend, PersistEntity record) +-- => record -> ReaderT backend m () +-- truncateTable tbl = E.rawExecute ("TRUNCATE TABLE " <> P.tableName tbl <> " RESTART IDENTITY") [] + +truncateTable :: (MonadIO m, BackendCompatible SqlBackend backend, PersistEntity record) -- TODO: test this code + => proxy record -> ReaderT backend m () +truncateTable tbl = + let tblName :: Text = P.unEntityNameDB $ P.entityDB $ P.entityDef tbl + in E.rawExecute ("TRUNCATE TABLE " <> tblName <> " RESTART IDENTITY") [] \ No newline at end of file diff --git a/src/Database/Esqueleto/Utils/TH.hs b/src/Database/Esqueleto/Utils/TH.hs index 546d85b29..6623220a6 100644 --- a/src/Database/Esqueleto/Utils/TH.hs +++ b/src/Database/Esqueleto/Utils/TH.hs @@ -1,4 +1,4 @@ --- SPDX-FileCopyrightText: 2022 Gregor Kleen ,Sarah Vaupel ,Steffen Jost +-- SPDX-FileCopyrightText: 2022-24 Gregor Kleen ,Sarah Vaupel ,Steffen Jost ,Steffen Jost -- -- SPDX-License-Identifier: AGPL-3.0-or-later @@ -9,7 +9,7 @@ module Database.Esqueleto.Utils.TH , sqlInTuple, sqlInTuples , _unValue , unValueN, unValueNIs - , sqlIJproj, sqlLOJproj, sqlFOJproj + , sqlIJproj, sqlLOJproj, sqlFOJproj, sqlMIXproj, sqlMIXproj' ) where import ClassyPrelude @@ -26,6 +26,9 @@ import Data.List (foldr1, foldl) import Utils.TH import Control.Lens.Iso (Iso', iso) +{-# ANN module ("HLint: ignore Redundant bracket"::String) #-} + + class E.SqlSelect a r => SqlIn a r | a -> r, r -> a where sqlIn :: a -> [r] -> E.SqlExpr (E.Value Bool) @@ -99,7 +102,7 @@ unValueNIs arity uvIdx = do -- | Generic projections for InnerJoin-tuples -- gives I-th element of N-tuple of left-associative InnerJoin-pairs, i.e. -- --- > $(projN n m) :: (t1 `E.InnerJoin` .. `E.InnerJoin` tn) -> tm@ (for m<=n) +-- > $(sqlIJproj n m) :: (t1 `E.InnerJoin` .. `E.InnerJoin` tn) -> tm@ (for m<=n) sqlIJproj :: Int -> Int -> ExpQ sqlIJproj = leftAssociativePairProjection 'E.InnerJoin @@ -108,3 +111,23 @@ sqlLOJproj = leftAssociativePairProjection 'E.LeftOuterJoin sqlFOJproj :: Int -> Int -> ExpQ sqlFOJproj = leftAssociativePairProjection 'E.FullOuterJoin + +-- | Generic projections for Join-tuple +-- gives i-th element of n-tuple of left-associative join pairs, i.e. +-- +-- > $(sqlMIXproj "IR" 3) :: ((t1 `E.InnerJoin` t2) `E.RightOuterJoin` t3) -> t3 +sqlMIXproj :: String -> Int -> ExpQ +sqlMIXproj = leftAssociativeProjection . map decodeJoin + where + decodeJoin 'I' = 'E.InnerJoin + decodeJoin 'L' = 'E.LeftOuterJoin + decodeJoin 'R' = 'E.RightOuterJoin + decodeJoin 'F' = 'E.FullOuterJoin + decodeJoin 'O' = 'E.FullOuterJoin + decodeJoin 'X' = 'E.CrossJoin + decodeJoin 'C' = 'E.CrossJoin + decodeJoin c = error $ "Database.Esqueleto.Utils.TH.sqlMIXproj: received unknown SQL join kind \"" ++ c:"\"" -- always raised at compile time, so this is ok + +-- Alternative using `reify`; works, but may require `$(return [])` between type definition and call to workaround ghc staging problems +sqlMIXproj' :: Name -> Int -> ExpQ +sqlMIXproj' t i = extractConstructorNames t >>= flip leftAssociativeProjection i diff --git a/src/Database/Persist/Types/Instances.hs b/src/Database/Persist/Types/Instances.hs index e32ed5951..1cdf4a70a 100644 --- a/src/Database/Persist/Types/Instances.hs +++ b/src/Database/Persist/Types/Instances.hs @@ -27,7 +27,7 @@ instance Hashable LiteralType instance Binary LiteralType instance NFData LiteralType - + deriving instance Generic PersistValue instance Hashable PersistValue diff --git a/src/Foundation/Authorization.hs b/src/Foundation/Authorization.hs index 0243b0609..93b15fd70 100644 --- a/src/Foundation/Authorization.hs +++ b/src/Foundation/Authorization.hs @@ -38,7 +38,7 @@ import Handler.Utils.I18n import Handler.Utils.Routes import Utils.Course (courseIsVisible) import Utils.Metrics (observeAuthTagEvaluation, AuthTagEvalOutcome(..)) - + import qualified Data.Set as Set import qualified Data.Aeson as JSON import qualified Data.HashSet as HashSet @@ -95,7 +95,7 @@ instance Exception InvalidAuthTag type AuthTagsEval m = AuthDNF -> Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> WriterT (Set AuthTag) m AuthResult - + data AccessPredicate = APPure (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> Reader MsgRenderer AuthResult) | APHandler (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> HandlerFor UniWorX AuthResult) @@ -174,7 +174,7 @@ cacheAPDB mExp k mkV cont = APBindDB $ \mAuthId route isWrite -> do v <- mkV memcachedBySet mExp k v either (return . Left) (fmap Right . lift) $ cont mAuthId route isWrite v - + -- cacheAP' :: ( Binary k -- , Typeable v, Binary v -- ) @@ -185,7 +185,7 @@ cacheAPDB mExp k mkV cont = APBindDB $ \mAuthId route isWrite -> do -- cacheAP' mExp mkKV cont = APBind $ \mAuthId route isWrite -> case mkKV mAuthId route isWrite of -- Just (k, mkV) -> either (return . Left) (fmap Right) . cont mAuthId route isWrite . Just =<< memcachedBy mExp k mkV -- Nothing -> either (return . Left) (fmap Right) $ cont mAuthId route isWrite Nothing - + cacheAPDB' :: ( Binary k , Typeable v, Binary v, NFData v ) @@ -313,7 +313,8 @@ isDryRunDB = fmap unIsDryRun . cached . fmap MkIsDryRun $ orM dnf <- throwLeft $ routeAuthTags currentRoute let eval :: forall m''. MonadAP m'' => AuthTagsEval m'' - eval dnf' mAuthId' route' isWrite' = evalAuthTags 'isDryRun (AuthTagActive $ const True) eval (noTokenAuth dnf') mAuthId' route' isWrite' + -- eval dnf' mAuthId' route' isWrite' = evalAuthTags 'isDryRun (AuthTagActive $ const True) eval (noTokenAuth dnf') mAuthId' route' isWrite' + eval dnf' = evalAuthTags 'isDryRun (AuthTagActive $ const True) eval (noTokenAuth dnf') in guardAuthResult <=< evalWriterT $ eval dnf mAuthId currentRoute isWrite return False @@ -368,7 +369,8 @@ validateBearer mAuthId' route' isWrite' token' = $runCachedMemoT $ for4 memo val noTokenAuth = over _dnfTerms . Set.filter . noneOf (re _nullable . folded) $ (== AuthToken) . plVar eval :: forall m'. MonadAP m' => AuthTagsEval m' - eval dnf' mAuthId'' route'' isWrite'' = evalAuthTags 'validateBearer (AuthTagActive $ const True) eval (noTokenAuth dnf') mAuthId'' route'' isWrite'' + -- eval dnf' mAuthId'' route'' isWrite'' = evalAuthTags 'validateBearer (AuthTagActive $ const True) eval (noTokenAuth dnf') mAuthId'' route'' isWrite'' + eval dnf' = evalAuthTags 'validateBearer (AuthTagActive $ const True) eval (noTokenAuth dnf') bearerAuthority' <- hoist apRunDB $ do bearerAuthority' <- flip foldMapM bearerAuthority $ \case @@ -538,14 +540,14 @@ tagAccessPredicate AuthAdmin = cacheAPSchoolFunction SchoolAdmin (Just $ Right d guardMExceptT (isJust adrights) (unauthorizedI MsgUnauthorizedSiteAdmin) return Authorized -tagAccessPredicate AuthSupervisor = APDB $ \_ _ mAuthId route _ -> case route of +tagAccessPredicate AuthSupervisor = APDB $ \_ _ mAuthId route _ -> case route of ForProfileR cID -> checkSupervisor (mAuthId, cID) ForProfileDataR cID -> checkSupervisor (mAuthId, cID) FirmAllR -> checkAnySupervisor mAuthId FirmUsersR fsh -> checkCompanySupervisor (mAuthId, fsh) FirmSupersR fsh -> checkCompanySupervisor (mAuthId, fsh) - r -> $unsupportedAuthPredicate AuthSupervisor r - where + r -> $unsupportedAuthPredicate AuthSupervisor r + where checkSupervisor sup@(mAuthId, cID) = $cachedHereBinary sup . exceptT return return $ do authId <- maybeExceptT AuthenticationRequired $ return mAuthId uid <- decrypt cID @@ -553,13 +555,13 @@ tagAccessPredicate AuthSupervisor = APDB $ \_ _ mAuthId route _ -> case route of guardMExceptT isSupervisor (unauthorizedI MsgUnauthorizedSupervisor) return Authorized checkCompanySupervisor sup@(mAuthId, fsh) = $cachedHereBinary sup . exceptT return return $ do - authId <- maybeExceptT AuthenticationRequired $ return mAuthId + authId <- maybeExceptT AuthenticationRequired $ return mAuthId -- isSupervisor <- lift . existsBy $ UniqueUserCompany authId $ CompanyKey fsh isSupervisor <- lift $ exists [UserCompanyUser ==. authId, UserCompanyCompany ==. CompanyKey fsh, UserCompanySupervisor ==. True] guardMExceptT isSupervisor (unauthorizedI $ MsgUnauthorizedCompanySupervisor fsh) return Authorized checkAnySupervisor mAuthId = $cachedHereBinary mAuthId . exceptT return return $ do - authId <- maybeExceptT AuthenticationRequired $ return mAuthId + authId <- maybeExceptT AuthenticationRequired $ return mAuthId isSupervisor <- lift $ exists [UserSupervisorSupervisor ==. authId] guardMExceptT isSupervisor (unauthorizedI MsgUnauthorizedAnySupervisor) return Authorized @@ -692,7 +694,7 @@ tagAccessPredicate AuthLecturer = cacheAPDB' (Just $ Right diffMinute) mkLecture _ | is _Nothing mAuthId' -> return AuthenticationRequired CourseR{} -> unauthorizedI MsgUnauthorizedLecturer EExamR{} -> unauthorizedI MsgUnauthorizedExternalExamLecturer - _other -> unauthorizedI MsgUnauthorizedSchoolLecturer + _other -> unauthorizedI MsgUnauthorizedSchoolLecturer | otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> case route of CourseR tid ssh csh _ -> $cachedHereBinary (mAuthId, tid, ssh, csh) . exceptT return return $ do authId <- maybeExceptT AuthenticationRequired $ return mAuthId @@ -722,7 +724,7 @@ tagAccessPredicate AuthLecturer = cacheAPDB' (Just $ Right diffMinute) mkLecture return Authorized where mkLecturerList _ route _ = case route of - CourseR{} -> cacheLecturerList + CourseR{} -> cacheLecturerList EExamR{} -> Just ( AuthCacheExternalExamStaffList , fmap (setOf $ folded . _Value) . E.select . E.from $ return . (E.^. ExternalExamStaffUser) @@ -1199,7 +1201,7 @@ tagAccessPredicate AuthExamRegistered = APDB $ \_ _ mAuthId route _ -> case rout guardMExceptT hasRegistration $ unauthorizedI MsgUnauthorizedRegisteredExam return Authorized CSheetR tid ssh csh shn _ -> exceptT return return $ do - requiredExam' <- $cachedHereBinary (tid, ssh, csh, shn) . lift . E.selectMaybe . E.from $ \(course `E.InnerJoin` sheet) -> do + requiredExam' <- $cachedHereBinary (tid, ssh, csh, shn) . lift . E.selectOne . E.from $ \(course `E.InnerJoin` sheet) -> do E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId E.where_ $ course E.^. CourseTerm E.==. E.val tid E.&&. course E.^. CourseSchool E.==. E.val ssh @@ -1700,7 +1702,7 @@ evalAccessWith :: (HasCallStack, MonadThrow m, MonadAP m) => [(AuthTag, Bool)] - evalAccessWith assumptions route isWrite = do mAuthId <- liftHandler maybeAuthId evalAccessWithFor assumptions mAuthId route isWrite - + evalAccessWithDB :: (HasCallStack, MonadThrow m, MonadAP m, BackendCompatible SqlReadBackend backend) => [(AuthTag, Bool)] -> Route UniWorX -> Bool -> ReaderT backend m AuthResult evalAccessWithDB = evalAccessWith diff --git a/src/Foundation/I18n.hs b/src/Foundation/I18n.hs index fd2bb9479..9eab3e2da 100644 --- a/src/Foundation/I18n.hs +++ b/src/Foundation/I18n.hs @@ -8,7 +8,7 @@ -- 3. add constructor to list of module exports {-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} +{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-unused-top-binds #-} module Foundation.I18n ( appLanguages, appLanguagesOpts @@ -39,7 +39,7 @@ module Foundation.I18n , StudyDegreeTerm(..) , ShortStudyFieldType(..) , StudyDegreeTermType(..) - , ErrorResponseTitle(..) + , ErrorResponseTitle(..) , UniWorXMessages(..) , uniworxMessages , unRenderMessage, unRenderMessage', unRenderMessageLenient @@ -87,21 +87,30 @@ pluralDE num singularForm pluralForm | num == 1 = singularForm | otherwise = pluralForm --- pluralDEx :: (Eq a, Num a) => Char -> a -> Text -> Text --- -- ^ @pluralENs n "Monat" = pluralEN n "Monat" "Monate"@ --- pluralDEx c n t = pluralDE n t $ t `snoc` c +pluralDEx :: (Eq a, Num a) => Char -> a -> Text -> Text +pluralDEx c n t = pluralDE n t $ t `snoc` c --- -- | like `pluralDEe` but also prefixes with the number --- pluralDExN :: (Eq a, Num a, Show a) => Char -> a -> Text -> Text --- pluralDExN c n t = tshow n <> cons ' ' (pluralDEx c n t) +-- | like `pluralDEx` but also prefixes with the number +pluralDExN :: (Eq a, Num a, Show a) => Char -> a -> Text -> Text +pluralDExN c n t = tshow n <> cons ' ' (pluralDEx c n t) pluralDEe :: (Eq a, Num a) => a -> Text -> Text --- ^ @pluralENs n "Monat" = pluralEN n "Monat" "Monate"@ -pluralDEe n t = pluralDE n t $ t `snoc` 'e' +-- ^ @pluralDEe n "Monat" = pluralDEe n "Monat" "Monate"@ +pluralDEe = pluralDEx 'e' -- | like `pluralDEe` but also prefixes with the number pluralDEeN :: (Eq a, Num a, Show a) => a -> Text -> Text -pluralDEeN n t = tshow n <> cons ' ' (pluralDEe n t) +pluralDEeN = pluralDExN 'e' + +-- | postfix plural with an 'n' +pluralDEn :: (Eq a, Num a) => a -> Text -> Text +-- ^ @pluralENs n "Monat" = pluralEN n "Monat" "Monate"@ +pluralDEn = pluralDEx 'n' + +-- | like `pluralDEn` but also prefixes with the number +pluralDEnN :: (Eq a, Num a, Show a) => a -> Text -> Text +pluralDEnN = pluralDExN 'n' + noneOneMoreDE :: (Eq a, Num a) => a -- ^ Count @@ -114,14 +123,14 @@ noneOneMoreDE num noneText singularForm pluralForm | num == 1 = singularForm | otherwise = pluralForm --- noneMoreDE :: (Eq a, Num a) --- => a -- ^ Count --- -> Text -- ^ None --- -> Text -- ^ Some --- -> Text --- noneMoreDE num noneText someText --- | num == 0 = noneText --- | otherwise = someText +noneMoreDE :: (Eq a, Num a) + => a -- ^ Count + -> Text -- ^ None + -> Text -- ^ Some + -> Text +noneMoreDE num noneText someText + | num == 0 = noneText + | otherwise = someText pluralEN :: (Eq a, Num a) => a -- ^ Count @@ -136,7 +145,7 @@ pluralENs :: (Eq a, Num a) => a -- ^ Count -> Text -- ^ Singular -> Text --- ^ @pluralENs n "foo" = pluralEN n "foo" "foos"@ +-- ^ @pluralENs n "foo" = pluralEN n "foo" "foos"@ pluralENs n t = pluralEN n t $ t `snoc` 's' -- | like `pluralENs` but also prefixes with the number @@ -154,14 +163,14 @@ noneOneMoreEN num noneText singularForm pluralForm | num == 1 = singularForm | otherwise = pluralForm --- noneMoreEN :: (Eq a, Num a) --- => a -- ^ Count --- -> Text -- ^ None --- -> Text -- ^ Some --- -> Text --- noneMoreEN num noneText someText --- | num == 0 = noneText --- | otherwise = someText +noneMoreEN :: (Eq a, Num a) + => a -- ^ Count + -> Text -- ^ None + -> Text -- ^ Some + -> Text +noneMoreEN num noneText someText + | num == 0 = noneText + | otherwise = someText _ordinalEN :: ToMessage a => a @@ -181,20 +190,20 @@ notEN :: Bool -> Text notEN = bool "not" "" {- -- TODO: use this is message eventually --- Commonly used plurals +-- Commonly used plurals data Thing = Person | Examinee deriving (Eq) -thingDE :: Int -> Thing -> Text +thingDE :: Int -> Thing -> Text thingDE num = (tshow num <>) . Text.cons ' ' . thing - where + where thing :: Thing -> Text thing Person = pluralDE num "Person" "Personen" thing Examinee = pluralDE num "Prüfling" "Prüflinge" - -thingEN :: Int -> Thing -> Text + +thingEN :: Int -> Thing -> Text thingEN num t = tshow num <> Text.cons ' ' (thing t) - where + where thing :: Thing -> Text thing Person = pluralENs num "person" thing Examinee = pluralENs num "examinee" @@ -210,6 +219,9 @@ maybeBoolMessage Nothing n _ _ = n maybeBoolMessage (Just True) _ t _ = t maybeBoolMessage (Just False) _ _ f = f +-- | Convenience function avoiding type signatures +boolText :: Text -> Text -> Bool -> Text +boolText = bool newtype ShortTermIdentifier = ShortTermIdentifier TermIdentifier deriving stock (Eq, Ord, Read, Show) @@ -269,7 +281,7 @@ mkMessageAddition ''UniWorX "Avs" "messages/uniworx/categories/avs" "de-de-forma embedRenderMessage ''UniWorX ''LmsStatus (uncurry ((<>) . (<> "Status")) . Text.splitAt 3) -newtype SomeMessages master = SomeMessages [SomeMessage master] +newtype SomeMessages master = SomeMessages [SomeMessage master] deriving newtype (Semigroup, Monoid) instance master ~ master' => RenderMessage master (SomeMessages master') where @@ -602,12 +614,12 @@ unRenderMessage = unRenderMessage' (==) unRenderMessageLenient :: forall a master. (Ord a, Finite a, RenderMessage master a) => master -> Text -> [a] unRenderMessageLenient = unRenderMessage' cmp - where cmp = (==) `on` mk . under packed (filter Char.isAlphaNum . concatMap unidecode) + where cmp = (==) `on` mk . under packed (concatMap $ filter Char.isAlphaNum . unidecode) instance Default DateTimeFormatter where def = mkDateTimeFormatter (getTimeLocale' []) def appTZ -instance RenderMessage UniWorX Address where +instance RenderMessage UniWorX Address where renderMessage s l a@Address{addressName = Just aname} = aname <> cons ' ' (renderMessage s l a{addressName=Nothing}) renderMessage _ _ Address{addressEmail = mail} = "<" <> mail <> ">" diff --git a/src/Foundation/Navigation.hs b/src/Foundation/Navigation.hs index 008e68e08..367fe7a21 100644 --- a/src/Foundation/Navigation.hs +++ b/src/Foundation/Navigation.hs @@ -1,4 +1,4 @@ --- SPDX-FileCopyrightText: 2022 Gregor Kleen ,Sarah Vaupel ,Sarah Vaupel ,Steffen Jost ,Steffen Jost ,Winnie Ros +-- SPDX-FileCopyrightText: 2022-24 Gregor Kleen ,Sarah Vaupel ,Sarah Vaupel ,Steffen Jost ,Steffen Jost ,Winnie Ros ,Steffen Jost -- -- SPDX-License-Identifier: AGPL-3.0-or-later @@ -87,9 +87,9 @@ breadcrumb (AdminUserR cID) = useRunDB . maybeT (i18nCrumb MsgBreadcrumbUser $ J uid <- decrypt cID User{..} <- MaybeT $ get uid return (userDisplayName, Just UsersR) -breadcrumb (AdminUserDeleteR cID) = i18nCrumb MsgBreadcrumbUserDelete . Just $ AdminUserR cID -breadcrumb (AdminHijackUserR cID) = i18nCrumb MsgBreadcrumbUserHijack . Just $ AdminUserR cID -breadcrumb (UserNotificationR cID) = useRunDB $ do +breadcrumb (AdminUserDeleteR cID) = i18nCrumb MsgBreadcrumbUserDelete . Just $ AdminUserR cID +breadcrumb (AdminHijackUserR cID) = i18nCrumb MsgBreadcrumbUserHijack . Just $ AdminUserR cID +breadcrumb (UserNotificationR cID) = useRunDB $ do mayList <- hasReadAccessTo UsersR if | mayList @@ -122,6 +122,7 @@ breadcrumb ProblemWithoutAvsId = i18nCrumb MsgProblemsNoAvsIdHeading $ Just breadcrumb ProblemFbutNoR = i18nCrumb MsgProblemsRWithoutFHeading $ Just AdminProblemsR breadcrumb ProblemAvsSynchR = i18nCrumb MsgProblemsAvsSynchHeading $ Just AdminProblemsR breadcrumb ProblemAvsErrorR = i18nCrumb MsgProblemsAvsErrorHeading $ Just AdminProblemsR +breadcrumb ConfigInterfacesR = i18nCrumb MsgConfigInterfacesHeading $ Just AdminProblemsR breadcrumb FirmAllR = i18nCrumb MsgMenuFirms Nothing breadcrumb FirmsCommR{} = i18nCrumb MsgMenuFirmsComm $ Just FirmAllR @@ -129,7 +130,13 @@ breadcrumb FirmUsersR{} = i18nCrumb MsgMenuFirmUsers $ Just FirmAll breadcrumb (FirmSupersR fsh)= i18nCrumb MsgMenuFirmSupervisors $ Just $ FirmUsersR fsh breadcrumb (FirmCommR fsh)= i18nCrumb MsgMenuFirmsComm $ Just $ FirmUsersR fsh -breadcrumb PrintCenterR = i18nCrumb MsgMenuApc Nothing +breadcrumb CommCenterR = i18nCrumb MsgMenuCommCenter Nothing +breadcrumb MailCenterR = i18nCrumb MsgMenuMailCenter $ Just CommCenterR +breadcrumb MailHtmlR{} = i18nCrumb MsgMenuMailHtml $ Just MailCenterR +breadcrumb MailPlainR{} = i18nCrumb MsgMenuMailPlain $ Just MailCenterR +breadcrumb (MailAttachmentR mid _) = i18nCrumb MsgMenuMailAttachment $ Just $ MailHtmlR mid + +breadcrumb PrintCenterR = i18nCrumb MsgMenuApc $ Just CommCenterR breadcrumb PrintSendR = i18nCrumb MsgMenuPrintSend $ Just PrintCenterR breadcrumb PrintDownloadR{} = i18nCrumb MsgMenuPrintDownload $ Just PrintCenterR breadcrumb PrintAckR{} = i18nCrumb MsgMenuPrintSend $ Just PrintCenterR -- never displayed @@ -137,11 +144,15 @@ breadcrumb PrintAckDirectR{}= i18nCrumb MsgMenuPrintAck $ Just PrintCenter breadcrumb PrintLogR = i18nCrumb MsgMenuPrintLog $ Just PrintCenterR breadcrumb SchoolListR = i18nCrumb MsgMenuSchoolList $ Just AdminR -breadcrumb (SchoolR ssh sRoute) = case sRoute of - SchoolEditR -> useRunDB . maybeT (i18nCrumb MsgBreadcrumbSchool $ Just SchoolListR) $ do +breadcrumb (SchoolR ssh SchoolEditR) = + useRunDB . maybeT (i18nCrumb MsgBreadcrumbSchool $ Just SchoolListR) $ do School{..} <- MaybeT $ get ssh isAdmin <- lift $ hasReadAccessTo SchoolListR return (CI.original schoolName, bool Nothing (Just SchoolListR) isAdmin) +breadcrumb (SchoolR ssh (SchoolDayR d)) = do + dt <- formatTime SelFormatDate d + mr <- getMessageRender + return (mr $ MsgMenuSchoolDay ssh dt, Just SchoolListR) breadcrumb SchoolNewR = i18nCrumb MsgMenuSchoolNew $ Just SchoolListR breadcrumb (ExamOfficeR EOExamsR) = i18nCrumb MsgMenuExamOfficeExams Nothing @@ -930,19 +941,37 @@ pageActions :: ( MonadHandler m , MonadUnliftIO m ) => Route UniWorX -> m [Nav] -pageActions NewsR = return - [ NavPageActionPrimary - { navLink = NavLink - { navLabel = MsgMenuOpenCourses - , navRoute = (CourseListR, [("courses-openregistration", toPathPiece True)]) - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False +pageActions NewsR = do + now <- liftIO getCurrentTime + let nowaday = utctDay now + nd <- formatTime SelFormatDate now + schools <- useRunDB $ selectList [] [Asc SchoolShorthand] + return $ + ( NavPageActionPrimary + { navLink = NavLink + { navLabel = MsgMenuOpenCourses + , navRoute = (CourseListR, [("courses-openregistration", toPathPiece True)]) + , navAccess' = NavAccessTrue + , navType = NavTypeLink { navModal = False } + , navQuick' = mempty + , navForceActive = False + } + , navChildren = [] } - , navChildren = [] - } - ] + ) : + [ NavPageActionPrimary + { navLink = NavLink + { navLabel = MsgMenuSchoolDay ssh nd + , navRoute = SchoolR ssh $ SchoolDayR nowaday + , navAccess' = NavAccessTrue + , navType = NavTypeLink { navModal = False } + , navQuick' = mempty + , navForceActive = False + } + , navChildren = [] + } + | sch <- schools, let ssh = sch ^. _entityKey + ] pageActions (CourseR tid ssh csh CShowR) = do materialListSecondary <- pageQuickActions NavQuickViewPageActionSecondary $ CourseR tid ssh csh MaterialListR tutorialListSecondary <- pageQuickActions NavQuickViewPageActionSecondary $ CourseR tid ssh csh CTutorialListR @@ -1172,6 +1201,13 @@ pageActions SchoolListR = return , navChildren = [] } ] +pageActions (SchoolR ssh (SchoolDayR nd)) = return + [ NavPageActionPrimary + { navLink = defNavLink msg $ SchoolR ssh (SchoolDayR $ addDays n nd) + , navChildren = [] + } + | (msg, n) <- [(MsgWeekPrev, -7), (MsgDayPrev, -1), (MsgDayNext, 1), (MsgWeekNext, 7)] + ] pageActions UsersR = return [ NavPageActionPrimary { navLink = NavLink @@ -1457,6 +1493,12 @@ pageActions (ForProfileR cID) = return , navChildren = [] } ] +pageActions (ForProfileDataR cID) = return + [ NavPageActionPrimary + { navLink = defNavLink MsgAdminUserHeading $ AdminUserR cID + , navChildren = [] + } + ] pageActions TermShowR = do participantsSecondary <- pageQuickActions NavQuickViewPageActionSecondary ParticipantsListR return @@ -1946,7 +1988,7 @@ pageActions (CSheetR tid ssh csh shn SShowR) = do { navLabel = MsgMenuSheetPersonalisedFiles , navRoute = CSheetR tid ssh csh shn SPersonalFilesR , navAccess' = NavAccessDB $ - let onlyPersonalised = fmap (maybe False $ not . E.unValue) . E.selectMaybe . E.from $ \(sheet `E.InnerJoin` course) -> do + let onlyPersonalised = fmap (maybe False $ not . E.unValue) . E.selectOne . E.from $ \(sheet `E.InnerJoin` course) -> do E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId E.where_$ sheet E.^. SheetName E.==. E.val shn E.&&. course E.^. CourseTerm E.==. E.val tid @@ -2471,6 +2513,50 @@ pageActions PrintCenterR = do dayLinks <- mapM toDayAck $ Map.toAscList dayMap return $ manualSend : printLog : printAck : take 9 dayLinks +pageActions CommCenterR = return + [ NavPageActionPrimary + { navLink = defNavLink MsgMenuMailCenter MailCenterR + , navChildren = [] + } + , NavPageActionPrimary + { navLink = defNavLink MsgMenuApc PrintCenterR + , navChildren = [] + } + ] + +pageActions (MailHtmlR smid) = do + sid <- decrypt smid + usrNotiSettings <- useRunDB $ runMaybeT $ do + sm <- MaybeT $ get sid + uid <- hoistMaybe $ sentMailRecipient sm + User{userDisplayName} <- MaybeT $ get uid + uuid <- liftHandler $ encrypt uid + return NavPageActionPrimary + { navLink = defNavLink (MsgNotificationSettingsHeading userDisplayName) $ UserNotificationR uuid + , navChildren = [] + } + let linkPlain = NavPageActionPrimary + { navLink = defNavLink MsgMenuMailPlain $ MailPlainR smid + , navChildren = [] + } + return $ msnoc [linkPlain] usrNotiSettings +pageActions (MailPlainR smid) = do + sid <- decrypt smid + usrNotiSettings <- useRunDB $ runMaybeT $ do + sm <- MaybeT $ get sid + uid <- hoistMaybe $ sentMailRecipient sm + User{userDisplayName} <- MaybeT $ get uid + uuid <- liftHandler $ encrypt uid + return NavPageActionPrimary + { navLink = defNavLink (MsgNotificationSettingsHeading userDisplayName) $ UserNotificationR uuid + , navChildren = [] + } + let linkHtml = NavPageActionPrimary + { navLink = defNavLink MsgMenuMailHtml $ MailHtmlR smid + , navChildren = [] + } + return $ msnoc [linkHtml] usrNotiSettings + pageActions AdminCrontabR = return [ NavPageActionPrimary { navLink = defNavLink MsgMenuAdminJobs AdminJobsR @@ -2478,6 +2564,20 @@ pageActions AdminCrontabR = return } ] +pageActions AdminProblemsR = return + [ NavPageActionPrimary + { navLink = defNavLink MsgConfigInterfacesHeading ConfigInterfacesR + , navChildren = [] + } + , NavPageActionPrimary + { navLink = defNavLink MsgProblemsAvsSynchHeading ProblemAvsSynchR + , navChildren = [] + } + , NavPageActionSecondary + { navLink = defNavLink MsgProblemsAvsErrorHeading ProblemAvsErrorR + } + ] + pageActions _ = return [] submissionList :: ( MonadIO m @@ -2490,7 +2590,7 @@ submissionList tid csh shn uid = withReaderT (projectBackend @SqlReadBackend) . E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId E.where_ $ submissionUser E.^. SubmissionUserUser E.==. E.val uid - E.&&. sheet E.^. SheetName E.==. E.val shn + E.&&. sheet E.^. SheetName E.==. E.val shn E.&&. course E.^. CourseShorthand E.==. E.val csh E.&&. course E.^. CourseTerm E.==. E.val tid diff --git a/src/Foundation/Type.hs b/src/Foundation/Type.hs index 5c77e9863..7f6814ea7 100644 --- a/src/Foundation/Type.hs +++ b/src/Foundation/Type.hs @@ -1,4 +1,4 @@ --- SPDX-FileCopyrightText: 2022 Gregor Kleen ,Sarah Vaupel ,Steffen Jost +-- SPDX-FileCopyrightText: 2022-24 Gregor Kleen ,Sarah Vaupel ,Steffen Jost ,Steffen Jost -- -- SPDX-License-Identifier: AGPL-3.0-or-later @@ -11,11 +11,9 @@ module Foundation.Type , _SessionStorageMemcachedSql, _SessionStorageAcid , AppMemcached(..) , _memcachedKey, _memcachedConn - , AppMemcachedLocal(..) - , _memcachedLocalARC , SMTPPool , _appSettings', _appStatic, _appConnPool, _appSmtpPool, _appLdapPool, _appWidgetMemcached, _appHttpManager, _appLogger, _appLogSettings, _appCryptoIDKey, _appClusterID, _appInstanceID, _appJobState, _appSessionStore, _appSecretBoxKey, _appJSONWebKeySet, _appHealthReport, _appMemcached, _appUploadCache, _appVerpSecret, _appAuthKey, _appPersonalisedSheetFilesSeedKey, _appVolatileClusterSettingsCache, _appAvsQuery - , DB, Form, MsgRenderer, MailM, DBFile + , DB, DBRead, Form, MsgRenderer, MailM, DBFile ) where import Import.NoFoundation @@ -32,18 +30,13 @@ import qualified Jose.Jwk as Jose import qualified Database.Memcached.Binary.IO as Memcached import Network.Minio (MinioConn) -import Data.IntervalMap.Strict (IntervalMap) - import qualified Utils.Pool as Custom import Utils.Metrics (DBConnUseState) -import qualified Data.ByteString.Lazy as Lazy -import Data.Time.Clock.POSIX (POSIXTime) -import GHC.Fingerprint (Fingerprint) import Handler.Sheet.PersonalisedFiles.Types (PersonalisedSheetFilesSeedKey) -import Utils.Avs (AvsQuery) +import Utils.Avs (AvsQuery()) type SMTPPool = Pool SMTPConnection @@ -62,13 +55,6 @@ data AppMemcached = AppMemcached makeLenses_ ''AppMemcached -data AppMemcachedLocal = AppMemcachedLocal - { memcachedLocalARC :: ARCHandle (Fingerprint, Lazy.ByteString) Int (NFDynamic, Maybe POSIXTime) - , memcachedLocalHandleInvalidations :: Async () - , memcachedLocalInvalidationQueue :: TVar (Seq (Fingerprint, Lazy.ByteString)) - } deriving (Generic) - -makeLenses_ ''AppMemcachedLocal -- | The foundation datatype for your application. This can be a good place to -- keep settings and values requiring initialization before your application @@ -93,13 +79,9 @@ data UniWorX = UniWorX , appJSONWebKeySet :: Jose.JwkSet , appHealthReport :: TVar (Set (UTCTime, HealthReport)) , appMemcached :: Maybe AppMemcached - , appMemcachedLocal :: Maybe AppMemcachedLocal , appUploadCache :: Maybe MinioConn , appVerpSecret :: VerpSecret , appAuthKey :: Auth.Key - , appFileSourceARC :: Maybe (ARCHandle (FileContentChunkReference, (Int, Int)) Int ByteString) - , appFileSourcePrewarm :: Maybe (LRUHandle (FileContentChunkReference, (Int, Int)) UTCTime Int ByteString) - , appFileInjectInhibit :: TVar (IntervalMap UTCTime (Set FileContentReference)) , appPersonalisedSheetFilesSeedKey :: PersonalisedSheetFilesSeedKey , appVolatileClusterSettingsCache :: TVar VolatileClusterSettingsCache , appStartTime :: UTCTime -- for Status Page @@ -123,8 +105,9 @@ instance HasCookieSettings RegisteredCookie UniWorX where instance (MonadHandler m, HandlerSite m ~ UniWorX) => ReadLogSettings m where readLogSettings = liftIO . readTVarIO =<< getsYesod (view _appLogSettings) - + type DB = YesodDB UniWorX +type DBRead = ReaderT SqlReadBackend (HandlerFor UniWorX) type Form x = Html -> MForm (HandlerFor UniWorX) (FormResult x, WidgetFor UniWorX ()) type MsgRenderer = MsgRendererS UniWorX -- see Utils type MailM a = MailT (HandlerFor UniWorX) a diff --git a/src/Foundation/Yesod/Auth.hs b/src/Foundation/Yesod/Auth.hs index efabadc80..cd6b4c42b 100644 --- a/src/Foundation/Yesod/Auth.hs +++ b/src/Foundation/Yesod/Auth.hs @@ -107,7 +107,7 @@ authenticate creds@Creds{..} = liftHandler . runDB . withReaderT projectBackend | not isDummy -> res <$ update uid [ UserLastAuthentication =. Just now ] _other -> return res - $logDebugS "auth" $ tshow Creds{..} + $logDebugS "auth" $ tshow Creds{..} ldapPool' <- getsYesod $ view _appLdapPool flip catches excHandlers $ case ldapPool' of @@ -153,9 +153,9 @@ _upsertCampusUserMode mMode cs@Creds{..} defaultOther = apHash -ldapLookupAndUpsert :: forall m. (MonadHandler m, HandlerSite m ~ UniWorX, MonadMask m, MonadUnliftIO m) => Text -> SqlPersistT m (Entity User) -ldapLookupAndUpsert ident = - getsYesod (view _appLdapPool) >>= \case +ldapLookupAndUpsert :: forall m. (MonadHandler m, HandlerSite m ~ UniWorX, MonadMask m, MonadUnliftIO m) => Text -> SqlPersistT m (Entity User) +ldapLookupAndUpsert ident = + getsYesod (view _appLdapPool) >>= \case Nothing -> throwM $ CampusUserLdapError $ LdapHostNotResolved "No LDAP configuration in Foundation." Just ldapPool -> campusUser'' ldapPool campusUserFailoverMode ident >>= \case @@ -182,22 +182,21 @@ upsertCampusUser upsertMode ldapData = do userDefaultConf <- getsYesod $ view _appUserDefaults (newUser,userUpdate) <- decodeUser now userDefaultConf upsertMode ldapData - --TODO: newUser should be associated with a company and company supervisor through Handler.Utils.Company.upsertUserCompany, but this is called by upsertAvsUser already - conflict? oldUsers <- for (userLdapPrimaryKey newUser) $ \pKey -> selectKeysList [ UserLdapPrimaryKey ==. Just pKey ] [] user@(Entity userId userRec) <- case oldUsers of Just [oldUserId] -> updateGetEntity oldUserId userUpdate _other -> upsertBy (UniqueAuthentication (newUser ^. _userIdent)) newUser userUpdate - unless (validDisplayName (newUser ^. _userTitle) + unless (validDisplayName (newUser ^. _userTitle) (newUser ^. _userFirstName) - (newUser ^. _userSurname) + (newUser ^. _userSurname) (userRec ^. _userDisplayName)) $ - update userId [ UserDisplayName =. (newUser ^. _userDisplayName) ] - when (validEmail' (userRec ^. _userEmail)) $ do + update userId [ UserDisplayName =. (newUser ^. _userDisplayName) ] -- update invalid display names only + when (validEmail' (userRec ^. _userEmail)) $ do -- RECALL: userRec already contains basic updates let emUps = [ UserDisplayEmail =. (newUser ^. _userEmail) | not (validEmail' (userRec ^. _userDisplayEmail)) ] ++ [ UserAuthentication =. AuthLDAP | is _AuthNoLogin (userRec ^. _userAuthentication) ] - unless (null emUps) $ update userId emUps + update userId emUps -- update already checks whether list is empty -- Attempt to update ident, too: unless (validEmail' (userRec ^. _userIdent)) $ void $ maybeCatchAll (update userId [ UserIdent =. (newUser ^. _userEmail) ] >> return (Just ())) @@ -228,10 +227,10 @@ decodeUserTest mbIdent ldapData = do decodeUser :: (MonadThrow m) => UTCTime -> UserDefaultConf -> UpsertCampusUserMode -> Ldap.AttrList [] -> m (User,_) -decodeUser now UserDefaultConf{..} upsertMode ldapData = do +decodeUser now UserDefaultConf{..} upsertMode ldapData = do let - userTelephone = decodeLdap ldapUserTelephone - userMobile = decodeLdap ldapUserMobile + userTelephone = decodeLdap ldapUserTelephone <&> canonicalPhone + userMobile = decodeLdap ldapUserMobile <&> canonicalPhone userCompanyPersonalNumber = decodeLdap ldapUserFraportPersonalnummer userCompanyDepartment = decodeLdap ldapUserFraportAbteilung @@ -267,7 +266,7 @@ decodeUser now UserDefaultConf{..} upsertMode ldapData = do -- -> return $ CI.mk userEmail | otherwise -> throwM CampusUserInvalidEmail - + userLdapPrimaryKey <- if | [bs] <- ldapMap !!! ldapPrimaryKey , Right userLdapPrimaryKey'' <- Text.decodeUtf8' bs @@ -306,13 +305,13 @@ decodeUser now UserDefaultConf{..} upsertMode ldapData = do , userPrefersPostal = userDefaultPrefersPostal , .. } - userUpdate = + userUpdate = [ UserLastAuthentication =. Just now | isLogin ] ++ [ UserEmail =. userEmail | validEmail' userEmail ] ++ [ - -- UserDisplayName =. userDisplayName -- not updated here, since users are allowed to change their DisplayName; see line 272 + -- UserDisplayName =. userDisplayName -- not updated here, since users are allowed to change their DisplayName; see line 191 UserFirstName =. userFirstName - , UserSurname =. userSurname + , UserSurname =. userSurname , UserLastLdapSynchronisation =. Just now , UserLdapPrimaryKey =. userLdapPrimaryKey , UserMobile =. userMobile diff --git a/src/Handler/Admin.hs b/src/Handler/Admin.hs index fd001c768..d19b90320 100644 --- a/src/Handler/Admin.hs +++ b/src/Handler/Admin.hs @@ -1,4 +1,4 @@ --- SPDX-FileCopyrightText: 2022 Gregor Kleen ,Steffen Jost +-- SPDX-FileCopyrightText: 2022-24 Gregor Kleen ,Steffen Jost ,Steffen Jost -- -- SPDX-License-Identifier: AGPL-3.0-or-later @@ -8,23 +8,29 @@ module Handler.Admin import Import -import Jobs -- import Data.Either -import qualified Data.Set as Set +import qualified Data.Set as Set +import qualified Data.Map as Map +import qualified Data.Text as Text -- import qualified Data.Text.Lazy.Encoding as LBS -- import qualified Control.Monad.Catch as Catch -- import Servant.Client (ClientError(..), ResponseF(..)) -- import Text.Blaze.Html (preEscapedToHtml) +import Database.Persist.Sql (updateWhereCount) import Database.Esqueleto.Experimental ((:&)(..)) import qualified Database.Esqueleto.Experimental as E +import qualified Database.Esqueleto.Legacy as EL (on) -- needed for dbTable import qualified Database.Esqueleto.Utils as E +import Jobs import Handler.Utils import Handler.Utils.Avs import Handler.Utils.Users +-- import Handler.Utils.Company import Handler.Health.Interface +import Handler.Users (AllUsersAction(..)) import Handler.Admin.Test as Handler.Admin import Handler.Admin.ErrorMessage as Handler.Admin @@ -34,11 +40,29 @@ import Handler.Admin.Avs as Handler.Admin import Handler.Admin.Ldap as Handler.Admin +-- Types and Template Haskell +data ProblemTableAction = ProblemTableMarkSolved + | ProblemTableMarkUnsolved + deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic) + deriving anyclass (Universe, Finite) + +nullaryPathPiece ''ProblemTableAction $ camelToPathPiece' 2 +embedRenderMessage ''UniWorX ''ProblemTableAction id + +data ProblemTableActionData = ProblemTableMarkSolvedData + | ProblemTableMarkUnsolvedData -- Placeholder, remove later + deriving (Eq, Ord, Read, Show, Generic) + + +-- Handlers getAdminR :: Handler Html getAdminR = redirect AdminProblemsR -getAdminProblemsR :: Handler Html -getAdminProblemsR = do +getAdminProblemsR, postAdminProblemsR :: Handler Html +getAdminProblemsR = handleAdminProblems Nothing + +handleAdminProblems :: Maybe Widget -> Handler Html +handleAdminProblems mbProblemTable = do now <- liftIO getCurrentTime let nowaday = utctDay now cutOffOldDays = 1 @@ -50,26 +74,27 @@ getAdminProblemsR = do msgErrorTooltip <- messageI Error MsgMessageError let flagError = messageTooltip . bool msgErrorTooltip msgSuccessTooltip - flagWarning = messageTooltip . bool msgWarningTooltip msgSuccessTooltip + flagWarning = messageTooltip . bool msgWarningTooltip msgSuccessTooltip flagNonZero :: Int -> Widget flagNonZero n | n <= 0 = flagError True - | otherwise = messageTooltip =<< handlerToWidget (messageI Error (MsgProblemsDriverSynch n)) + | otherwise = messageTooltip =<< handlerToWidget (messageI Error (MsgProblemsDriverSynch n)) - (usersAreReachable, driversHaveAvsIds, rDriversHaveFs, noStalePrintJobs, noBadAPCids, (interfaceOks, interfaceTable)) <- runDB $ (,,,,,) - <$> areAllUsersReachable + (usersAreReachable, driversHaveAvsIds, rDriversHaveFs, noStalePrintJobs, noBadAPCids, (interfaceOks, interfaceTable)) <- runDB $ (,,,,,) + <$> areAllUsersReachable <*> allDriversHaveAvsId now <*> allRDriversHaveFs now <*> (not <$> exists [PrintJobAcknowledged ==. Nothing, PrintJobCreated <. cutOffOldTime]) - <*> (not <$> exists [PrintAcknowledgeProcessed ==. False]) - <*> mkInterfaceLogTable flagError mempty + <*> (not <$> exists [PrintAcknowledgeProcessed ==. False]) + <*> mkInterfaceLogTable mempty let interfacesBadNr = length $ filter (not . snd) interfaceOks - -- interfacesOk = all snd interfaceOks + -- interfacesOk = all snd interfaceOks + diffLics <- try retrieveDifferingLicences >>= \case -- (Left (UnsupportedContentType "text/html" resp)) -> Left $ text2widget "Html received" (Left e) -> return $ Left $ text2widget $ tshow (e :: SomeException) - (Right AvsLicenceDifferences{..}) -> do + (Right (AvsLicenceDifferences{..},_)) -> do let problemIds = avsLicenceDiffRevokeAll <> avsLicenceDiffGrantVorfeld <> avsLicenceDiffRevokeRollfeld <> avsLicenceDiffGrantRollfeld - forM_ (take 42 $ Set.toList problemIds) $ queueJob' . flip JobSynchroniseAvsId (Just nowaday) + void $ runDB $ queueAvsUpdateByAID problemIds $ Just nowaday return $ Right ( Set.size avsLicenceDiffRevokeAll , Set.size avsLicenceDiffGrantVorfeld @@ -78,7 +103,7 @@ getAdminProblemsR = do ) -- Attempt to format results in a nicer way failed, since rendering Html within a modal destroyed the page layout itself -- let procDiffLics (to0, to1, to2) = Right (Set.size to0, Set.size to1, Set.size to2) - -- diffLics <- (procDiffLics <$> retrieveDifferingLicences) `catches` + -- diffLics <- (procDiffLics . fst <$> retrieveDifferingLicences) `catches` -- [ Catch.Handler (\case (UnsupportedContentType "text/html;charset=utf-8" Response{responseBody}) -- -> return $ Left $ toWidget $ preEscapedToHtml $ fromRight "Response UTF8-decoding error" $ LBS.decodeUtf8' responseBody -- ex -> return $ Left $ text2widget $ tshow ex) @@ -86,20 +111,63 @@ getAdminProblemsR = do -- ] rerouteMail <- getsYesod $ view _appMailRerouteTo + problemLogTable <- maybeM (snd <$> runDB mkProblemLogTable) return $ return mbProblemTable -- formResult only processed in POST-Handler siteLayoutMsg MsgProblemsHeading $ do setTitleI MsgProblemsHeading $(widgetFile "admin-problems") +postAdminProblemsR = do + (problemLogRes, problemLogTable) <- runDB mkProblemLogTable + formResult problemLogRes procProblems + handleAdminProblems $ Just problemLogTable + where + procProblems :: (ProblemTableActionData, Set ProblemLogId) -> Handler () + procProblems (ProblemTableMarkSolvedData , pids) = actUpdate True pids + procProblems (ProblemTableMarkUnsolvedData, pids) = actUpdate False pids -getProblemUnreachableR :: Handler Html -getProblemUnreachableR = do + actUpdate markdone pids = do + mauid <- maybeAuthId + now <- liftIO getCurrentTime + let (pls_fltr,newv,msg) | markdone = (ProblemLogSolved ==. Nothing, Just now, MsgAdminProblemsSolved) + | otherwise = (ProblemLogSolved !=. Nothing, Nothing , MsgAdminProblemsReopened) + (fromIntegral -> oks) <- runDB $ updateWhereCount [pls_fltr, ProblemLogId <-. toList pids] + [ProblemLogSolved =. newv, ProblemLogSolver =. mauid] + let no_req = Set.size pids + mkind = if oks < no_req || no_req <= 0 then Warning else Success + addMessageI mkind $ msg oks + when (oks > 0) $ reloadKeepGetParams AdminProblemsR -- reload to update all tables + +getProblemUnreachableR, postProblemUnreachableR :: Handler Html +getProblemUnreachableR = postProblemUnreachableR +postProblemUnreachableR = do unreachables <- runDB retrieveUnreachableUsers + + -- the following form is a nearly identicaly copy from Handler.Users: + ((noreachUsersRes, noreachUsersWgt'), noreachUsersEnctype) <- runFormPost . identifyForm FIDUnreachableUsersAction $ buttonForm + let noreachUsersWgt = wrapForm noreachUsersWgt' def + { formSubmit = FormNoSubmit + , formAction = Just $ SomeRoute ProblemUnreachableR + , formEncoding = noreachUsersEnctype + } + formResult noreachUsersRes $ \case + AllUsersLdapSync -> do + forM_ unreachables $ \Entity{entityKey=uid} -> void . queueJob $ JobSynchroniseLdapUser uid + addMessageI Success . MsgSynchroniseLdapUserQueued $ length unreachables + redirect ProblemUnreachableR + AllUsersAvsSync -> do + n <- runDB $ queueAvsUpdateByUID (entityKey <$> unreachables) Nothing + addMessageI Success . MsgSynchroniseAvsUserQueued $ fromIntegral n + redirect ProblemUnreachableR + siteLayoutMsg MsgProblemsUnreachableHeading $ do setTitleI MsgProblemsUnreachableHeading [whamlet|
- #{length unreachables} _{MsgProblemsUnreachableBody} +

_{MsgProblemsUnreachableButtons} + ^{noreachUsersWgt} +
+ #{length unreachables} _{MsgProblemsUnreachableBody}