Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export default class CalibrationForm extends Component {
{{t "common.actions.cancel"}}
</PixButtonLink>
<PixButtonLink
@route="authenticated.certification-frameworks.certification-framework.versions.version.meshes-configuration"
@route="authenticated.certification-frameworks.certification-framework.versions.version.scoring"
@variant="primary"
>
{{t "common.actions.next"}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import PixButton from '@1024pix/pix-ui/components/pix-button';
import PixButtonLink from '@1024pix/pix-ui/components/pix-button-link';
import PixInput from '@1024pix/pix-ui/components/pix-input';
import { fn } from '@ember/helper';
import { on } from '@ember/modifier';
import { action } from '@ember/object';
import { service } from '@ember/service';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { t } from 'ember-intl';
import Card from 'pix-admin/components/card';

export default class ScoringForm extends Component {
@service pixToast;
@service intl;
@tracked hasError = false;

get globalScoringConfiguration() {
return this.args.draftVersion.globalScoringConfiguration;
}

@action
saveCapacityByMesh(event) {
event.preventDefault();

try {
this.args.draftVersion.save();
this.pixToast.sendSuccessNotification({
message: this.intl.t(
'components.certification-frameworks.certification-framework.versions.scoring.success-notification',
),
});
} catch (error) {
this.pixToast.sendErrorNotification({ message: error.errors?.[0].detail });
}
}

@action
updateValue(name, index, event) {
const isMax = name === 'max';
const newArray = this.globalScoringConfiguration;
newArray.at(index).bounds[name] = Number(event.target.value);

if (isMax && this.globalScoringConfiguration.at(index + 1)) {
newArray.at(index + 1).bounds.min = Number(event.target.value);
}
this.globalScoringConfiguration = [...newArray];
this.fieldValidator();
}

fieldValidator() {
const errors = this.globalScoringConfiguration.map(({ bounds }) => {
if (bounds.max <= bounds.min) return 'error';
return 'default';
});
this.hasError = errors.some((error) => error === 'error');
}

@action
isNotFirstRow(index) {
return index !== 0;
}

@action
isFirstRow(index) {
return index === 0;
}

@action
lastMaxValue(index) {
return this.globalScoringConfiguration.at(index - 1)?.bounds.max;
}

@action
isGreaterThanMin(index) {
return this.globalScoringConfiguration.at(index).bounds.max > this.globalScoringConfiguration.at(index).bounds.min;
}

<template>
<Card
class="versions-scoring"
@title={{t "components.certification-frameworks.certification-framework.versions.scoring.title"}}
>
<form id="version-scoring-form" class="versions-scoring__form" {{on "submit" this.saveCapacityByMesh}}>
{{#each this.globalScoringConfiguration as |mesh|}}
<h3>{{t
"components.certification-frameworks.certification-framework.versions.scoring.level"
index=mesh.meshLevel
}}</h3>
<section>
<PixInput
type="number"
step="0.01"
readonly={{this.isNotFirstRow mesh.meshLevel}}
required={{this.isFirstRow mesh.meshLevel}}
@requiredLabel={{if (this.isFirstRow mesh.meshLevel) (t "common.forms.mandatory") false}}
@value={{if (this.isNotFirstRow mesh.meshLevel) (this.lastMaxValue mesh.meshLevel) mesh.bounds.min}}
{{on "change" (fn this.updateValue "min" mesh.meshLevel)}}
>
<:label>{{t
"components.certification-frameworks.certification-framework.versions.scoring.minimum-input-label"
}}</:label>
</PixInput>

<PixInput
type="number"
step="0.01"
required={{this.isFirstRow mesh.meshLevel}}
@requiredLabel={{if (this.isFirstRow mesh.meshLevel) (t "common.forms.mandatory") false}}
@errorMessage={{t
"components.certification-frameworks.certification-framework.versions.scoring.cannot-be-lower-error"
}}
@validationStatus={{if (this.isGreaterThanMin mesh.meshLevel) "default" "error"}}
@value={{mesh.bounds.max}}
{{on "change" (fn this.updateValue "max" mesh.meshLevel)}}
>
<:label>{{t
"components.certification-frameworks.certification-framework.versions.scoring.maximum-input-label"
}}</:label>
</PixInput>
</section>
{{/each}}

<PixButton @type="submit" form="version-scoring-form" @isDisabled={{this.hasError}} @variant="primary-bis">
{{t "components.certification-frameworks.certification-framework.versions.scoring.capacity-submit-button"}}
</PixButton>
</form>

</Card>
<section class="actions-container">
<PixButtonLink @route="authenticated.certification-frameworks.certification-framework" @variant="secondary">
{{t "common.actions.cancel"}}
</PixButtonLink>

</section>
</template>
}
1 change: 1 addition & 0 deletions admin/app/models/certification-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default class CertificationVersion extends Model {
@attr('string') scope;
@attr('string') comments;
@attr('number') externalCalibrationId;
@attr() globalScoringConfiguration;

@hasMany('area', { async: false, inverse: null }) areas;

Expand Down
2 changes: 1 addition & 1 deletion admin/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ Router.map(function () {
this.route('version', { path: '/:version_id' }, function () {
this.route('edit');
this.route('calibration');
this.route('meshes-configuration');
this.route('scoring');
});
});
this.route('target-profile', function () {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';

export default class FrameworkEditRoute extends Route {
@service store;
@service router;

async model() {
const { version_id: versionId } = this.paramsFor(
'authenticated.certification-frameworks.certification-framework.versions.version',
);
const draftVersion = await this.store.findRecord('certification-version', versionId);

return {
draftVersion,
};
}

afterModel(model) {
if (!model.draftVersion.isDraft) {
this.router.transitionTo('authenticated.certification-frameworks.certification-framework');
}
}

resetController(controller, isExiting) {
if (isExiting && controller.model.draftVersion.hasDirtyAttributes) {
controller.model.draftVersion.rollbackAttributes();
}
}
}
1 change: 1 addition & 0 deletions admin/app/styles/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
@use 'components/certification-frameworks/certification-frameworks/framework/certification-version-detail-modal' as *;
@use 'components/certification-frameworks/certification-frameworks/versions/certification-version-edit-form' as *;
@use 'components/certification-frameworks/certification-frameworks/versions/certification-version-calibration-form' as *;
@use 'components/certification-frameworks/certification-frameworks/versions/certification-version-scoring-form' as *;
@use 'components/certification-frameworks/link-to-current-target-profile' as *;
@use 'components/certification-frameworks/search-bar' as *;
@use 'components/certification-frameworks/selected-target-profile' as *;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.versions-scoring {
&__form {
display: flex;
flex-direction: column;

h3 {
font-weight: 700;
font-size: 1.2rem;
line-height: 20px;
}

section {
display: flex;
gap: 1.5rem;
align-items: start;
justify-content: space-between;
margin-bottom: 1.5rem;

.pix-label {
font-size: 0.9rem;
}
}

div {
flex: 1.5
}
}

&__fields-actions {
display: flex;
gap: 24px;

label {
display: flex;
gap: 0.3rem;
align-items: center;
cursor: pointer;
}
}
}

.actions-container {
display: flex;
gap: 1rem;
justify-content: flex-end;
margin-top: 1rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import CertificationVersionScoringForm from 'pix-admin/components/certification-frameworks/certification-framework/versions/certification-version-scoring-form';

<template><CertificationVersionScoringForm @draftVersion={{@model.draftVersion}} /></template>
9 changes: 9 additions & 0 deletions admin/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,15 @@
"title": "Creating a new version of the certification framework for {frameworkLabel}"
},
"page-title": "Creation of a new version of the certification in {scope} framework",
"scoring": {
"cannot-be-lower-error": "This field cannot be lower than the minimum",
"capacity-submit-button": "Save capacities by mesh",
"level": "Level {index}",
"maximum-input-label": "Max capacity",
"minimum-input-label": "Min capacity",
"success-notification": "The scoring has been successfully updated",
"title": "Min / max capacity by mesh"
},
"title": "Creating a Certification Release"
}
},
Expand Down
9 changes: 9 additions & 0 deletions admin/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,15 @@
"title": "Création d'une nouvelle version du référentiel de certification"
},
"page-title": "Création d’une nouvelle version du référentiel de certification pour {scope}",
"scoring": {
"cannot-be-lower-error": "Ce champ ne peut pas être inférieur au minimum",
"capacity-submit-button": "Sauvegarder les capacités par mailles",
"level": "Niveau {index}",
"maximum-input-label": "Capacité maximum",
"minimum-input-label": "Capacité minimum",
"success-notification": "Le scoring à bien été mis à jour",
"title": "Capacités minimum et maximum par mailles"
},
"title": "Création d'une version de certification"
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async function getInfo(request) {
return certificationInfoSerializer.serialize(certificationInfo);
}

const certificationVersionController = {
export const certificationVersionController = {
createDraft,
getVersionById,
deleteCertificationVersion,
Expand All @@ -81,8 +81,6 @@ const certificationVersionController = {
generateCalibrationReport,
};

export { certificationVersionController };

function deserialize(json) {
const deserializer = new Deserializer({ keyForAttribute: 'camelCase' });
return deserializer.deserialize(json);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ async function register(server) {
'default-candidate-capacity': Joi.number().required(),
'limit-to-one-question-per-tube': Joi.boolean().required(),
'enable-passage-by-all-competences': Joi.boolean().required(),
'global-scoring-configuration': Joi.array()
.items(
Joi.object({
bounds: Joi.object({
min: Joi.number().required(),
max: Joi.number().required(),
}),
meshLevel: Joi.number().required(),
}),
)
.empty(),
})
.required()
.unknown(true),
Expand Down
14 changes: 13 additions & 1 deletion api/src/certification/configuration/domain/models/Version.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,17 @@ export class Version {
expirationDate: Joi.date().allow(null).optional(),
assessmentDuration: Joi.number().required(),
minimumAnswersRequiredToValidateACertification: Joi.number().required(),
globalScoringConfiguration: Joi.array().allow(null).optional(),
globalScoringConfiguration: Joi.array()
.items(
Joi.object({
bounds: Joi.object({
min: Joi.number().required(),
max: Joi.number().required(),
}),
meshLevel: Joi.number().required(),
}).optional(),
)
.min(0),
competencesScoringConfiguration: Joi.array().allow(null).optional(),
challengesConfiguration: Joi.object().instance(FlashAssessmentAlgorithmConfiguration).required(),
comments: Joi.string().allow(null).optional(),
Expand Down Expand Up @@ -102,6 +112,7 @@ export class Version {
limitToOneQuestionPerTube,
enablePassageByAllCompetences,
externalCalibrationId,
globalScoringConfiguration,
}) {
if (!this.isDraft) {
throw new VersionNotDraftError();
Expand All @@ -119,6 +130,7 @@ export class Version {
enablePassageByAllCompetences,
});
this.externalCalibrationId = externalCalibrationId;
this.globalScoringConfiguration = globalScoringConfiguration;
this.validate();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export class VersionDetails {
limitToOneQuestionPerTube,
enablePassageByAllCompetences,
externalCalibrationId,
globalScoringConfiguration,
scope,
status,
comments,
Expand All @@ -31,6 +32,7 @@ export class VersionDetails {
this.limitToOneQuestionPerTube = limitToOneQuestionPerTube;
this.enablePassageByAllCompetences = enablePassageByAllCompetences;
this.externalCalibrationId = externalCalibrationId;
this.globalScoringConfiguration = globalScoringConfiguration;
this.scope = scope;
this.status = status;
this.comments = comments;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,4 @@ const usecasesWithoutInjectedDependencies = {
generateCalibrationReportCheck,
};

const usecases = injectDependencies(usecasesWithoutInjectedDependencies, dependencies, boundedContext);

/**
* @typedef {dependencies} dependencies
*/
export { usecases };
export const usecases = injectDependencies(usecasesWithoutInjectedDependencies, dependencies, boundedContext);
Loading
Loading