diff --git a/api/src/organizational-entities/domain/models/OrganizationFeature.js b/api/src/organizational-entities/domain/models/OrganizationFeature.js index bc314e7dac1..108f39eca4b 100644 --- a/api/src/organizational-entities/domain/models/OrganizationFeature.js +++ b/api/src/organizational-entities/domain/models/OrganizationFeature.js @@ -1,8 +1,15 @@ class OrganizationFeature { - constructor({ featureId, organizationId, params }) { + #deleteLearner; + constructor({ featureId, organizationId, params, deleteLearner }) { this.featureId = parseInt(featureId, 10); this.organizationId = parseInt(organizationId, 10); this.params = params ? JSON.parse(params) : null; + + this.#deleteLearner = deleteLearner === 'Y'; + } + + get deleteLearner() { + return this.#deleteLearner; } } diff --git a/api/src/organizational-entities/domain/usecases/add-organization-feature-in-batch.js b/api/src/organizational-entities/domain/usecases/add-organization-feature-in-batch.js index 38952dd1dca..dcb2c30424b 100644 --- a/api/src/organizational-entities/domain/usecases/add-organization-feature-in-batch.js +++ b/api/src/organizational-entities/domain/usecases/add-organization-feature-in-batch.js @@ -28,31 +28,43 @@ const organizationFeatureCsvHeader = { name: 'Params', isRequired: false, }), + new CsvColumn({ + property: 'deleteLearner', + name: 'Delete Learner', + isRequired: false, + }), ], }; export const addOrganizationFeatureInBatch = withTransaction( /** * @param {Object} params - A parameter object. - * @param {Number} params.userId - user connected performing action + * @param {Number} params.userId - user connected performing the action * @param {string} params.filePath - path of csv file wich contains organizations and params. * @param {OrganizationFeatureRepository} params.organizationFeatureRepository - organizationRepository to use. * @param {Object} params.dependencies * @returns {Promise} */ - async ({ filePath, organizationFeatureRepository }) => { + async ({ userId, filePath, organizationFeatureRepository, learnersApi }) => { const stream = createReadStream(filePath); const buffer = await getDataBuffer(stream); const csvParser = new CsvParser(buffer, organizationFeatureCsvHeader); const csvData = csvParser.parse(); - const data = csvData.map(({ featureId, organizationId, params }) => { + const data = csvData.map(({ featureId, organizationId, params, deleteLearner }) => { try { - return new OrganizationFeature({ featureId, organizationId, params: params }); + return new OrganizationFeature({ featureId, organizationId, params, deleteLearner }); } catch (err) { throw new FeatureParamsNotProcessable(); } }); + + data.forEach(async ({ organizationId, deleteLearner }) => { + if (deleteLearner) { + await learnersApi.deleteOrganizationLearnerBeforeImportFeature({ userId, organizationId }); + } + }); + return organizationFeatureRepository.saveInBatch(data); }, ); diff --git a/api/src/organizational-entities/domain/usecases/index.js b/api/src/organizational-entities/domain/usecases/index.js index baea1f155a1..fbfab78eb89 100644 --- a/api/src/organizational-entities/domain/usecases/index.js +++ b/api/src/organizational-entities/domain/usecases/index.js @@ -2,6 +2,7 @@ import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import * as organizationTagRepository from '../../../../lib/infrastructure/repositories/organization-tag-repository.js'; +import * as learnersApi from '../../../prescription/learner-management/application/api/learners-api.js'; import * as schoolRepository from '../../../school/infrastructure/repositories/school-repository.js'; import { injectDependencies } from '../../../shared/infrastructure/utils/dependency-injection.js'; import { importNamedExportsFromDirectory } from '../../../shared/infrastructure/utils/import-named-exports-from-directory.js'; @@ -12,10 +13,10 @@ import * as dataProtectionOfficerRepository from '../../infrastructure/repositor import * as organizationFeatureRepository from '../../infrastructure/repositories/organization-feature-repository.js'; import { organizationForAdminRepository } from '../../infrastructure/repositories/organization-for-admin.repository.js'; import { tagRepository } from '../../infrastructure/repositories/tag.repository.js'; - const path = dirname(fileURLToPath(import.meta.url)); /** + * @typedef {import ('../../../prescription/learner-management/application/api/learners-api.js')} learnersApi * @typedef {import ('../../infrastructure/repositories/certification-center.repository.js')} CertificationCenterRepository * @typedef {import ('../../infrastructure/repositories/certification-center-for-admin-repository.js')} CertificationCenterForAdminRepository * @typedef {import ('../../infrastructure/repositories/complementary-certification-habilitation-repository.js')} ComplementaryCertificationHabilitationRepository @@ -34,6 +35,7 @@ const repositories = { organizationForAdminRepository, organizationFeatureRepository, schoolRepository, + learnersApi, organizationTagRepository, tagRepository, }; diff --git a/api/tests/organizational-entities/unit/application/organization/organization.admin.controller.test.js b/api/tests/organizational-entities/unit/application/organization/organization.admin.controller.test.js index 4d2e830652e..62f352f40a9 100644 --- a/api/tests/organizational-entities/unit/application/organization/organization.admin.controller.test.js +++ b/api/tests/organizational-entities/unit/application/organization/organization.admin.controller.test.js @@ -5,11 +5,12 @@ import { domainBuilder, expect, hFake, sinon } from '../../../../test-helper.js' describe('Unit | Organizational Entities | Application | Controller | Admin | organization', function () { describe('#addOrganizationFeatureInBatch', function () { - let filePath, request; + let filePath, request, userId; beforeEach(function () { + userId = Symbol('userId'); filePath = Symbol('filePath'); - request = { payload: { path: filePath } }; + request = { payload: { path: filePath }, auth: { credentials: { userId } } }; sinon.stub(usecases, 'addOrganizationFeatureInBatch').resolves(); }); @@ -24,6 +25,7 @@ describe('Unit | Organizational Entities | Application | Controller | Admin | or // then expect(usecases.addOrganizationFeatureInBatch).to.have.been.calledWithExactly({ + userId, filePath, }); }); diff --git a/api/tests/organizational-entities/unit/domain/models/OrganizationFeature_test.js b/api/tests/organizational-entities/unit/domain/models/OrganizationFeature_test.js index 5de9c7f46f4..14e5ab18b90 100644 --- a/api/tests/organizational-entities/unit/domain/models/OrganizationFeature_test.js +++ b/api/tests/organizational-entities/unit/domain/models/OrganizationFeature_test.js @@ -16,7 +16,24 @@ describe('Unit | Organizational Entities | Domain | Model | OrganizationFeature' //when organizationFeature = new OrganizationFeature({ featureId, organizationId, params }); // then - expect(organizationFeature).to.deep.equal({ featureId: 1, organizationId: 2, params: { id: 3 } }); + expect(organizationFeature).to.deep.equal({ + featureId: 1, + organizationId: 2, + params: { id: 3 }, + deletedLearner: false, + }); + }); + + it('should activate learner deletion given params', function () { + //when + organizationFeature = new OrganizationFeature({ featureId, organizationId, params, deletedLearner: 'Y' }); + // then + expect(organizationFeature).to.deep.equal({ + featureId: 1, + organizationId: 2, + params: { id: 3 }, + deletedLearner: true, + }); }); }); }); diff --git a/api/tests/organizational-entities/unit/domain/usecases/add-organization-feature-in-batch_test.js b/api/tests/organizational-entities/unit/domain/usecases/add-organization-feature-in-batch_test.js index 8b722f2cb8c..23128fd01f0 100644 --- a/api/tests/organizational-entities/unit/domain/usecases/add-organization-feature-in-batch_test.js +++ b/api/tests/organizational-entities/unit/domain/usecases/add-organization-feature-in-batch_test.js @@ -5,19 +5,20 @@ import { DomainTransaction } from '../../../../../src/shared/domain/DomainTransa import { catchErr, createTempFile, expect, removeTempFile, sinon } from '../../../../test-helper.js'; describe('Unit | Domain | UseCases | add-organization-feature-in-batch', function () { - let organizationFeatureRepository, featureId, filePath, csvData; + let organizationFeatureRepository, learnersApi, featureId, filePath, csvData, userId; beforeEach(function () { sinon.stub(DomainTransaction, 'execute').callsFake((callback) => { return callback(); }); - + userId = Symbol('userId'); featureId = 1; csvData = [ new OrganizationFeature({ featureId, organizationId: 123, params: `{ "id": 123 }` }), new OrganizationFeature({ featureId, organizationId: 456, params: `{ "id": 123 }` }), ]; + learnersApi = { deleteOrganizationLearnerBeforeImportFeature: sinon.stub() }; organizationFeatureRepository = { saveInBatch: sinon.stub(), }; @@ -37,9 +38,29 @@ describe('Unit | Domain | UseCases | add-organization-feature-in-batch', functio `, ); // when - await addOrganizationFeatureInBatch({ filePath, organizationFeatureRepository }); + await addOrganizationFeatureInBatch({ filePath, organizationFeatureRepository, learnersApi }); + + expect(organizationFeatureRepository.saveInBatch).to.have.been.calledOnceWithExactly(csvData); + expect(learnersApi.deleteOrganizationLearnerBeforeImportFeature.called).to.be.false; + }); + + it('should call call learner api with correct paramaters', async function () { + // given + filePath = await createTempFile( + 'test.csv', + `Feature ID;Organization ID;Params;Delete Learner + ${featureId};123;{"id": 123}; + ${featureId};456;{"id": 123};Y +`, + ); + // when + await addOrganizationFeatureInBatch({ userId, filePath, organizationFeatureRepository, learnersApi }); expect(organizationFeatureRepository.saveInBatch).to.have.been.calledOnceWithExactly(csvData); + expect(learnersApi.deleteOrganizationLearnerBeforeImportFeature).to.have.been.calledOnceWithExactly({ + userId, + organizationId: 456, + }); }); it('should throw a FeatureParamsNotProcessable error', async function () {