@universis/dining
Version:
Universis api for dining
292 lines (288 loc) • 9.86 kB
JavaScript
import { ApplicationService, DataError, TraceUtils } from '@themost/common';
import {
SchemaLoaderStrategy,
DataObject,
EdmMapping,
ModelClassLoaderStrategy,
} from '@themost/data';
import path from 'path';
import fs from 'fs';
export class Student extends DataObject {
.func(
'diningPreferenceEffectiveStatus',
'DiningPreferenceEffectiveStatus'
)
async getDiningPreferenceEffectiveStatus() {
try {
const context = this.context;
// first of all, get dining settings
// and determine if the feature should be available
const diningSettings = context
.getApplication()
.getConfiguration()
.getSourceAt('settings/universis/dining');
const diningPreferenceAvailable =
typeof (diningSettings && diningSettings.useDiningRequestPreference) ===
'boolean'
? diningSettings.useDiningRequestPreference
: true;
// if not, just return a NO_ACTION code
// so this process has no impact on any other service that uses it
// (e.g student period registration, study program register action etc)
if (!diningPreferenceAvailable) {
return await context
.model('DiningPreferenceEffectiveStatus')
.where('code')
.equal('NO_ACTION')
.getItem();
}
// check if student is active
const isActive = await this.isActive();
if (!isActive) {
// if not, the student does not have to take
// any actions regarding their dining preference declaration
return await context
.model('DiningPreferenceEffectiveStatus')
.where('code')
.equal('NO_ACTION')
.getItem();
}
// get student request configuration for DiningRequestAction
// note: an after-execute listener handles request rules validation
const studentRequestConfiguration = await context
.model('StudentRequestConfiguration')
.where('additionalType')
.equal('DiningRequestAction')
.select('id')
.silent()
.getItem();
// check if student does not meet the student request configuration rules
if (
studentRequestConfiguration &&
studentRequestConfiguration.validationResult &&
studentRequestConfiguration.validationResult.success === false
) {
// and return a NO_ACTION code
return await context
.model('DiningPreferenceEffectiveStatus')
.where('code')
.equal('NO_ACTION')
.getItem();
}
// get current year
const department = await this.property('department')
.select('currentYear')
.flatten()
.getItem();
if (!(department && department.currentYear)) {
throw new DataError(
'E_CURRENT_YEAR',
`The department's current year cannot be determined.`
);
}
// try to find if an open dining request event exists for the current year
const diningRequestEvent = await context
.model('DiningRequestEvent')
.where('academicYear')
.equal(department.currentYear)
.and('eventStatus/alternateName')
.equal('EventOpened')
.select('id')
.getItem();
// if it does not exist
if (diningRequestEvent == null) {
// student does not have to take any actions regarding their dining preference declaration
return await context
.model('DiningPreferenceEffectiveStatus')
.where('code')
.equal('NO_ACTION')
.getItem();
}
// the actual request has priority, so check it
const diningRequestAction = await context
.model('DiningRequestAction')
.where('student')
.equal(this.getId())
.and('diningRequestEvent')
.equal(diningRequestEvent.id)
.select('id')
.getItem();
if (diningRequestAction) {
// if a request exists, no extra action has to happen
return await context
.model('DiningPreferenceEffectiveStatus')
.where('code')
.equal('NO_ACTION')
.getItem();
}
// given that an event exists, check the student's preference on that event
const diningRequestPreference = await context
.model('DiningRequestPreference')
.where('student')
.equal(this.getId())
.and('diningRequestEvent')
.equal(diningRequestEvent.id)
.select('id', 'preference')
.getItem();
// if preference has not been declared
if (diningRequestPreference == null) {
// the student has to declare the preference
return Object.assign(
await context
.model('DiningPreferenceEffectiveStatus')
.where('code')
.equal('PREFERENCE_REQUIRED')
.getItem(),
{
diningRequestEvent,
}
);
} else {
// if the student has declared a preference
if (diningRequestPreference.preference === false) {
// and it is falsy, no action needs to happen
return await context
.model('DiningPreferenceEffectiveStatus')
.where('code')
.equal('NO_ACTION')
.getItem();
}
// check if request should be 'forced'
const forceRequest =
typeof (
diningSettings && diningSettings.forceRequestOnPositivePreference
) === 'boolean'
? diningSettings.forceRequestOnPositivePreference
: false;
// if the request should be forced
if (forceRequest) {
// return a REQUEST_REQUIRED status code
return Object.assign(
await context
.model('DiningPreferenceEffectiveStatus')
.where('code')
.equal('REQUEST_REQUIRED')
.getItem(),
{
diningRequestPreference,
}
);
}
// else, just an REQUEST_OPTIONAL status code
return Object.assign(
await context
.model('DiningPreferenceEffectiveStatus')
.where('code')
.equal('REQUEST_OPTIONAL')
.getItem(),
{
diningRequestPreference,
}
);
}
} catch (err) {
// log error
TraceUtils.error(err);
// and return no action status
return await this.context
.model('DiningPreferenceEffectiveStatus')
.where('code')
.equal('NO_ACTION')
.getItem();
}
}
}
export class StudentReplacer extends ApplicationService {
constructor(app) {
super(app);
}
apply() {
try {
// get schema loader
const schemaLoader = this.getApplication()
.getConfiguration()
.getStrategy(SchemaLoaderStrategy);
// get model definition
const model = schemaLoader.getModelDefinition('Student');
const findAttribute = model.fields.find((field) => {
return field.name === 'diningCard';
});
if (findAttribute == null) {
model.fields.push({
name: 'diningCard',
type: 'DiningCard',
many: true,
multiplicity: 'ZeroOrOne',
mapping: {
associationType: 'junction',
associationAdapter: 'StudentDiningCard',
associatedObjectField: 'student',
associatedValueField: 'diningCard',
cascade: 'delete',
parentModel: 'Student',
parentField: 'id',
childModel: 'DiningCard',
childField: 'id',
},
});
model.eventListeners = model.eventListeners || [];
model.eventListeners.push({
type: path.resolve(
__dirname,
'listeners/UpdateStudenDiningCardListener'
),
});
schemaLoader.setModelDefinition(model);
}
// get model class loader
const loader = this.getApplication()
.getConfiguration()
.getStrategy(ModelClassLoaderStrategy);
// and student class
const StudentClass = loader.resolve(model);
// extend existing functions
StudentClass.prototype.getDiningPreferenceEffectiveStatus =
Student.prototype.getDiningPreferenceEffectiveStatus;
// apply also data-localization listener to DiningPreferenceEffectiveStatus model
const diningPreferenceEffectiveStatus = schemaLoader.getModelDefinition(
'DiningPreferenceEffectiveStatus'
);
if (diningPreferenceEffectiveStatus == null) {
return;
}
// ensure listeners array
diningPreferenceEffectiveStatus.eventListeners =
diningPreferenceEffectiveStatus.eventListeners || [];
// get data-localization listener path
const dataLocalizationListener = path.resolve(
this.getApplication().getConfiguration().getExecutionPath(),
'listeners/data-localization'
);
// ensure correct file path
const stat = fs.statSync(`${dataLocalizationListener}.js`);
if (!stat.isFile()) {
return;
}
const findListener = diningPreferenceEffectiveStatus.eventListeners.find(
(listener) => {
return listener.type === dataLocalizationListener;
}
);
if (findListener) {
return;
}
// add listener, if it does not exist
diningPreferenceEffectiveStatus.eventListeners.push({
type: dataLocalizationListener,
});
// and set model definition
schemaLoader.setModelDefinition(diningPreferenceEffectiveStatus);
} catch (err) {
TraceUtils.error(
'An error occured while the DiningService tried to extend the Student model (StudentReplacer).'
);
TraceUtils.error(err);
throw err;
}
}
}