diff --git a/domain/curriculum.js b/domain/curriculum.js
index 93632caf..4528d081 100644
--- a/domain/curriculum.js
+++ b/domain/curriculum.js
@@ -150,6 +150,83 @@ function curriculumInfo({ programmeTermYear = {}, curriculum }) {
}
}
+function curriculumInfoFromLadok({ programmeTermYear = {}, curriculum }) {
+ let code = ''
+ let specializationName = null
+ let isCommon = true
+
+ const participations = {}
+ const isFirstSpec = false
+
+ const { programmeSpecialization, studyYears } = curriculum
+ const { studyYear } = programmeTermYear
+
+ if (programmeSpecialization) {
+ code = programmeSpecialization.programmeSpecializationCode
+ specializationName = programmeSpecialization.title
+ isCommon = false
+ }
+
+ const [curriculumStudyYear] = studyYears.filter(s => Math.abs(s.yearNumber) === Math.abs(studyYear))
+
+ if (curriculumStudyYear) {
+ for (const course of curriculumStudyYear.courses) {
+ if (!participations[course.Valvillkor]) participations[course.Valvillkor] = []
+
+ const termCode = course.startperiod.code.startsWith('HT') ? '1' : '2'
+ const year = course.startperiod.code.replace(/[^0-9]/g, '')
+ const term = `${year}${termCode}`
+
+ const creditsPerPeriod = [0, 0, 0, 0, 0, 0]
+
+ course.Tillfallesperioder.forEach(period => {
+ if (period.Lasperiodsfordelning) {
+ period.Lasperiodsfordelning.forEach(lasperiod => {
+ const periodIndex =
+ {
+ P1: 1,
+ P2: 2,
+ P3: 3,
+ P4: 4,
+ }[lasperiod.Lasperiodskod] || 0
+
+ creditsPerPeriod[periodIndex] += lasperiod.Omfattningsvarde
+ })
+ } else {
+ creditsPerPeriod[1] += period.Omfattningsvarde // Default to first period
+ }
+ })
+
+ participations[course.Valvillkor].push({
+ course: {
+ courseCode: course.kod,
+ title: course.benamning,
+ credits: course.omfattning.number,
+ formattedCredits: course.omfattning.formattedWithUnit,
+ educationalLevel: course.utbildningstyp?.level?.name,
+ electiveCondition: course.Valvillkor,
+ },
+ applicationCodes: [course.tillfalleskod],
+ term,
+ creditsPerPeriod,
+ })
+
+ participations[course.Valvillkor].sort((a, b) => a.term.localeCompare(b.term))
+ }
+ }
+
+ const hasInfo = Object.keys(participations).length !== 0
+
+ return {
+ code,
+ specializationName,
+ isCommon,
+ participations,
+ isFirstSpec,
+ hasInfo,
+ }
+}
+
function setFirstSpec(cis) {
for (let i = 0; i < cis.length; i++) {
const ci = cis[i]
@@ -164,6 +241,7 @@ const ELECTIVE_CONDITIONS = ['ALL', 'O', 'VV', 'R', 'V']
module.exports = {
curriculumInfo,
+ curriculumInfoFromLadok,
setFirstSpec,
filterCourseRoundsForNthYear,
ELECTIVE_CONDITIONS,
diff --git a/package-lock.json b/package-lock.json
index cec17827..e9d2f994 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -94,10 +94,11 @@
}
},
"../studadm-om-kursen-packages/packages/om-kursen-ladok-client": {
- "version": "0.0.1",
+ "name": "@kth/om-kursen-ladok-client",
+ "version": "1.1.0",
"dependencies": {
- "ladok-attributvarde-utils": "file:../ladok-attributvarde-utils",
- "ladok-mellanlager-client": "file:../ladok-mellanlager-client"
+ "@kth/ladok-attributvarde-utils": "file:../ladok-attributvarde-utils",
+ "@kth/ladok-mellanlager-client": "file:../ladok-mellanlager-client"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
diff --git a/public/js/app/pages/Appendix1.jsx b/public/js/app/pages/Appendix1.jsx
index 2fe7fb5e..8cf3d687 100644
--- a/public/js/app/pages/Appendix1.jsx
+++ b/public/js/app/pages/Appendix1.jsx
@@ -21,7 +21,7 @@ import { courseLink } from '../util/links'
function CourseListTableRow({ course }) {
const { language } = useStore()
const t = translate(language)
- const { code, name, comment, credits, creditAbbr, level } = course
+ const { code, name, comment, credits, creditAbbr, formattedCredits, formattedLevel, level } = course
const courselink = courseLink(code, language)
return (
@@ -33,8 +33,10 @@ function CourseListTableRow({ course }) {
{name}
{comment && {comment}}
-
{`${formatCredits(language, credits)} ${creditAbbr}`} |
- {`${t('programme_edulevel')[level]}`} |
+
+ {formattedCredits ? formattedCredits : `${formatCredits(language, credits)} ${creditAbbr}`}
+ |
+ {formattedLevel ? formattedLevel : `${t('programme_edulevel')[level]}`} |
)
}
@@ -44,8 +46,10 @@ const courseType = PropTypes.shape({
name: PropTypes.string.isRequired,
comment: PropTypes.oneOfType([PropTypes.string, undefined]).isRequired,
credits: PropTypes.number.isRequired,
- creditAbbr: PropTypes.string.isRequired,
- level: PropTypes.string.isRequired,
+ creditAbbr: PropTypes.string,
+ formattedCredits: PropTypes.string,
+ level: PropTypes.string,
+ formattedLevel: PropTypes.string,
})
CourseListTableRow.propTypes = {
diff --git a/public/js/app/pages/Curriculum.jsx b/public/js/app/pages/Curriculum.jsx
index 449fba41..90b936c3 100644
--- a/public/js/app/pages/Curriculum.jsx
+++ b/public/js/app/pages/Curriculum.jsx
@@ -43,11 +43,14 @@ function CourseTableRow({
creditsPerPeriod,
}) {
const { language } = useStore()
+ console.log('here', credits)
return (
{courseNameCellData} |
{applicationCodeCellData} |
- {`${formatCredits(language, credits)} ${creditUnitAbbr}`} |
+
+ {creditUnitAbbr ? `${formatCredits(language, credits)} ${creditUnitAbbr}` : credits}
+ |
)
@@ -59,8 +62,11 @@ function CourseTableRows({ participations }) {
return participations.map(participation => {
const { course, applicationCodes, term, creditsPerPeriod } = participation
- const { courseCode, title, credits, creditUnitAbbr, comment } = course
- const translatedCreditUnitAbbr = translateCreditUnitAbbr(language, creditUnitAbbr)
+ console.log(course)
+
+ const { courseCode, title, credits, formattedCredits, creditUnitAbbr, comment } = course
+ let translatedCreditUnitAbbr
+ if (!formattedCredits) translatedCreditUnitAbbr = translateCreditUnitAbbr(language, creditUnitAbbr)
const currentTerm = getCurrentTerm()
const courseNameCellData = (
<>
@@ -75,7 +81,7 @@ function CourseTableRows({ participations }) {
courseCode={courseCode}
courseNameCellData={courseNameCellData}
applicationCodeCellData={applicationCodeCellData}
- credits={credits}
+ credits={formattedCredits ? formattedCredits : credits}
creditUnitAbbr={translatedCreditUnitAbbr}
creditsPerPeriod={creditsPerPeriod}
/>
diff --git a/server/controllers/appendix1Ctrl.js b/server/controllers/appendix1Ctrl.js
index c2751e7c..778cb3d7 100644
--- a/server/controllers/appendix1Ctrl.js
+++ b/server/controllers/appendix1Ctrl.js
@@ -45,10 +45,10 @@ async function getIndex(req, res, next) {
const options = { applicationStore, lang, programmeCode, term }
log.info(`Starting to fill in application store ${storeId} on server side `, { programmeCode })
- const { programmeName } = await fetchAndFillProgrammeDetails(options, storeId)
+ const { programmeName, tillfalleUid } = await fetchAndFillProgrammeDetails(options, storeId)
fillStoreWithQueryParams(options)
- await fetchAndFillCurriculumList(options)
+ await fetchAndFillCurriculumList(options, tillfalleUid)
const compressedStoreCode = getCompressedStoreCode(applicationStore)
log.info(`${storeId} store was filled in and compressed on server side`, { programmeCode })
diff --git a/server/controllers/appendix2Ctrl.js b/server/controllers/appendix2Ctrl.js
index fdde112e..1defe127 100644
--- a/server/controllers/appendix2Ctrl.js
+++ b/server/controllers/appendix2Ctrl.js
@@ -54,9 +54,9 @@ async function getIndex(req, res, next) {
const options = { applicationStore, lang, programmeCode, term }
log.info(`Starting to fill application store, for ${storeId}`)
- const { programmeName } = await fetchAndFillProgrammeDetails(options, storeId)
+ const { programmeName, tillfalleUid } = await fetchAndFillProgrammeDetails(options, storeId)
fillStoreWithQueryParams(options)
- await fetchAndFillSpecializations(options)
+ await fetchAndFillSpecializations(options, tillfalleUid)
const compressedStoreCode = getCompressedStoreCode(applicationStore)
log.info(`${storeId} store was filled in and compressed on server side`)
diff --git a/server/controllers/curriculumCtrl.js b/server/controllers/curriculumCtrl.js
index 2e6b3763..0953c18a 100644
--- a/server/controllers/curriculumCtrl.js
+++ b/server/controllers/curriculumCtrl.js
@@ -5,13 +5,15 @@ const { server: serverConfig } = require('../configuration')
const i18n = require('../../i18n')
const koppsApi = require('../kopps/koppsApi')
-const { curriculumInfo, setFirstSpec } = require('../../domain/curriculum')
+const { curriculumInfo, curriculumInfoFromLadok, setFirstSpec } = require('../../domain/curriculum')
const { calculateStartTerm } = require('../../domain/academicYear')
const { createProgrammeBreadcrumbs } = require('../utils/breadcrumbUtil')
const { getServerSideFunctions } = require('../utils/serverSideRendering')
const { programmeFullName } = require('../utils/programmeFullName')
+const { getProgramStructure, getActiveProgramTillfalle } = require('../ladok/ladokApi')
+
const {
fillStoreWithQueryParams,
fetchAndFillProgrammeDetails,
@@ -79,21 +81,32 @@ function _compareCurriculum(a, b) {
* @param {string} options.term
* @param {string} storeId
*/
-async function _fetchAndFillCurriculumByStudyYear(options, storeId) {
+async function _fetchAndFillCurriculumByStudyYear(options, storeId, tillfalleUid) {
const { applicationStore, lang, programmeCode, studyYear, term } = options
const { studyProgrammeId, statusCode } = await fetchAndFillStudyProgrammeVersion({ ...options, storeId })
if (!studyProgrammeId) {
_setErrorMissingAdmission(applicationStore, statusCode)
return
} // react NotFound
- const { curriculums, statusCode: secondStatusCode } = await koppsApi.listCurriculums(studyProgrammeId, lang)
- applicationStore.setStatusCode(secondStatusCode)
- if (secondStatusCode !== 200) return // react NotFound
-
- const curriculumsWithCourseRounds = await _addCourseRounds(curriculums, programmeCode, term, studyYear, lang)
- applicationStore.setCurriculums(curriculumsWithCourseRounds)
- const curriculumInfos = curriculumsWithCourseRounds
- .map(curriculum => curriculumInfo({ programmeTermYear: { programStartTerm: term, studyYear }, curriculum }))
+
+ let curriculumData
+
+ if (tillfalleUid) {
+ curriculumData = await getProgramStructure(tillfalleUid, lang)
+ } else {
+ const { curriculums, statusCode: secondStatusCode } = await koppsApi.listCurriculums(studyProgrammeId, lang)
+ applicationStore.setStatusCode(secondStatusCode)
+ if (secondStatusCode !== 200) return // react NotFound
+ curriculumData = await _addCourseRounds(curriculums, programmeCode, term, studyYear, lang)
+ }
+
+ applicationStore.setCurriculums(curriculumData)
+ const curriculumInfos = curriculumData
+ .map(curriculum => {
+ if (tillfalleUid)
+ return curriculumInfoFromLadok({ programmeTermYear: { programStartTerm: term, studyYear }, curriculum })
+ else return curriculumInfo({ programmeTermYear: { programStartTerm: term, studyYear }, curriculum })
+ })
.filter(ci => ci.hasInfo)
curriculumInfos.sort(_compareCurriculum)
setFirstSpec(curriculumInfos)
@@ -134,10 +147,11 @@ async function getIndex(req, res, next) {
const options = { applicationStore, lang, programmeCode, term, studyYear }
log.info(`Starting to fill in application store ${storeId} on server side `, { programmeCode })
- const { programmeName } = await fetchAndFillProgrammeDetails(options, storeId)
+
+ const { programmeName, tillfalleUid } = await fetchAndFillProgrammeDetails(options, storeId)
fillStoreWithQueryParams(options)
- await _fetchAndFillCurriculumByStudyYear(options, storeId)
+ await _fetchAndFillCurriculumByStudyYear(options, storeId, tillfalleUid)
const compressedStoreCode = getCompressedStoreCode(applicationStore)
log.info(`${storeId} store was filled in and compressed`, { programmeCode })
diff --git a/server/ladok/ladokApi.js b/server/ladok/ladokApi.js
index 81d82aab..43bc4d3e 100644
--- a/server/ladok/ladokApi.js
+++ b/server/ladok/ladokApi.js
@@ -9,6 +9,20 @@ async function searchCourses(pattern, lang) {
return courses
}
+async function getActiveProgramTillfalle(programCode, startPeriod, lang) {
+ const client = createApiClient(serverConfig.ladokMellanlagerApi)
+ const courses = await client.getActiveProgramTillfalle(programCode, startPeriod, lang)
+ return courses
+}
+
+async function getProgramStructure(programCode, lang) {
+ const client = createApiClient(serverConfig.ladokMellanlagerApi)
+ const courses = await client.getUtbildningstilfalleStructure(programCode, lang)
+ return courses
+}
+
module.exports = {
searchCourses,
+ getProgramStructure,
+ getActiveProgramTillfalle,
}
diff --git a/server/stores/programmeStoreSSR.js b/server/stores/programmeStoreSSR.js
index 5f161c67..025927bf 100644
--- a/server/stores/programmeStoreSSR.js
+++ b/server/stores/programmeStoreSSR.js
@@ -3,6 +3,7 @@ const log = require('@kth/log')
const { browser: browserConfig, server: serverConfig } = require('../configuration')
const koppsApi = require('../kopps/koppsApi')
const { programmeLink } = require('../../domain/links')
+const { getActiveProgramTillfalle, getProgramStructure } = require('../ladok/ladokApi')
/**
* add props to a MobX-stores on server side
@@ -43,15 +44,36 @@ function fillBrowserConfigWithHostUrl({ applicationStore }) {
* @param {string} storeId
* @returns {object}
*/
-async function fetchAndFillProgrammeDetails({ applicationStore, lang, programmeCode }, storeId = '') {
+async function fetchAndFillProgrammeDetails({ applicationStore, term, lang, programmeCode }, storeId = '') {
log.info('Fetching programme from KOPPs API, programmeCode:', programmeCode)
- const { programme, statusCode } = await koppsApi.getProgramme(programmeCode, lang)
- applicationStore.setStatusCode(statusCode)
- if (statusCode !== 200 || !programme) {
- log.debug('Failed to fetch from KOPPs api, programmeCode:', programmeCode)
- return
- } // react NotFound
+ let programDetails
+
+ const year = term.slice(0, 4)
+ const convertedTerm = `${term.endsWith('1') ? 'VT' : 'HT'}${term.slice(0, 4)}`
+
+ if (year >= 2024) {
+ // TODO - this is for test now, the exact year needs to be changed after the actual user stories are ready
+ const program = await getActiveProgramTillfalle(programmeCode, convertedTerm, lang)
+ programDetails = {
+ title: program.benamning,
+ lengthInStudyYears: program.lengthInStudyYears,
+ creditUnitAbbr: program.creditUnitAbbr,
+ owningSchoolCode: program.organisation.name,
+ credits: program.omfattning.number,
+ titleOtherLanguage: program.organisation.nameOther,
+ educationalLevel: program.tilltradesniva.name,
+ tillfalleUid: program.uid,
+ }
+ } else {
+ const { programme, statusCode } = await koppsApi.getProgramme(programmeCode, lang)
+ programDetails = programme
+ applicationStore.setStatusCode(statusCode)
+ if (statusCode !== 200 || !programme) {
+ log.debug('Failed to fetch from KOPPs api, programmeCode:', programmeCode)
+ return
+ } // react NotFound
+ }
log.info('Successfully fetched programme from KOPPs API, programmeCode:', programmeCode)
@@ -63,7 +85,7 @@ async function fetchAndFillProgrammeDetails({ applicationStore, lang, programmeC
credits,
titleOtherLanguage,
educationalLevel,
- } = programme
+ } = programDetails
applicationStore.setProgrammeName(programmeName)
applicationStore.setLengthInStudyYears(lengthInStudyYears)
if (storeId === 'appendix1' || storeId === 'pdfStore') {
@@ -78,7 +100,7 @@ async function fetchAndFillProgrammeDetails({ applicationStore, lang, programmeC
applicationStore.setEducationalLevel(educationalLevel)
}
// eslint-disable-next-line consistent-return
- return { programmeName, ...programme }
+ return { programmeName, ...programDetails }
}
/**
@@ -97,7 +119,6 @@ async function fetchAndFillStudyProgrammeVersion({ applicationStore, lang, progr
if (statusCode !== 200) return { statusCode } // react NotFound
if (
- storeId === 'appendix1' ||
storeId === 'eligibility' ||
storeId === 'extent' ||
storeId === 'implementation' ||
@@ -214,6 +235,85 @@ function _parseCurriculumsAndFillStore(applicationStore, curriculums) {
})
}
+function _parseCurriculumsAndFillStoreFromStructure(applicationStore, curriculums) {
+ curriculums.forEach(curriculum => {
+ if (curriculum.programmeSpecialization) {
+ // Specialization
+ const { programmeSpecialization, studyYears } = curriculum
+ const { programmeSpecializationCode: code, title } = programmeSpecialization
+
+ applicationStore.addSpecialization({
+ code,
+ title,
+ studyYears: studyYears.reduce((years, studyYear) => {
+ if (studyYear.courses.length) {
+ years.push(studyYear.yearNumber)
+ }
+
+ studyYear.courses.forEach(course => {
+ const {
+ kod: courseCode,
+ benamning: name,
+ omfattning: { number: credits, formattedWithUnit: formattedCredits },
+ utbildningstyp: {
+ level: { name: level },
+ },
+ Valvillkor: electiveCondition,
+ } = course
+
+ applicationStore.addElectiveConditionCourse(
+ {
+ code: courseCode,
+ name,
+ credits,
+ formattedCredits,
+ formattedLevel: level,
+ },
+ electiveCondition,
+ studyYear.yearNumber,
+ code
+ )
+ })
+ return years
+ }, []),
+ })
+ } else {
+ // Common
+ const { studyYears } = curriculum
+ studyYears.forEach(studyYear => {
+ if (studyYear.courses.length) {
+ applicationStore.addStudyYear(studyYear.yearNumber)
+ }
+
+ studyYear.courses.forEach(course => {
+ const {
+ kod: courseCode,
+ benamning: name,
+ omfattning: { number: credits, formattedWithUnit: formattedCredits },
+ utbildningstyp: {
+ level: { name: level },
+ },
+ Valvillkor: electiveCondition,
+ } = course
+
+ applicationStore.addElectiveConditionCourse(
+ {
+ code: courseCode,
+ name,
+ credits,
+ formattedCredits,
+ formattedLevel: level,
+ },
+ electiveCondition,
+ studyYear.yearNumber,
+ 'Common'
+ )
+ })
+ })
+ }
+ })
+}
+
/**
* Appendix 2
*
@@ -243,15 +343,24 @@ function _parseSpecializations(curriculums) {
* @param {string} options.programmeCode
* @param {string} options.term
*/
-async function fetchAndFillCurriculumList(options) {
+async function fetchAndFillCurriculumList(options, tillfalleUid) {
const { applicationStore, lang } = options
- const { studyProgrammeId } = await fetchAndFillStudyProgrammeVersion({ ...options, storeId: 'appendix1' })
- if (!studyProgrammeId) return
- const { curriculums, statusCode: secondStatusCode } = await koppsApi.listCurriculums(studyProgrammeId, lang)
- applicationStore.setStatusCode(secondStatusCode)
- if (secondStatusCode !== 200) return // react NotFound
- _parseCurriculumsAndFillStore(applicationStore, curriculums)
+ let curriculumData
+
+ if (tillfalleUid) {
+ curriculumData = await getProgramStructure(tillfalleUid, lang)
+ } else {
+ const { studyProgrammeId } = await fetchAndFillStudyProgrammeVersion({ ...options }) // we are not using the programmeStudy data here so I removed the option of saving it to store
+ if (!studyProgrammeId) return
+ const { curriculums, statusCode: secondStatusCode } = await koppsApi.listCurriculums(studyProgrammeId, lang)
+ curriculumData = curriculums
+ applicationStore.setStatusCode(secondStatusCode)
+ if (secondStatusCode !== 200) return // react NotFound
+ }
+
+ if (tillfalleUid) _parseCurriculumsAndFillStoreFromStructure(applicationStore, curriculumData)
+ else _parseCurriculumsAndFillStore(applicationStore, curriculumData)
return
}
@@ -263,15 +372,21 @@ async function fetchAndFillCurriculumList(options) {
* @param {string} options.programmeCode
* @param {string} options.term
*/
-async function fetchAndFillSpecializations(options) {
+async function fetchAndFillSpecializations(options, tillfalleUid) {
const { applicationStore, lang } = options
- const { studyProgrammeId } = await fetchAndFillStudyProgrammeVersion({ ...options, storeId: 'appendix2' })
- if (!studyProgrammeId) return
- const { curriculums, statusCode: secondStatusCode } = await koppsApi.listCurriculums(studyProgrammeId, lang)
- applicationStore.setStatusCode(secondStatusCode)
- if (secondStatusCode !== 200) return // react NotFound
+ let curriculumData
+ if (!tillfalleUid) {
+ const { studyProgrammeId } = await fetchAndFillStudyProgrammeVersion({ ...options, storeId: 'appendix2' })
+ if (!studyProgrammeId) return
+ const { curriculums, statusCode: secondStatusCode } = await koppsApi.listCurriculums(studyProgrammeId, lang)
+ curriculumData = curriculums
+ applicationStore.setStatusCode(secondStatusCode)
+ if (secondStatusCode !== 200) return // react NotFound
+ } else {
+ curriculumData = await getProgramStructure(tillfalleUid, lang)
+ }
- const specializations = _parseSpecializations(curriculums)
+ const specializations = _parseSpecializations(curriculumData)
applicationStore.setSpecializations(specializations)
return
}