Skip to content

[FEATURE] Ajoute la verification des status dans le critères campaignParticipations sur les quêtes (Pix-16375). #11313

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
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
47 changes: 38 additions & 9 deletions api/db/seeds/data/team-prescription/build-quests.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ATTESTATIONS } from '../../../../src/profile/domain/constants.js';
import { REWARD_TYPES } from '../../../../src/quest/domain/constants.js';
import { TYPES } from '../../../../src/quest/domain/models/Eligibility.js';
import { COMPARISON } from '../../../../src/quest/domain/models/Quest.js';
import { TYPES as SUCCESS_TYPES } from '../../../../src/quest/domain/models/Success.js';
import { Assessment, CampaignParticipationStatuses, Membership } from '../../../../src/shared/domain/models/index.js';
import { temporaryStorage } from '../../../../src/shared/infrastructure/key-value-storages/index.js';
import {
Expand All @@ -10,10 +12,35 @@ import {
USER_ID_ADMIN_ORGANIZATION,
USER_ID_MEMBER_ORGANIZATION,
} from '../common/constants.js';
import { TARGET_PROFILE_BADGES_STAGES_ID } from './constants.js';
import { TARGET_PROFILE_BADGES_STAGES_ID, TARGET_PROFILE_NO_BADGES_NO_STAGES_ID } from './constants.js';

const profileRewardTemporaryStorage = temporaryStorage.withPrefix('profile-rewards:');

function buildParenthoodQuest(databaseBuilder) {
const { id: rewardId } = databaseBuilder.factory.buildAttestation({
templateName: 'parenthood-attestation-template',
key: ATTESTATIONS.PARENTHOOD,
});

const targetProfileId = TARGET_PROFILE_NO_BADGES_NO_STAGES_ID;
databaseBuilder.factory.buildQuest({
rewardType: REWARD_TYPES.ATTESTATION,
rewardId,
eligibilityRequirements: [
{
type: TYPES.CAMPAIGN_PARTICIPATIONS,
data: {
targetProfileId: [targetProfileId],
status: {
value: [CampaignParticipationStatuses.SHARED, CampaignParticipationStatuses.TO_SHARE],
comparison: COMPARISON.ONE_OF,
},
},
},
],
});
}

const USERS = [
{
firstName: 'attestation-success',
Expand Down Expand Up @@ -157,22 +184,22 @@ const buildSixthGradeQuests = (
) => {
const firstQuestRequirement = [
{
type: 'organization',
type: TYPES.ORGANIZATION,
data: {
type: 'SCO',
},
comparison: COMPARISON.ALL,
},
{
type: 'organization',
type: TYPES.ORGANIZATION,
data: {
isManagingStudents: true,
tags: [AEFE_TAG.name],
},
comparison: COMPARISON.ONE_OF,
},
{
type: 'campaignParticipations',
type: TYPES.CAMPAIGN_PARTICIPATIONS,
data: {
targetProfileIds: [firstTargetProfile.id],
},
Expand All @@ -181,7 +208,7 @@ const buildSixthGradeQuests = (
];
const firstQuestSuccessRequirements = [
{
type: 'skill',
type: SUCCESS_TYPES.SKILL,
data: {
ids: CAMPAIGN_SKILLS[0],
threshold: 50,
Expand All @@ -198,22 +225,22 @@ const buildSixthGradeQuests = (

const secondQuestEligibilityRequirements = [
{
type: 'organization',
type: TYPES.ORGANIZATION,
data: {
type: 'SCO',
},
comparison: COMPARISON.ALL,
},
{
type: 'organization',
type: TYPES.ORGANIZATION,
data: {
isManagingStudents: true,
tags: [AEFE_TAG.name],
},
comparison: COMPARISON.ONE_OF,
},
{
type: 'campaignParticipations',
type: TYPES.CAMPAIGN_PARTICIPATIONS,
data: {
targetProfileIds: [secondTargetProfile.id, thirdTargetProfile.id],
},
Expand All @@ -223,7 +250,7 @@ const buildSixthGradeQuests = (

const secondQuestSuccessRequirements = [
{
type: 'skill',
type: SUCCESS_TYPES.SKILL,
data: {
ids: [CAMPAIGN_SKILLS[1], CAMPAIGN_SKILLS[2]].flat(),
threshold: 50,
Expand Down Expand Up @@ -440,4 +467,6 @@ export const buildQuests = async (databaseBuilder) => {
organizationId: SCO_ORGANIZATION_ID,
profileRewardId: otherUserProfileRewardId,
});

buildParenthoodQuest(databaseBuilder);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export class KnowledgeElementDTO {
constructor({ status }) {
constructor({ status, skillId }) {
this.status = status;
this.skillId = skillId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export class OrganizationLearnerWithParticipations {
tags: tagNames,
type: organization.type,
};
this.campaignParticipations = campaignParticipations.map(({ id, targetProfileId }) => ({ id, targetProfileId }));
this.campaignParticipations = campaignParticipations.map(({ id, targetProfileId, status }) => ({
id,
targetProfileId,
status,
}));
}
}
1 change: 1 addition & 0 deletions api/src/profile/domain/constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const ATTESTATIONS = {
SIXTH_GRADE: 'SIXTH_GRADE',
PARENTHOOD: 'PARENTHOOD',
};
Binary file not shown.
29 changes: 11 additions & 18 deletions api/src/quest/domain/models/Eligibility.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,35 @@
export class Eligibility {
#campaignParticipations;
export const TYPES = {
ORGANIZATION_LEARNER: 'organizationLearner',
ORGANIZATION: 'organization',
CAMPAIGN_PARTICIPATIONS: 'campaignParticipations',
};

export class Eligibility {
constructor({ organizationLearner, organization, campaignParticipations = [] }) {
this.organizationLearner = {
id: organizationLearner.id,
MEFCode: organizationLearner?.MEFCode,
};
this.organization = organization;
this.#campaignParticipations = campaignParticipations;
}

get campaignParticipations() {
return {
targetProfileIds: this.#campaignParticipations.map(({ targetProfileId }) => targetProfileId),
};
}

set campaignParticipations(campaignParticipations) {
this.#campaignParticipations = campaignParticipations;
this.campaignParticipations = campaignParticipations;
}

hasCampaignParticipation(campaignParticipationId) {
return Boolean(
this.#campaignParticipations.find(
(campaignParticipation) => campaignParticipation.id === campaignParticipationId,
),
this.campaignParticipations.find((campaignParticipation) => campaignParticipation.id === campaignParticipationId),
);
}

hasCampaignParticipationForTargetProfileId(targetProfileId) {
return Boolean(
this.#campaignParticipations.find(
this.campaignParticipations.find(
(campaignParticipation) => campaignParticipation.targetProfileId === targetProfileId,
),
);
}

getTargetProfileForCampaignParticipation(campaignParticipationId) {
const campaignParticipation = this.#campaignParticipations.find(
const campaignParticipation = this.campaignParticipations.find(
(campaignParticipation) => campaignParticipation.id === campaignParticipationId,
);

Expand Down
89 changes: 77 additions & 12 deletions api/src/quest/domain/models/Quest.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { KnowledgeElement } from '../../../shared/domain/models/index.js';
import { TYPES as ELIGIBILITY_TYPES } from './Eligibility.js';
import { TYPES as SUCCESS_TYPES } from './Success.js';

export const COMPARISON = {
ALL: 'all',
Expand All @@ -24,29 +26,92 @@ class Quest {
);
}

#checkRequirement(eligibilityRequirement, eligibility) {
const comparaisonFunction = eligibilityRequirement.comparison === COMPARISON.ONE_OF ? 'some' : 'every';
isGrantedWithParticipationId({ eligibility, campaignParticipationId }) {
console.log('eligibility', JSON.stringify(eligibility, undefined, 2));
const criteria = this.eligibilityRequirements.filter(
(eligibilityRequirement) => eligibilityRequirement.type === ELIGIBILITY_TYPES.CAMPAIGN_PARTICIPATIONS,
);
const campaignParticipation = eligibility.campaignParticipations.find(
(campaignParticipation) => campaignParticipation.id === campaignParticipationId,
);
console.log('campaignParticipation', JSON.stringify(campaignParticipation, undefined, 2));

return Object.keys(eligibilityRequirement.data)[comparaisonFunction]((key) => {
const eligibilityData = eligibility[eligibilityRequirement.type][key];
const criterion = eligibilityRequirement.data[key];
for (const criterion of criteria) {
console.log('criterion', JSON.stringify(criterion, undefined, 2));
const alterKey = criterion.data.targetProfileIds !== undefined ? 'targetProfileIds' : 'targetProfileId';
console.log({ alterKey });
const isQuestRelatedToCampaignParticipationId = criterion.data[alterKey].includes(
campaignParticipation.targetProfileId,
);
console.log(isQuestRelatedToCampaignParticipationId);

if (Array.isArray(criterion)) {
if (isQuestRelatedToCampaignParticipationId) return true;
}

return false;
}

#checkCriterion({ criterion, eligibilityData }) {
if (Array.isArray(criterion)) {
if (Array.isArray(eligibilityData)) {
return criterion.every((valueToTest) => eligibilityData.includes(valueToTest));
}
return eligibilityData === criterion;
return criterion.some((valueToTest) => valueToTest === eligibilityData);
}
if (typeof criterion === 'object') {
const comparisonFunction = criterion.comparison === COMPARISON.ONE_OF ? 'some' : 'every';
return criterion.value[comparisonFunction]((valueToTest) => eligibilityData.includes(valueToTest));
}
return eligibilityData === criterion;
}

#checkRequirement(eligibilityRequirement, eligibility) {
const comparisonFunction = eligibilityRequirement.comparison === COMPARISON.ONE_OF ? 'some' : 'every';

if (Array.isArray(eligibility[eligibilityRequirement.type])) {
return eligibility[eligibilityRequirement.type].some((item) => {
return Object.keys(eligibilityRequirement.data)[comparisonFunction]((key) => {
// TODO: Dés que les quêtes ont été mises à jour il faudra retirer cette ligne
const alterKey = key === 'targetProfileIds' ? 'targetProfileId' : key;
return this.#checkCriterion({
criterion: eligibilityRequirement.data[alterKey],
eligibilityData: item[key],
});
});
});
}

return Object.keys(eligibilityRequirement.data)[comparisonFunction]((key) => {
return this.#checkCriterion({
criterion: eligibilityRequirement.data[key],
eligibilityData: eligibility[eligibilityRequirement.type][key],
});
});
}

/**
* @param {Success} success
*/
isSuccessful(success) {
const skillsCount = this.successRequirements[0].data.ids.length;
const threshold = this.successRequirements[0].data.threshold / 100;
const skillsValidatedCount = success.knowledgeElements.filter(
(knowledgeElement) => knowledgeElement.status === KnowledgeElement.StatusType.VALIDATED,
).length;
if (this.successRequirements === undefined || this.successRequirements.length === 0) return true;

return this.successRequirements.every((successRequirement) => {
if (successRequirement.type === SUCCESS_TYPES.SKILL) {
return this.#validateSuccessRequirementsOfTypeSkill({ successRequirement, success });
}
});
}

#validateSuccessRequirementsOfTypeSkill({ successRequirement, success }) {
const knowledgeElementValidatedForSuccess = success.knowledgeElements.filter(
(knowledgeElement) =>
successRequirement.data.ids.includes(knowledgeElement.skillId) &&
knowledgeElement.status === KnowledgeElement.StatusType.VALIDATED,
);
const skillsCount = successRequirement.data.ids.length;
const threshold = successRequirement.data.threshold / 100;

const skillsValidatedCount = knowledgeElementValidatedForSuccess.length;

return skillsValidatedCount / skillsCount >= threshold;
}
Expand Down
4 changes: 4 additions & 0 deletions api/src/quest/domain/models/Success.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export const TYPES = {
SKILL: 'skill',
};

export class Success {
constructor({ knowledgeElements }) {
this.knowledgeElements = knowledgeElements;
Expand Down
Loading