Skip to content

Commit

Permalink
[FEATURE] Utiliser la date de réconciliation du candidat dans le serv…
Browse files Browse the repository at this point in the history
…ice Placement Profile (PIX-14402)

 #10201
  • Loading branch information
pix-service-auto-merge authored Oct 1, 2024
2 parents 69aa102 + 11c6b55 commit 53a7611
Show file tree
Hide file tree
Showing 19 changed files with 330 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const buildCertificationCandidate = function ({
} = {}) {
sessionId = _.isUndefined(sessionId) ? buildSession().id : sessionId;
userId = _.isUndefined(userId) ? buildUser().id : userId;
reconciledAt = userId ? new Date('2020-01-02') : undefined;
reconciledAt = userId && !reconciledAt ? new Date('2020-01-02') : reconciledAt;

const values = {
id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,6 @@ async function _startNewCertification({
}) {
const challengesForCertification = [];

const placementProfile = await placementProfileService.getPlacementProfile({
userId,
limitDate: new Date(),
version,
});

const certificationCenter = await certificationCenterRepository.getBySessionId({ sessionId });

const complementaryCertificationCourseData = [];
Expand Down Expand Up @@ -228,6 +222,12 @@ async function _startNewCertification({

let challengesForPixCertification = [];
if (!CertificationVersion.isV3(version)) {
const placementProfile = await placementProfileService.getPlacementProfile({
userId,
limitDate: certificationCandidate.reconciledAt,
version,
});

challengesForPixCertification = await certificationChallengesService.pickCertificationChallenges(
placementProfile,
locale,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class CertificationCandidate {
/**
* @param {Object} param
* @param {number} param.userId
* @param {Date} param.reconciledAt
*/
constructor({ userId, reconciledAt } = {}) {
this.userId = userId;
this.reconciledAt = reconciledAt;
}
}

export { CertificationCandidate };
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
/**
* @typedef {import('./index.js').CompetenceMarkRepository} CompetenceMarkRepository
* @typedef {import('./index.js').CertificationAssessmentRepository} CertificationAssessmentRepository
* @typedef {import('./index.js').CertificationCandidateRepository} CertificationCandidateRepository
* @typedef {import('./index.js').PlacementProfileService} PlacementProfileService
* @typedef {import('./index.js').ScoringCertificationService} ScoringCertificationService
*/
import { CERTIFICATION_VERSIONS } from '../../../shared/domain/models/CertificationVersion.js';
import { CertificationDetails } from '../read-models/CertificationDetails.js';

/**
* @param {Object} params
* @param {number} params.certificationCourseId
* @param {CompetenceMarkRepository} params.competenceMarkRepository
* @param {CertificationAssessmentRepository} params.certificationAssessmentRepository
* @param {CertificationCandidateRepository} params.certificationCandidateRepository
* @param {PlacementProfileService} params.placementProfileService
* @param {ScoringCertificationService} params.scoringCertificationService
*/
const getCertificationDetails = async function ({
certificationCourseId,
competenceMarkRepository,
certificationAssessmentRepository,
certificationCandidateRepository,
placementProfileService,
scoringCertificationService,
}) {
Expand All @@ -13,55 +30,55 @@ const getCertificationDetails = async function ({
});

const competenceMarks = await competenceMarkRepository.findByCertificationCourseId({ certificationCourseId });
const candidate = await certificationCandidateRepository.getByCertificationCourseId({
certificationCourseId: certificationAssessment.certificationCourseId,
});

const placementProfile = await placementProfileService.getPlacementProfile({
userId: candidate.userId,
limitDate: candidate.reconciledAt,
version: CERTIFICATION_VERSIONS.V2,
allowExcessPixAndLevels: false,
});

if (competenceMarks.length) {
return _retrievePersistedCertificationDetails(competenceMarks, certificationAssessment, placementProfileService);
return _retrievePersistedCertificationDetails({
competenceMarks,
certificationAssessment,
placementProfile,
});
} else {
return _computeCertificationDetailsOnTheFly(
return _computeCertificationDetailsOnTheFly({
certificationAssessment,
placementProfileService,
placementProfile,
scoringCertificationService,
);
});
}
};

export { getCertificationDetails };

async function _computeCertificationDetailsOnTheFly(
async function _computeCertificationDetailsOnTheFly({
certificationAssessment,
placementProfileService,
placementProfile,
scoringCertificationService,
) {
}) {
const certificationAssessmentScore = await scoringCertificationService.calculateCertificationAssessmentScore({
certificationAssessment,
continueOnError: true,
});
const placementProfile = await placementProfileService.getPlacementProfile({
userId: certificationAssessment.userId,
limitDate: certificationAssessment.createdAt,
version: CERTIFICATION_VERSIONS.V2,
allowExcessPixAndLevels: false,
});

return CertificationDetails.fromCertificationAssessmentScore({
certificationAssessmentScore,
certificationAssessment,
placementProfile,
});
}

async function _retrievePersistedCertificationDetails(
competenceMarks,
certificationAssessment,
placementProfileService,
) {
const placementProfile = await placementProfileService.getPlacementProfile({
userId: certificationAssessment.userId,
limitDate: certificationAssessment.createdAt,
version: CERTIFICATION_VERSIONS.V2,
allowExcessPixAndLevels: false,
});

/**
* @param {PlacementProfileService} placementProfileService
* @param {CertificationCandidateRepository} certificationCandidateRepository
*/
async function _retrievePersistedCertificationDetails({ competenceMarks, certificationAssessment, placementProfile }) {
return CertificationDetails.from({
competenceMarks,
certificationAssessment,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ import { cpfReceiptsStorage } from '../../infrastructure/storage/cpf-receipts-st
* @typedef {import('../../infrastructure/repositories/index.js').CertificationAssessmentRepository} CertificationAssessmentRepository
* @typedef {import('../../infrastructure/repositories/index.js').CertificationCpfCityRepository} CertificationCpfCityRepository
* @typedef {import('../../infrastructure/repositories/index.js').CertificationCpfCountryRepository} CertificationCpfCountryRepository
* @typedef {import('../../infrastructure/repositories/index.js').CertificationCandidateRepository} CertificationCandidateRepository
* @typedef {import('../../infrastructure/storage/cpf-receipts-storage.js').cpfReceiptsStorage} CpfReceiptsStorage
* @typedef {import('../../infrastructure/storage/cpf-exports-storage.js').cpfExportsStorage} CpfExportsStorage
* @typedef {import('../../../shared/domain/services/certification-badges-service.js')} CertificationBadgesService
* @typedef {import('../../../shared/domain/services/scoring-certification-service.js')} ScoringCertificationService
* @typedef {import('../../../../shared/domain/services/placement-profile-service.js')} PlacementProfileService
* @typedef {import('../../../shared/domain/services/certification-cpf-service.js')} CertificationCpfService
* @typedef {import('../../infrastructure/repositories/index.js').CertificationCandidateRepository} CertificationCandidateRepository
**/

/**
Expand Down Expand Up @@ -96,6 +98,7 @@ import { cpfReceiptsStorage } from '../../infrastructure/storage/cpf-receipts-st
* @typedef {flashAlgorithmService} FlashAlgorithmService
* @typedef {flashAlgorithmConfigurationRepository} FlashAlgorithmConfigurationRepository
* @typedef {cpfExportRepository} CpfExportRepository
* @typedef {certificationCandidateRepository} CertificationCandidateRepository
**/
const dependencies = {
...sessionRepositories,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { DomainTransaction } from '../../../../shared/domain/DomainTransaction.js';
import { NotFoundError } from '../../../../shared/domain/errors.js';
import { CertificationCandidate } from '../../domain/models/CertificationCandidate.js';

export const getByCertificationCourseId = async ({ certificationCourseId }) => {
const knexConn = DomainTransaction.getConnection();
const certificationCandidate = await knexConn('certification-courses')
.select('certification-candidates.userId', 'certification-candidates.reconciledAt')
.innerJoin('sessions', 'sessions.id', 'certification-courses.sessionId')
.innerJoin('certification-candidates', 'sessions.id', 'certification-candidates.sessionId')
.where('certification-courses.id', '=', certificationCourseId)
.first();

if (!certificationCandidate) {
throw new NotFoundError();
}

return _toDomain(certificationCandidate);
};

const _toDomain = (candidateData) => {
return new CertificationCandidate({ userId: candidateData.userId, reconciledAt: candidateData.reconciledAt });
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as sharedCompetenceMarkRepository from '../../../shared/infrastructure/
import * as complementaryCertificationCourseResultRepository from '../../../shared/infrastructure/repositories/complementary-certification-course-result-repository.js';
import * as flashAlgorithmConfigurationRepository from '../../../shared/infrastructure/repositories/flash-algorithm-configuration-repository.js';
import * as certificationCandidateForSupervisingRepository from './certification-candidate-for-supervising-repository.js';
import * as certificationCandidateRepository from './certification-candidate-repository.js';
import * as certificationOfficerRepository from './certification-officer-repository.js';
import * as competenceMarkRepository from './competence-mark-repository.js';
import * as courseAssessmentResultRepository from './course-assessment-result-repository.js';
Expand Down Expand Up @@ -67,6 +68,7 @@ import * as v3CertificationCourseDetailsForAdministrationRepository from './v3-c
* @typedef {flashAlgorithmConfigurationRepository} FlashAlgorithmConfigurationRepository
* @typedef {cpfExportRepository} CpfExportRepository
* @typedef {juryCertificationSummaryRepository} JuryCertificationSummaryRepository
* @typedef {certificationCandidateRepository} CertificationCandidateRepository
*/
const repositoriesWithoutInjectedDependencies = {
assessmentRepository,
Expand Down Expand Up @@ -97,6 +99,7 @@ const repositoriesWithoutInjectedDependencies = {
complementaryCertificationCourseResultRepository,
certificationCpfCityRepository,
certificationCpfCountryRepository,
certificationCandidateRepository,
};

/**
Expand Down
2 changes: 2 additions & 0 deletions api/src/shared/domain/models/CertificationCandidate.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class CertificationCandidate {
subscriptions = [],
hasSeenCertificationInstructions = false,
accessibilityAdjustmentNeeded = false,
reconciledAt,
} = {}) {
this.id = id;
this.firstName = firstName;
Expand All @@ -63,6 +64,7 @@ class CertificationCandidate {
this.prepaymentCode = prepaymentCode;
this.hasSeenCertificationInstructions = hasSeenCertificationInstructions;
this.accessibilityAdjustmentNeeded = accessibilityAdjustmentNeeded;
this.reconciledAt = reconciledAt;

Object.defineProperty(this, 'complementaryCertification', {
enumerable: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
CertificationVersion,
} from '../../../../src/certification/shared/domain/models/CertificationVersion.js';
import { config } from '../../../../src/shared/config.js';
import { KnowledgeElement } from '../../../../src/shared/domain/models/KnowledgeElement.js';
import {
createServer,
databaseBuilder,
Expand Down Expand Up @@ -333,6 +334,7 @@ describe('Acceptance | API | Certification Course', function () {
// given
const { options, userId, sessionId } = _createRequestOptions();
_createNonExistingCertifCourseSetup({ learningContent, userId, sessionId });

await databaseBuilder.commit();

// when
Expand All @@ -342,6 +344,9 @@ describe('Acceptance | API | Certification Course', function () {
const certificationCourses = await knex('certification-courses').where({ userId, sessionId });
expect(certificationCourses).to.have.length(1);
expect(certificationCourses[0].version).to.equal(CERTIFICATION_VERSIONS.V2);
expect(response.result.data.attributes).to.include({
'nb-challenges': 2,
});
});

context('when the session is v3', function () {
Expand Down Expand Up @@ -512,12 +517,23 @@ function _createNonExistingCertifCourseSetup({ learningContent, sessionId, userI
sessionId,
userId,
authorizedToStart: true,
reconciledAt: new Date('2019-02-01'),
});
databaseBuilder.factory.buildCoreSubscription({ certificationCandidateId: certificationCandidate.id });
databaseBuilder.factory.buildCorrectAnswersAndKnowledgeElementsForLearningContent.fromAreas({
learningContent,
userId,
earnedPix: 4,
placementDate: new Date('2019-01-01'),
});

// KnowledgeElement.StatusType.RESET after the reconciledAt date
databaseBuilder.factory.buildKnowledgeElement({
status: KnowledgeElement.StatusType.RESET,
skillId: 'recSkill5_1',
competenceId: 'recCompetence5',
userId: userId,
createdAt: new Date('2023-03-03'),
});

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,21 @@ describe('Integration | Repository | CertificationCandidate', function () {
describe('#getBySessionIdAndUserId', function () {
let userId;
let complementaryCertificationId;
let certificationCandidateId;
let createdAt, reconciledAt;

beforeEach(function () {
// given
createdAt = new Date('2000-01-01');
reconciledAt = new Date('2020-01-02');
userId = databaseBuilder.factory.buildUser().id;
complementaryCertificationId = databaseBuilder.factory.buildComplementaryCertification().id;
const certificationCandidateId = databaseBuilder.factory.buildCertificationCandidate({ sessionId, userId }).id;
certificationCandidateId = databaseBuilder.factory.buildCertificationCandidate({
sessionId,
userId,
createdAt,
reconciledAt,
}).id;
databaseBuilder.factory.buildCoreSubscription({ certificationCandidateId });
databaseBuilder.factory.buildComplementaryCertificationSubscription({
complementaryCertificationId,
Expand All @@ -156,13 +165,52 @@ describe('Integration | Repository | CertificationCandidate', function () {
context('when there is one certification candidate with the given session id and user id', function () {
it('should fetch the candidate', async function () {
// when
const actualCandidates = await certificationCandidateRepository.getBySessionIdAndUserId({ sessionId, userId });
const actualCandidate = await certificationCandidateRepository.getBySessionIdAndUserId({ sessionId, userId });

// then
expect(actualCandidates.sessionId).to.equal(sessionId);
expect(actualCandidates.userId).to.equal(userId);
expect(actualCandidates.complementaryCertification).not.to.be.null;
expect(actualCandidates.complementaryCertification.id).to.equal(complementaryCertificationId);
expect(actualCandidate).to.deep.equal({
accessibilityAdjustmentNeeded: false,
authorizedToStart: false,
billingMode: null,
birthCity: 'PARIS 1',
birthCountry: 'France',
birthINSEECode: '75101',
birthPostalCode: null,
birthProvinceCode: null,
birthdate: '2000-01-04',
complementaryCertification: {
id: complementaryCertificationId,
key: 'DROIT',
label: 'UneSuperCertifComplémentaire',
},
createdAt,
email: '[email protected]',
externalId: 'externalId',
extraTimePercentage: 0.3,
firstName: 'first-name',
hasSeenCertificationInstructions: false,
id: certificationCandidateId,
lastName: 'last-name',
organizationLearnerId: null,
prepaymentCode: null,
reconciledAt,
resultRecipientEmail: '[email protected]',
sessionId,
sex: 'M',
subscriptions: [
{
certificationCandidateId: undefined,
complementaryCertificationId: null,
type: 'CORE',
},
{
certificationCandidateId,
complementaryCertificationId,
type: 'COMPLEMENTARY',
},
],
userId: userId,
});
});
});

Expand Down
Loading

0 comments on commit 53a7611

Please sign in to comment.