-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(apiv2):40 implement referentiel import functionality with region…
… academique support - Added ReferentielModule to handle import tasks related to regions academiques. - Introduced ReferentielImportTaskService for managing import logic. - Created RegionAcademiqueImportService for handling region-specific imports. - Implemented ImportReferentielController to manage API endpoints for imports. - Added necessary models, DTOs, and MongoDB repository for region academique. - Added tests for the new functionality. This commit enhances the API with the ability to import region academique data, improving data management capabilities.
- Loading branch information
1 parent
ff86b5f
commit 132f4d9
Showing
45 changed files
with
1,506 additions
and
166 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,4 @@ dist/**/* | |
**.js | ||
.env | ||
**.xlsx* | ||
!test/**/**/fixtures/*.xlsx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
apiv2/src/admin/core/referentiel/ReferentielImportTask.model.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { TaskModel } from "@task/core/Task.model"; | ||
import { ReferentielTaskType } from "snu-lib"; | ||
|
||
export interface ReferentielImportTaskAuthor { | ||
id?: string; | ||
prenom?: string; | ||
nom?: string; | ||
role?: string; | ||
sousRole?: string; | ||
email?: string; | ||
} | ||
|
||
export interface ReferentielImportTaskParameters { | ||
type: ReferentielTaskType; | ||
fileName: string; | ||
fileKey: string; | ||
fileLineCount: number; | ||
auteur: ReferentielImportTaskAuthor; | ||
folderPath: string; | ||
} | ||
|
||
export type ReferentielImportTaskResult = { | ||
rapportKey: string; | ||
}; | ||
|
||
export type ReferentielImportTaskModel = TaskModel<ReferentielImportTaskParameters, ReferentielImportTaskResult>; | ||
export type CreateReferentielImportTaskModel = Omit<ReferentielImportTaskModel, "id" | "createdAt" | "updatedAt">; |
163 changes: 163 additions & 0 deletions
163
apiv2/src/admin/core/referentiel/ReferentielImportTask.service.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
import { Test, TestingModule } from "@nestjs/testing"; | ||
import { Logger } from "@nestjs/common"; | ||
|
||
import { FileGateway } from "@shared/core/File.gateway"; | ||
import { TaskGateway } from "@task/core/Task.gateway"; | ||
import { FunctionalException, FunctionalExceptionCode } from "@shared/core/FunctionalException"; | ||
import { ReferentielTaskType, TaskName, TaskStatus } from "snu-lib"; | ||
import { ReferentielImportTaskService } from "./ReferentielImportTask.service"; | ||
import { REGION_ACADEMIQUE_COLUMN_NAMES } from "./ReferentielTaskTypeColumns"; | ||
|
||
describe("ReferentielImportTaskService", () => { | ||
let referentielImportTaskService: ReferentielImportTaskService; | ||
let fileGateway: FileGateway; | ||
let taskGateway: TaskGateway; | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
providers: [ | ||
ReferentielImportTaskService, | ||
Logger, | ||
{ | ||
provide: FileGateway, | ||
useValue: { | ||
parseXLS: jest.fn().mockResolvedValue([{}]), | ||
uploadFile: jest.fn().mockResolvedValue({ Key: "test-key" }), | ||
}, | ||
}, | ||
{ provide: TaskGateway, useValue: { create: jest.fn() } }, | ||
], | ||
}).compile(); | ||
|
||
fileGateway = module.get<FileGateway>(FileGateway); | ||
taskGateway = module.get<TaskGateway>(TaskGateway); | ||
referentielImportTaskService = module.get<ReferentielImportTaskService>(ReferentielImportTaskService); | ||
}); | ||
|
||
const regionAcademiqueRecord = { | ||
[REGION_ACADEMIQUE_COLUMN_NAMES.code]: "BRE", | ||
[REGION_ACADEMIQUE_COLUMN_NAMES.libelle]: "BRETAGNE", | ||
[REGION_ACADEMIQUE_COLUMN_NAMES.zone]: "A", | ||
[REGION_ACADEMIQUE_COLUMN_NAMES.date_derniere_modification_si]: "31/07/2024" | ||
}; | ||
|
||
|
||
describe("import", () => { | ||
const mockAuteur = { | ||
id: "id", | ||
prenom: "prenom", | ||
nom: "nom", | ||
role: "role", | ||
sousRole: "sousRole", | ||
}; | ||
|
||
const mockImportParams = { | ||
importType: ReferentielTaskType.IMPORT_REGIONS_ACADEMIQUES, | ||
fileName: "fileName", | ||
buffer: Buffer.from("test"), | ||
mimetype: "mimetype", | ||
auteur: mockAuteur, | ||
}; | ||
|
||
it("should not import empty file", async () => { | ||
jest.spyOn(fileGateway, "parseXLS").mockResolvedValue([]); | ||
await expect( | ||
referentielImportTaskService.import({ | ||
importType: ReferentielTaskType.IMPORT_REGIONS_ACADEMIQUES, | ||
fileName: "fileName", | ||
buffer: Buffer.from(""), | ||
mimetype: "mimetype", | ||
auteur: mockAuteur, | ||
}), | ||
).rejects.toThrow(new FunctionalException(FunctionalExceptionCode.IMPORT_EMPTY_FILE)); | ||
}); | ||
|
||
it("should not import file without valid column", async () => { | ||
jest.spyOn(fileGateway, "parseXLS").mockResolvedValue([ | ||
{ | ||
"Session formule": "", | ||
"Code court de Route": "", | ||
"Commentaire interne sur l'enregistrement": "", | ||
"Session : Code de la session": "", | ||
"Session : Désignation de la session": "", | ||
"Session : Date de début de la session": "", | ||
"Session : Date de fin de la session": "", | ||
// Route: "", | ||
"Code point de rassemblement initial": "", | ||
"Point de rassemblement initial": "", | ||
} | ||
]); | ||
await expect( | ||
referentielImportTaskService.import({ | ||
importType: ReferentielTaskType.IMPORT_ROUTES, | ||
fileName: "fileName", | ||
buffer: Buffer.from("test"), | ||
mimetype: "mimetype", | ||
auteur: { | ||
id: "id", | ||
prenom: "prenom", | ||
nom: "nom", | ||
role: "role", | ||
sousRole: "sousRole", | ||
}, | ||
}), | ||
).rejects.toThrow(new FunctionalException(FunctionalExceptionCode.IMPORT_MISSING_COLUMN)); | ||
}); | ||
|
||
|
||
|
||
it("should import file with valid columns", async () => { | ||
jest.spyOn(fileGateway, "parseXLS").mockResolvedValue([regionAcademiqueRecord]); | ||
|
||
await referentielImportTaskService.import({ | ||
importType: ReferentielTaskType.IMPORT_REGIONS_ACADEMIQUES, | ||
fileName: "fileName", | ||
buffer: Buffer.from("test"), | ||
mimetype: "mimetype", | ||
auteur: { | ||
id: "id", | ||
prenom: "prenom", | ||
nom: "nom", | ||
role: "role", | ||
sousRole: "sousRole", | ||
}, | ||
}); | ||
|
||
expect(taskGateway.create).toHaveBeenCalledWith({ | ||
name: TaskName.REFERENTIEL_IMPORT, | ||
status: TaskStatus.PENDING, | ||
metadata: { | ||
parameters: { | ||
type: ReferentielTaskType.IMPORT_REGIONS_ACADEMIQUES, | ||
fileKey: "test-key", | ||
fileLineCount: 1, | ||
fileName: "fileName", | ||
auteur: { | ||
id: "id", | ||
prenom: "prenom", | ||
nom: "nom", | ||
role: "role", | ||
sousRole: "sousRole", | ||
}, | ||
}, | ||
}, | ||
}); | ||
}); | ||
|
||
it("devrait vérifier la présence de toutes les colonnes requises", async () => { | ||
const partialData = { | ||
"Code région académique": "RA01", | ||
"Région académique : Libellé région académique long": "Test Region", | ||
// Colonne manquante: "Zone région académique édition" | ||
"Région académique : Date de création": "2023-01-01", | ||
"Région académique : Date de dernière modification": "2023-01-02", | ||
}; | ||
|
||
jest.spyOn(fileGateway, "parseXLS").mockResolvedValue([partialData]); | ||
|
||
await expect(referentielImportTaskService.import(mockImportParams)) | ||
.rejects | ||
.toThrow(new FunctionalException(FunctionalExceptionCode.IMPORT_MISSING_COLUMN, "Zone région académique édition")); | ||
}); | ||
}); | ||
}); |
71 changes: 71 additions & 0 deletions
71
apiv2/src/admin/core/referentiel/ReferentielImportTask.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { Inject, Injectable } from "@nestjs/common"; | ||
import { FileGateway } from "@shared/core/File.gateway"; | ||
import { FunctionalException, FunctionalExceptionCode } from "@shared/core/FunctionalException"; | ||
import { TaskGateway } from "@task/core/Task.gateway"; | ||
|
||
import { ReferentielTaskType, TaskName, TaskStatus } from "snu-lib"; | ||
import { TaskModel } from "@task/core/Task.model"; | ||
import { ReferentielImportTaskAuthor } from "./ReferentielImportTask.model"; | ||
import { REGION_ACADEMIQUE_COLUMN_NAMES, ROUTE_COLUMN_NAMES } from "@admin/core/referentiel/ReferentielTaskTypeColumns"; | ||
|
||
export const REQUIRED_COLUMN_NAMES = { | ||
[ReferentielTaskType.IMPORT_REGIONS_ACADEMIQUES]: Object.values(REGION_ACADEMIQUE_COLUMN_NAMES), | ||
[ReferentielTaskType.IMPORT_ROUTES]: Object.values(ROUTE_COLUMN_NAMES), | ||
}; | ||
|
||
@Injectable() | ||
export class ReferentielImportTaskService { | ||
constructor( | ||
@Inject(TaskGateway) private readonly taskGateway: TaskGateway, | ||
@Inject(FileGateway) private readonly fileGateway: FileGateway, | ||
) {} | ||
|
||
async import({ | ||
importType, | ||
fileName, | ||
buffer, | ||
mimetype, | ||
auteur, | ||
}: { | ||
importType: typeof ReferentielTaskType[keyof typeof ReferentielTaskType]; | ||
fileName: string; | ||
buffer: Buffer; | ||
mimetype: string; | ||
auteur: ReferentielImportTaskAuthor; | ||
}): Promise<TaskModel> { | ||
const dataToImport = await this.fileGateway.parseXLS<Record<string, string>>(buffer, { | ||
defval: "", | ||
}); | ||
|
||
if (dataToImport.length === 0) { | ||
throw new FunctionalException(FunctionalExceptionCode.IMPORT_EMPTY_FILE); | ||
} | ||
for (const column of REQUIRED_COLUMN_NAMES[importType]) { | ||
if (!dataToImport[0].hasOwnProperty(column)) { | ||
throw new FunctionalException(FunctionalExceptionCode.IMPORT_MISSING_COLUMN, column); | ||
} | ||
} | ||
|
||
const timestamp = `${new Date().toISOString()?.replaceAll(":", "-")?.replace(".", "-")}`; | ||
const s3File = await this.fileGateway.uploadFile(`file/admin/referentiel/${importType}/${timestamp}_${fileName}`, { | ||
data: buffer, | ||
mimetype, | ||
}); | ||
|
||
const task = await this.taskGateway.create({ | ||
name: TaskName.REFERENTIEL_IMPORT, | ||
status: TaskStatus.PENDING, | ||
metadata: { | ||
parameters: { | ||
type: importType, | ||
fileName, | ||
fileKey: s3File.Key, | ||
fileLineCount: dataToImport.length, | ||
auteur, | ||
}, | ||
}, | ||
}); | ||
|
||
return task; | ||
} | ||
} |
Oops, something went wrong.