diff --git a/client/components/AdminPage/AdminFilter.js b/client/components/AdminPage/AdminFilter.js
index 48d4c40..1f27738 100644
--- a/client/components/AdminPage/AdminFilter.js
+++ b/client/components/AdminPage/AdminFilter.js
@@ -20,6 +20,9 @@ export default function AdminFilter({
}, {
key: 'currentYearEligible',
name: 'Current years eligible students',
+ }, {
+ key: 'extendedRequester',
+ name: 'Extended requester',
}, {
key: 'allStaff',
name: 'All staff (Admin/staff/distributor/reclaimer)',
diff --git a/client/components/AdminPage/AllUsersTab.js b/client/components/AdminPage/AllUsersTab.js
index 746f955..aee62a4 100644
--- a/client/components/AdminPage/AllUsersTab.js
+++ b/client/components/AdminPage/AllUsersTab.js
@@ -61,21 +61,26 @@ export default () => {
switch (filter) {
case 'all':
filtered = users
+ hiddenColumns = ['ext_wants_device', 'ext_eligible']
break
case 'deviceHolders':
filtered = users.filter(u => !!u.deviceGivenAt && !u.deviceReturned)
- hiddenColumns = ['admin', 'staff', 'distributor', 'reclaimer', 'eligible', 'digitaidot', 'enrolled', 'wants_device', 'mark_eligible', 'device_returned_at', 'device_returned_by']
+ hiddenColumns = ['ext_wants_device', 'ext_eligible', 'admin', 'staff', 'distributor', 'reclaimer', 'eligible', 'digitaidot', 'enrolled', 'wants_device', 'mark_eligible', 'device_returned_at', 'device_returned_by']
break
case 'returnedDevices':
filtered = users.filter(u => u.deviceReturned)
- hiddenColumns = ['admin', 'staff', 'distributor', 'reclaimer', 'eligible', 'digitaidot', 'enrolled', 'wants_device', 'mark_eligible']
+ hiddenColumns = ['ext_wants_device', 'ext_eligible', 'admin', 'staff', 'distributor', 'reclaimer', 'eligible', 'digitaidot', 'enrolled', 'wants_device', 'mark_eligible']
break
case 'currentYearEligible':
filtered = users.filter(u => u.signupYear === settings.currentYear && u.eligible)
- hiddenColumns = ['admin', 'staff', 'distributor', 'reclaimer', 'eligible', 'mark_eligible', 'device_returned_at', 'device_returned_by']
+ hiddenColumns = ['ext_wants_device', 'ext_eligible', 'admin', 'staff', 'distributor', 'reclaimer', 'eligible', 'mark_eligible', 'device_returned_at', 'device_returned_by']
break
case 'allStaff':
filtered = users.filter(u => u.admin || u.staff || u.distributor || u.reclaimer)
+ hiddenColumns = ['ext_wants_device', 'ext_eligible', 'student_number', 'studyPrograms', 'eligible', 'digitaidot', 'enrolled', 'wants_device', 'device_given_at', 'device_id', 'device_distributed_by', 'mark_eligible', 'mark_returned', 'device_returned_at', 'device_returned_by']
+ break
+ case 'extendedRequester':
+ filtered = users.filter(u => u.extendedEligible || u.extendedWantsDevice)
hiddenColumns = ['student_number', 'studyPrograms', 'eligible', 'digitaidot', 'enrolled', 'wants_device', 'device_given_at', 'device_id', 'device_distributed_by', 'mark_eligible', 'mark_returned', 'device_returned_at', 'device_returned_by']
break
default:
diff --git a/client/components/AdminPage/UserTable/index.js b/client/components/AdminPage/UserTable/index.js
index 2863e80..d9c2c6d 100644
--- a/client/components/AdminPage/UserTable/index.js
+++ b/client/components/AdminPage/UserTable/index.js
@@ -113,6 +113,13 @@ const UserTable = ({
getCellVal: ({ eligible }) => eligible,
width: 80,
},
+ {
+ key: 'ext_eligible',
+ label: 'ExtEligible',
+ renderCell: ({ extendedEligible }) => boolToString(extendedEligible),
+ getCellVal: ({ extendedEligible }) => extendedEligible,
+ width: 80,
+ },
{
key: 'digitaidot',
label: 'Digi skills',
@@ -134,6 +141,13 @@ const UserTable = ({
getCellVal: ({ wantsDevice }) => !!wantsDevice,
width: 130,
},
+ {
+ key: 'ext_wants_device',
+ label: 'ExtWants',
+ renderCell: ({ extendedWantsDevice }) => boolToString(extendedWantsDevice),
+ getCellVal: ({ extendedWantsDevice }) => extendedWantsDevice,
+ width: 80,
+ },
{
key: 'device_given_at',
label: 'Device given at',
diff --git a/client/components/StudentPage/ExtendedRequestDeviceForm.js b/client/components/StudentPage/ExtendedRequestDeviceForm.js
new file mode 100644
index 0000000..bacfc90
--- /dev/null
+++ b/client/components/StudentPage/ExtendedRequestDeviceForm.js
@@ -0,0 +1,95 @@
+import React, { useState } from 'react'
+import { useSelector, useDispatch } from 'react-redux'
+import {
+ Button, Segment, Form, Checkbox,
+} from 'semantic-ui-react'
+import { deviceRequestAction } from 'Utilities/redux/deviceRequestReducer'
+import { localeSelector } from 'Utilities/redux/localeReducer'
+import InstructionModal from './InstructionModal'
+
+const translations = {
+ iWantDevice: {
+ en: 'I want a device',
+ fi: 'Haluan laitteen',
+ },
+ hello: {
+ en: 'Hello',
+ fi: 'Hei',
+ },
+ youAreEntitledToADevice: {
+ en: '...',
+ fi: 'Olet oikeutettu normaalin jakelun ulkopuolella annettavaan fuksilaitteeseen',
+ },
+ email: {
+ en: 'Email',
+ fi: 'Sähköposti',
+ },
+ termsAndConditions: {
+ en: 'Read the instructions',
+ fi: 'Lue ohjeet',
+ },
+ areYouSure: {
+ en: 'Are you sure?',
+ fi: 'Oletko varma?',
+ },
+ iHaveRead: {
+ en: 'I have read and understood the instructions',
+ fi: 'Olen lukenut ja ymmärtänyt ohjeet',
+ },
+}
+
+const ExtendedRequestDeviceForm = () => {
+ const [termsOpen, setTermsOpen] = useState(false)
+ const [termsHaveBeenOpened, setTermsHaveBeenOpened] = useState(false)
+ const [termsAccepted, setTermsAccepted] = useState(false)
+ const dispatch = useDispatch()
+ const user = useSelector(state => state.user.data)
+ const locale = useSelector(localeSelector)
+
+ const handleRequestClick = () => {
+ dispatch(deviceRequestAction({ extended: true, email: null }))
+ }
+
+ const handleTermsClose = () => setTermsOpen(false)
+
+ const primaryButtonDisabled = !termsAccepted // Always disable if not valid
+
+ const handleTermsOpen = () => {
+ setTermsOpen(true)
+ setTermsHaveBeenOpened(true)
+ }
+
+ return (
+
+
+
+ {`${translations.hello[locale]} ${user.name},`}
+ {translations.youAreEntitledToADevice[locale]}
+
+
+
+ )
+}
+
+export default ExtendedRequestDeviceForm
diff --git a/client/components/StudentPage/ExtendedTaskStatus.js b/client/components/StudentPage/ExtendedTaskStatus.js
new file mode 100644
index 0000000..4ff6134
--- /dev/null
+++ b/client/components/StudentPage/ExtendedTaskStatus.js
@@ -0,0 +1,78 @@
+import React from 'react'
+import { useSelector } from 'react-redux'
+import { Segment, Icon, Header } from 'semantic-ui-react'
+import { localeSelector } from 'Utilities/redux/localeReducer'
+import TranslatedMarkdown from 'Components/TranslatedMarkdown'
+import StudentInfo from './StudentInfo'
+// import TaskInfo from './TaskInfo'
+
+const translations = {
+ taskStatus: {
+ en: 'Task status:',
+ fi: 'Tehtävien tila:',
+ },
+ beFuksi: {
+ en: 'Be a fresher',
+ fi: 'Ole fuksi',
+ },
+ registeredToRelevant: {
+ en: 'Registered to relevant course',
+ fi: 'Rekisteröitynyt relevantille kurssille',
+ },
+ digiSkillsCompleted: {
+ en: 'DIGI-A completed',
+ fi: 'DIGI-A suoritettu',
+ },
+ tasksFinished: {
+ en: 'You have completed the tasks required for fresher device. Info about device distribution will be sent to you by email.',
+ fi: 'Olet suorittanut fuksilaitteeseen vaadittavat tehtävät. Saat tiedot laitteiden jakelusta sähköpostitse.',
+ },
+}
+
+const Task = ({ task, completed }) => {
+ if (completed) {
+ return (
+
+ {task}
+
+
+ )
+ }
+ return (
+
+ {task}
+
+
+ )
+}
+
+const fake = {
+ user: {
+ eligible: true,
+ courseRegistrationCompleted: true,
+ digiSkillsCompleted: true,
+ },
+}
+
+const StudentStatusPage = ({ faking }) => {
+ const user = faking ? fake.user : useSelector(state => state.user.data)
+ const locale = useSelector(localeSelector)
+
+ return (
+
+
+ {/* */}
+
+ You will get an email when the device is ready!
+
+
+
+
+
+ )
+}
+
+export default StudentStatusPage
diff --git a/client/components/StudentPage/NotEligible.js b/client/components/StudentPage/NotEligible.js
index e81e5ba..efadfea 100644
--- a/client/components/StudentPage/NotEligible.js
+++ b/client/components/StudentPage/NotEligible.js
@@ -44,6 +44,10 @@ const translations = {
en: 'Name',
fi: 'Nimi',
},
+ hasNotDeviceGiven: {
+ en: 'Has not a device given already',
+ fi: 'Et ole vielä saanut laitetta',
+ },
}
const fake = {
@@ -65,13 +69,15 @@ export default function NotEligible({ user, notCurrentYearsFuksi, faking }) {
const EligibilityBreakdown = () => {
if (!Object.entries(eligibilityReasons).length) return null // Only users starting from 2020 have eligibilityReasons (unless updated).
+ const reasons = Object.entries(eligibilityReasons).filter(r => r[0] !== 'hasNotDeviceGiven')
+
return (
<>
{translations.header[locale]}
- {Object.entries(eligibilityReasons).map(([key, status]) => {
+ {reasons.map(([key, status]) => {
if (key === 'signedUpForFreshmanDeviceThisYear' && notCurrentYearsFuksi) {
// eslint-disable-next-line no-param-reassign
status = false
diff --git a/client/components/StudentPage/index.js b/client/components/StudentPage/index.js
index 89138de..5b18ca5 100644
--- a/client/components/StudentPage/index.js
+++ b/client/components/StudentPage/index.js
@@ -4,6 +4,8 @@ import RequestDeviceForm from './RequestDeviceForm'
import TaskStatus from './TaskStatus'
import DeviceInfo from './DeviceInfo'
import NotEligible from './NotEligible'
+import ExtendedRequestDeviceForm from './ExtendedRequestDeviceForm'
+import ExtendedTaskStatus from './ExtendedTaskStatus'
const StudentPage = () => {
const user = useSelector(state => state.user.data)
@@ -13,6 +15,8 @@ const StudentPage = () => {
if (!user) return null
if (user.deviceSerial) return
+ if (user.extendedEligible && !user.extendedWantsDevice) return
+ if (user.extendedEligible) return
if (!user.eligible || notCurrentYearsFuksi) return
if (!user.wantsDevice) return
return
diff --git a/client/util/fakeShibboleth.js b/client/util/fakeShibboleth.js
index 228adb4..6022593 100644
--- a/client/util/fakeShibboleth.js
+++ b/client/util/fakeShibboleth.js
@@ -9,6 +9,7 @@ const possibleUsers = [
schacDateOfBirth: undefined,
hyPersonStudentId: undefined,
sn: 'admin',
+ hygroupcn: 'grp-toska',
},
{
uid: 'jakelija',
@@ -45,6 +46,7 @@ const possibleUsers = [
schacDateOfBirth: 19850806,
hyPersonStudentId: 'non-fuksi',
sn: 'non-fuksi',
+ hygroupcn: 'grp-toska',
},
{
uid: 'fuksi_without_digiskills',
diff --git a/client/util/redux/deviceRequestReducer.js b/client/util/redux/deviceRequestReducer.js
index bf4bf26..514472e 100644
--- a/client/util/redux/deviceRequestReducer.js
+++ b/client/util/redux/deviceRequestReducer.js
@@ -3,10 +3,10 @@ import callBuilder from '../apiConnection'
/**
* Actions and reducers are in the same file for readability
*/
-export const deviceRequestAction = ({ email }) => {
+export const deviceRequestAction = ({ email, extended }) => {
const route = '/request_device'
const prefix = 'NEW_DEVICE_REQUEST'
- return callBuilder(route, prefix, 'post', { email })
+ return callBuilder(route, prefix, 'post', { email, extended })
}
// Reducer
diff --git a/client/util/useTranslation.js b/client/util/useTranslation.js
index 15f16f7..a3918af 100644
--- a/client/util/useTranslation.js
+++ b/client/util/useTranslation.js
@@ -5,7 +5,8 @@ import { customTextSelector } from 'Utilities/redux/serviceStatusReducer'
export default function useTranslation(translationKey) {
const customTexts = useSelector(customTextSelector)
const locale = useSelector(localeSelector)
- const translatedText = customTexts && customTexts[translationKey][locale]
+
+ const translatedText = customTexts && customTexts[translationKey] && customTexts[translationKey][locale]
return translatedText
}
diff --git a/docker-compose.yml b/docker-compose.yml
index e4fd938..80fee23 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -26,8 +26,8 @@ services:
- POSTGRES_PASSWORD=postgres
volumes:
- pg_data:/data
- adminer:
- container_name: adminer
+ adminer_fular:
+ container_name: adminer_fukrek
environment:
- ADMINER_DESIGN=pepa-linha
- ADMINER_DEFAULT_SERVER=db
diff --git a/migrations/20240125203210-extended-device-fields-to-user.js b/migrations/20240125203210-extended-device-fields-to-user.js
new file mode 100644
index 0000000..34b2cb2
--- /dev/null
+++ b/migrations/20240125203210-extended-device-fields-to-user.js
@@ -0,0 +1,17 @@
+
+
+module.exports = {
+ up: (queryInterface, Sequelize) => queryInterface.sequelize.transaction(t => Promise.all([
+ queryInterface.addColumn('users', 'extended_wants_device', {
+ type: Sequelize.BOOLEAN,
+ }, { transaction: t }),
+ queryInterface.addColumn('users', 'extended_eligible', {
+ type: Sequelize.BOOLEAN,
+ }, { transaction: t }),
+ ])),
+
+ down: queryInterface => queryInterface.sequelize.transaction(t => Promise.all([
+ queryInterface.removeColumn('users', 'extended_wants_device', { transaction: t }),
+ queryInterface.removeColumn('users', 'extended_eligible', { transaction: t }),
+ ])),
+}
diff --git a/server/controllers/userController.js b/server/controllers/userController.js
index b643367..9fa8df8 100644
--- a/server/controllers/userController.js
+++ b/server/controllers/userController.js
@@ -14,8 +14,8 @@ const getUser = async (req, res) => {
const userIsPotentiallyEligible = user.isStudent && !user.hasDeviceGiven && !userIsEligibleThisYear
if (userIsPotentiallyEligible) {
- await user.checkAndUpdateEligibility()
- userIsEligibleThisYear = user.eligible && user.signupYear === settings.currentYear
+ await user.checkAndUpdateEligibility(req.toska)
+ userIsEligibleThisYear = user.extendedEligible || (user.eligible && user.signupYear === settings.currentYear)
}
if (userIsEligibleThisYear && !user.hasCompletedAllTasks) {
@@ -49,16 +49,16 @@ const getLogoutUrl = async (req, res) => {
}
const requestDevice = async (req, res) => {
- const { email } = req.body
+ const { email, extended } = req.body
const { user } = req
const settings = await ServiceStatus.getObject()
- const userIsEligibleThisYear = user.eligible && user.signupYear === settings.currentYear
+ const userIsEligibleThisYear = extended || (user.eligible && user.signupYear === settings.currentYear)
if (!userIsEligibleThisYear) throw new ForbiddenError('Not eligible')
if (email !== null && !validateEmail(email)) throw new ParameterError('Invalid email')
- await user.requestDevice(email)
+ await user.requestDevice(email, extended)
await completionChecker(user)
return res.json(user)
}
diff --git a/server/middleware/authenticationMiddleware.js b/server/middleware/authenticationMiddleware.js
index b508ebe..7fc51a2 100644
--- a/server/middleware/authenticationMiddleware.js
+++ b/server/middleware/authenticationMiddleware.js
@@ -12,6 +12,7 @@ const authentication = async (req, res, next) => {
hypersonstudentid: hyPersonStudentId = null,
sn = null,
uid = null,
+ hygroupcn,
} = req.headers
if (!uid) return res.status(403).json({ error: 'forbidden' })
@@ -27,6 +28,10 @@ const authentication = async (req, res, next) => {
logger.warn(`Non superadmin ${uid} tried to use loginAs without permissions`)
return res.sendStatus(403)
}
+
+ console.log('hygroupcn', hygroupcn)
+ req.toska = hygroupcn && hygroupcn.includes('grp-toska')
+
const foundUser = await User.findOne({
where: { userId: uid },
include: [
@@ -102,7 +107,7 @@ const authentication = async (req, res, next) => {
// eligibilityReasons,
})
- const { eligible, eligibilityReasons } = await newUser.checkEligibility()
+ const { eligible, eligibilityReasons, extendedEligible } = await newUser.checkEligibility()
const { digiSkills, hasEnrollments } = await newUser.getStatus()
@@ -113,6 +118,7 @@ const authentication = async (req, res, next) => {
digiSkillsCompleted: digiSkills,
courseRegistrationCompleted: hasEnrollments,
eligibilityReasons,
+ extendedEligible: extendedEligible && req.toska,
})
req.user = newUser
diff --git a/server/middleware/studentMiddleware.js b/server/middleware/studentMiddleware.js
index cf74da9..8c93041 100644
--- a/server/middleware/studentMiddleware.js
+++ b/server/middleware/studentMiddleware.js
@@ -6,6 +6,7 @@ const checkStudent = async (req, _res, next) => {
if (!studentNumber) throw new ParameterError('Studentnumber required')
const student = await User.findStudent(studentNumber)
+
if (!student) throw new NotFoundError('student not found')
req.student = student
diff --git a/server/models/lib/apiInterface.js b/server/models/lib/apiInterface.js
index f05f242..bd52e97 100644
--- a/server/models/lib/apiInterface.js
+++ b/server/models/lib/apiInterface.js
@@ -5,6 +5,8 @@ const {
} = require('../../util/common')
const mock = require('./mock')
+const useMock = true
+
class ApiInterface {
constructor() {
this.userApi = axios.create({
@@ -20,8 +22,9 @@ class ApiInterface {
}
async getStudyRights(studentNumber) {
- if (!inProduction) return Promise.resolve(mock.findStudyrights(studentNumber))
+ if (!inProduction && useMock) return Promise.resolve(mock.findStudyrights(studentNumber))
const { data } = await this.userApi.get(`/students/${studentNumber}/studyrights`)
+
return data
}
@@ -57,7 +60,7 @@ class ApiInterface {
}
async getSemesterEnrollments(studentNumber) {
- if (!inProduction) return Promise.resolve(mock.findSemesterEnrollments(studentNumber))
+ if (!inProduction && useMock) return Promise.resolve(mock.findSemesterEnrollments(studentNumber))
if (!SIS) {
const res = await this.userApi.get(`/students/${studentNumber}/semesterEnrollments`)
return res.data
@@ -67,7 +70,7 @@ class ApiInterface {
}
async getYearsCredits(studentNumber, startingSemester, signUpYear) {
- if (!inProduction) return Promise.resolve(mock.findFirstYearCredits(studentNumber))
+ if (!inProduction && useMock) return Promise.resolve(mock.findFirstYearCredits(studentNumber))
if (!SIS) {
const res = await this.userApi.get(`/students/${studentNumber}/fuksiYearCredits/${startingSemester}`)
return res.data
diff --git a/server/models/lib/mock.js b/server/models/lib/mock.js
index 0f8f010..30a88cb 100644
--- a/server/models/lib/mock.js
+++ b/server/models/lib/mock.js
@@ -55,13 +55,14 @@ const mockData = {
elements: [
{
code: 'KH50_005',
- start_date: '2019-07-31T21:00:00.000Z',
+ start_date: '2023-07-31T21:00:00.000Z',
end_date: '2025-07-30T21:00:00.000Z',
},
],
- admission_date: '2017-06-30T21:00:00.000Z',
- end_date: '2024-07-30T21:00:00.000Z',
- start_date: '2017-07-31T21:00:00.000Z',
+ admission_date: '2023-06-30T21:00:00.000Z',
+ end_date: '2025-07-30T21:00:00.000Z',
+ start_date: '2023-07-31T21:00:00.000Z',
+ id: 'hy-opinoik-131106475',
},
],
},
@@ -69,16 +70,159 @@ const mockData = {
md5: '12345',
status: 200,
elapsed: 0.002677027,
+ data: {
+ 'hy-opinoik-131106475': [
+ {
+ full_time_student: 'true',
+ semester_enrollment_type_code: 1,
+ absence_reason_code: null,
+ semester_enrollment_date: '2016-06-30T21:00:00.000Z',
+ semester_code: 139,
+ },
+ {
+ full_time_student: 'true',
+ semester_enrollment_type_code: 1,
+ absence_reason_code: null,
+ semester_enrollment_date: '2023-06-30T21:00:00.000Z',
+ semester_code: 147,
+ },
+ ],
+ },
+ },
+ },
+ 'non-fuksi': {
+ studyrights: {
data: [
{
- full_time_student: 'true',
- semester_enrollment_type_code: 1,
- absence_reason_code: null,
- semester_enrollment_date: '2016-06-30T21:00:00.000Z',
- semester_code: 139,
+ faculty_code: 'H50',
+ elements: [
+ {
+ code: 'KH50_005',
+ start_date: '2019-07-31T21:00:00.000Z',
+ end_date: '2025-07-30T21:00:00.000Z',
+ },
+ ],
+ admission_date: '2017-06-30T21:00:00.000Z',
+ end_date: '2024-07-30T21:00:00.000Z',
+ start_date: '2019-07-31T21:00:00.000Z',
+ id: 'hy-opinoik-131106475',
},
],
},
+ semesterEnrollments: {
+ md5: '12345',
+ status: 200,
+ elapsed: 0.002677027,
+ data: {
+ 'hy-avoin-ew-sr-C5960': [],
+ 'hy-avoin-ew-sr-C5DA9': [],
+ 'hy-avoin-ew-sr-C5DAA': [],
+ 'hy-avoin-ew-sr-C5F09': [],
+ 'hy-avoin-ew-sr-C7450': [],
+ 'hy-avoin-ew-sr-C7451': [],
+ 'hy-avoin-ew-sr-C7452': [],
+ 'hy-avoin-ew-sr-CD0E1': [],
+ 'hy-avoin-ew-sr-CD0E2': [],
+ 'hy-avoin-ew-sr-D392D': [],
+ 'hy-avoin-ew-sr-D3AA9': [],
+ 'hy-avoin-ew-sr-D4C67': [],
+ 'hy-avoin-ew-sr-D66F1': [],
+ 'hy-opinoik-128685407': [
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 139,
+ },
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 140,
+ },
+ ],
+ 'hy-opinoik-129282918': [
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 139,
+ },
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 140,
+ },
+ ],
+ 'hy-opinoik-131106475': [
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 139,
+ },
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 140,
+ },
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 141,
+ },
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 142,
+ },
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 143,
+ },
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 144,
+ },
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 145,
+ },
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 146,
+ },
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 147,
+ },
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 148,
+ },
+ {
+ semester_enrollment_type_code: 2,
+ semester_code: 149,
+ },
+ {
+ semester_enrollment_type_code: 2,
+ semester_code: 150,
+ },
+ ],
+ 'hy-opinoik-135812691': [
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 140,
+ },
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 141,
+ },
+ ],
+ 'hy-opinoik-136744535': [
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 140,
+ },
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 141,
+ },
+ {
+ semester_enrollment_type_code: 1,
+ semester_code: 142,
+ },
+ ],
+ },
+ },
},
fuksi_without_digiskills: {
studyrights: {
diff --git a/server/models/user.js b/server/models/user.js
index aea3d67..ed152f7 100644
--- a/server/models/user.js
+++ b/server/models/user.js
@@ -12,6 +12,7 @@ const api = new ApiInterface()
const includesValidBachelorStudyright = async (studyrights) => {
const acceptableStudyProgramCodes = (await StudyProgram.findAll({ attributes: ['code'] })).map(({ code }) => code)
+
return !!studyrights
.reduce((pre, { elements }) => pre.concat(elements), [])
.find(({ code, end_date }) => acceptableStudyProgramCodes.includes(code) && new Date(end_date) > new Date().getTime())
@@ -39,8 +40,10 @@ const getStudyrightValidities = async (studyrights, semesterEnrollments, current
const previousStudyrightIsPossiblyNew = !hasPre2008Studyright && !hasNewStudyright && hasPreviousStudyright
+ const flattenEnrolments = Object.values(semesterEnrollments.data).reduce((set, obj) => set.concat(obj), [])
+
if (previousStudyrightIsPossiblyNew) {
- const hasBeenPresentBefore = semesterEnrollments.data.some(({ semester_code, semester_enrollment_type_code }) => (
+ const hasBeenPresentBefore = flattenEnrolments.some(({ semester_code, semester_enrollment_type_code }) => (
semester_code < currentSemester && semester_enrollment_type_code !== 2))
if (!hasBeenPresentBefore) {
@@ -91,8 +94,10 @@ class User extends Model {
})
}
+
async getStudyRights() {
if (this.studyrights) return this.studyrights
+
const studyrights = await api.getStudyRights(this.studentNumber)
this.studyrights = studyrights
return studyrights
@@ -117,7 +122,10 @@ class User extends Model {
return api.getYearsCredits(this.studentNumber, startingSemester, this.signupYear)
}
+
async checkEligibility() {
+ const flattenEnrolmentsFor = (rights, enrollments) => rights.reduce((set, right) => set.concat(enrollments[right]), [])
+
const settings = await ServiceStatus.getObject()
const studyrights = await this.getStudyRights()
const semesterEnrollments = await this.getSemesterEnrollments()
@@ -128,7 +136,9 @@ class User extends Model {
hasValidBachelorsStudyright,
} = await getStudyrightValidities(studyrights, semesterEnrollments, settings.currentSemester)
- const isPresent = semesterEnrollments.data.some(enrollment => (
+ const flattenEnrolments = flattenEnrolmentsFor(studyrights.map(s => s.id), semesterEnrollments.data)
+
+ const isPresent = flattenEnrolments.some(enrollment => (
enrollment.semester_code === settings.currentSemester && enrollment.semester_enrollment_type_code === 1))
return {
@@ -137,7 +147,9 @@ class User extends Model {
hasValidStudyright: hasValidBachelorsStudyright,
hasNoPreviousStudyright: !hasPreviousStudyright,
isPresent,
+ hasNotDeviceGiven: !this.hasDeviceGiven,
},
+ extendedEligible: (hasPreviousStudyright && isPresent && hasValidBachelorsStudyright && !this.hasDeviceGiven),
}
}
@@ -181,11 +193,11 @@ class User extends Model {
}
}
- async checkAndUpdateEligibility() {
+ async checkAndUpdateEligibility(inToska = false) {
try {
const settings = await ServiceStatus.getObject()
- const { eligible, eligibilityReasons } = await this.checkEligibility()
+ const { eligible, eligibilityReasons, extendedEligible } = await this.checkEligibility()
if (eligible) {
await this.createUserStudyprograms()
@@ -194,9 +206,18 @@ class User extends Model {
logger.info(`${this.studentNumber} eligibility updated automatically`)
}
+ if (extendedEligible && inToska) {
+ await this.createUserStudyprograms()
+ this.extendedEligible = extendedEligible
+ this.signupYear = settings.currentYear
+ logger.info(`${this.studentNumber} extendedEligible updated automatically`)
+ }
+
this.eligibilityReasons = eligibilityReasons
await this.save()
} catch (e) {
+ console.log(e)
+
logger.error(`Failed checking and updating ${this.studentNumber} eligibility`)
}
}
@@ -324,8 +345,8 @@ class User extends Model {
})
}
- async requestDevice(email) {
- await this.update({ wantsDevice: true, personalEmail: email })
+ async requestDevice(email, extended = false) {
+ await this.update({ wantsDevice: !extended, extendedWantsDevice: extended, personalEmail: email })
}
async claimDevice(deviceId, deviceDistributedBy) {
@@ -466,6 +487,14 @@ User.init(
type: DataTypes.BOOLEAN,
field: 'third_year_or_later_student',
},
+ extendedEligible: {
+ type: DataTypes.BOOLEAN,
+ field: 'extended_eligible',
+ },
+ extendedWantsDevice: {
+ type: DataTypes.BOOLEAN,
+ field: 'extended_wants_device',
+ },
isStudent: {
type: DataTypes.VIRTUAL,
get() {