From 19444692e905b9a47f3e60e6029c21f26e7e401c Mon Sep 17 00:00:00 2001 From: Caroline <4971715+carolineBda@users.noreply.github.com> Date: Mon, 23 May 2022 11:39:14 +0200 Subject: [PATCH] feat: psy admin form : format input values + update fields (#190) --- config/keycloak/mon-psy-sante-realm.json | 2 +- src/__tests__/psychologists-update.spec.ts | 98 ++++++++++ src/components/Admin/PsychologistForm.tsx | 173 ++++++++++++++---- .../Admin/PsychologistsForInstructors.tsx | 24 ++- src/pages/administration-annuaire/index.tsx | 23 ++- .../psychologists/[id].tsx | 14 +- src/pages/api/admin/psychologists/[id].ts | 39 ++-- .../__tests__/ds-parse-psychologists.spec.ts | 3 + src/services/__tests__/psychologists.spec.ts | 83 --------- .../parse-psychologists.ts | 79 +------- src/services/format-psychologists.ts | 92 ++++++++++ src/services/psychologists.ts | 76 ++++---- 12 files changed, 442 insertions(+), 264 deletions(-) create mode 100644 src/__tests__/psychologists-update.spec.ts create mode 100644 src/services/format-psychologists.ts diff --git a/config/keycloak/mon-psy-sante-realm.json b/config/keycloak/mon-psy-sante-realm.json index 3fc8c882..6e477ee2 100644 --- a/config/keycloak/mon-psy-sante-realm.json +++ b/config/keycloak/mon-psy-sante-realm.json @@ -413,7 +413,7 @@ "groups": [ { "id": "5b4a2802-26c7-457d-9977-c1ab23e02ab4", - "name": "admin", + "name": "CPAM", "path": "/admin", "attributes": {}, "realmRoles": [ diff --git a/src/__tests__/psychologists-update.spec.ts b/src/__tests__/psychologists-update.spec.ts new file mode 100644 index 00000000..2cb636e3 --- /dev/null +++ b/src/__tests__/psychologists-update.spec.ts @@ -0,0 +1,98 @@ +import { stub } from "sinon"; + +import { models } from "../db/models"; +import { getOnePsychologist } from "../db/seeds/psychologist"; +import { updateIfExists } from "../pages/api/admin/psychologists/[id]"; +import * as address from "../services/getAddressCoordinates"; +import { Psychologist } from "../types/psychologist"; + +describe("updateIfExists", () => { + let getAddressCoordinatesStub; + beforeAll(async () => { + await models.Psychologist.destroy({ where: {} }); + + const psy = await getOnePsychologist({ id: 1, department: "01" }); + // @ts-ignore + await models.Psychologist.create(psy); + }); + beforeEach(() => { + getAddressCoordinatesStub = stub(address, "default"); + }); + + afterEach(() => { + getAddressCoordinatesStub.restore(); + }); + const valideInput = { + address: "My new adress", + addressAdditional: "Mon complément d'adresse", + cdsmsp: "optio", + displayEmail: true, + email: "updated@example.net", + firstName: "CaRmen", + lastName: "strOMan", + phone: "02 71 94 65 55", + displayPhone: false, + public: "Adultes et enfants/adolescents", + teleconsultation: true, + visible: true, + website: "grotesque-proximity.info", + shouldBeIgnored: "should not failed", + }; + + it("update should undefined if does not exists", async () => { + expect(await updateIfExists("1111", "", {})).toEqual(undefined); + }); + it("update should undefined if the department does not match", async () => { + expect(await updateIfExists("1111", "99", {})).toEqual(undefined); + }); + it("update should return error if wrong params", async () => { + let exception; + await updateIfExists("1", "01", {}).catch((e) => (exception = e)); + expect(exception.details.length).toEqual(9); + expect(exception.details[0].message).toEqual('"address" is required'); + }); + it("update should update psy in db", async () => { + let exception; + getAddressCoordinatesStub.returns({ latitude: 456, longitude: 123 }); + + const result = await updateIfExists("1", "01", valideInput).catch( + (e) => (exception = e) + ); + expect(exception).toEqual(undefined); + expect(result).toEqual([1]); + + // @ts-ignore + const updatedPsy: Psychologist = await models.Psychologist.findOne({ + raw: true, + where: { email: valideInput.email }, + }); + expect(updatedPsy.firstName).toEqual("Carmen"); + expect(updatedPsy.lastName).toEqual("STROMAN"); + expect(updatedPsy.phone).toEqual("02 71 94 65 55"); + expect(updatedPsy.displayPhone).toEqual(false); + expect(updatedPsy.public).toEqual("Adultes et enfants/adolescents"); + expect(updatedPsy.addressAdditional).toEqual("Mon complément d'adresse"); + expect(updatedPsy.coordinates.coordinates).toEqual([123, 456]); + expect(updatedPsy.coordinates.type).toEqual("Point"); + + // @ts-ignore + expect(updatedPsy.shouldBeIgnored).toEqual(undefined); + }); + it("update should coordinate with null if API returns null", async () => { + let exception; + getAddressCoordinatesStub.returns(null); + + const result = await updateIfExists("1", "01", valideInput).catch( + (e) => (exception = e) + ); + expect(exception).toEqual(undefined); + expect(result).toEqual([1]); + + // @ts-ignore + const updatedPsy: Psychologist = await models.Psychologist.findOne({ + raw: true, + where: { email: valideInput.email }, + }); + expect(updatedPsy.coordinates).toEqual(null); + }); +}); diff --git a/src/components/Admin/PsychologistForm.tsx b/src/components/Admin/PsychologistForm.tsx index 06d5d29b..e82c0228 100644 --- a/src/components/Admin/PsychologistForm.tsx +++ b/src/components/Admin/PsychologistForm.tsx @@ -14,29 +14,55 @@ import { Psychologist } from "../../types/psychologist"; const editableFields = [ { field: "visible", - label: "Actuellement disponible", + label: "Disponibilité du psychologue :", + legend: "En indisponible les modalités de contact ne seront plus visible", type: "boolean", + options: [ + { label: "Disponible", value: true }, + { label: "Indisponible", value: false }, + ], + required: true, }, { field: "lastName", label: "Nom", required: true }, { field: "firstName", label: "Prénom", required: true }, - { field: "address", label: "Adresse", required: true }, - { field: "addressAdditional", label: "Complément d'adresse" }, - { field: "secondAddress", label: "Adresse secondaire" }, { - field: "secondAddressAdditional", - label: "Complément d'adresse secondaire", + field: "address", + label: "Adresse postale du cabinet principal", + legend: + "Préciser ici : numéro, libellé de la voie, code postal, et ville. Cette adresse doit se situer dans le même département que la CPAM. Pour les séances au domicile du patient, noter le code postal + ville.", + required: true, }, - { field: "phone", label: "Téléphone", required: true }, { - field: "displayPhone", - label: "Afficher le téléphone", - type: "boolean", + field: "addressAdditional", + label: "Informations complémentaires (cabinet principal)", + legend: "Informations complémentaires (cabinet principal)", }, + { + field: "secondAddress", + label: "Adresse postale d'un second lieu d'exercice", + legend: + "Bien préciser : numéro et libellé de la voie, code postal, et ville", + }, + { + field: "secondAddressAdditional", + label: "Informations complémentaires (second lieu)", + }, + { field: "phone", label: "Téléphone", required: true }, { field: "email", label: "Email" }, { field: "displayEmail", - label: "Afficher l'email", + label: "Afficher l'email :", type: "boolean", + options: [ + { label: "Oui", value: true }, + { label: "Non", value: false }, + ], + }, + { + field: "website", + label: "Site web", + legend: + "Préciser ici l'url complet du site internet en commençant par http:// ou https://", }, { field: "public", @@ -49,18 +75,37 @@ const editableFields = [ required: true, type: "select", }, - { field: "languages", label: "Langue(s) parlée(s)" }, - { field: "cdsmsp", label: "Nom du CDS ou de la MSP" }, - { field: "website", label: "Site" }, + { + field: "languages", + label: "Langues de réalisation des séances (autre que le français)", + }, + { + field: "cdsmsp", + label: "Nom du CDS ou de la MSP", + legend: + "A préciser UNIQUEMENT si le psychologue a une activité salariée en MSP ou CDS et s’il a choisi d’être conventionné au titre de cette activité salariée.", + }, { field: "teleconsultation", - label: "Possibilité de séances à distance", + label: "Possibilité de séances à distance :", type: "boolean", + options: [ + { label: "Oui", value: true }, + { label: "Non", value: false }, + ], }, ]; -const PsychologistForm = ({ psychologist }: { psychologist: Psychologist }) => { +const PsychologistForm = ({ + psychologist, + isSuperAdmin, +}: { + psychologist: Psychologist; + isSuperAdmin: boolean; +}) => { const [result, setResult] = useState<{ type: string; text: string }>(); + const [sending, setSending] = useState(false); + const [modifiedPsychologist, setModifiedPsychologist] = useState(psychologist); useEffect(() => { @@ -74,26 +119,20 @@ const PsychologistForm = ({ psychologist }: { psychologist: Psychologist }) => { }); }; + const findLabel = (options: any[], value: boolean): string => { + const selected = options.find((item) => item.value === value); + return selected.label; + }; + const submit = (e) => { e.preventDefault(); setResult(null); + setSending(true); axios - .put(`/api/admin/psychologists/${modifiedPsychologist.id}`, { - address: modifiedPsychologist.address, - secondAddress: modifiedPsychologist.secondAddress, - cdsmsp: modifiedPsychologist.cdsmsp, - displayEmail: modifiedPsychologist.displayEmail, - email: modifiedPsychologist.email, - firstName: modifiedPsychologist.firstName, - languages: modifiedPsychologist.languages, - lastName: modifiedPsychologist.lastName, - phone: modifiedPsychologist.phone, - displayPhone: modifiedPsychologist.displayPhone, - public: modifiedPsychologist.public, - teleconsultation: modifiedPsychologist.teleconsultation, - visible: modifiedPsychologist.visible, - website: modifiedPsychologist.website, - }) + .put( + `/api/admin/psychologists/${modifiedPsychologist.id}`, + modifiedPsychologist + ) .then(() => { setResult({ text: "Psychologue correctement mis à a jour", @@ -105,6 +144,9 @@ const PsychologistForm = ({ psychologist }: { psychologist: Psychologist }) => { text: "Une erreur est survenue, veuillez reesayer", type: "error", }); + }) + .finally(() => { + setSending(false); }); }; @@ -112,13 +154,30 @@ const PsychologistForm = ({ psychologist }: { psychologist: Psychologist }) => { Modifier le psychologue - + + Les champs suivis d’un astérisque ( *{" "} + ) sont obligatoires. + + {editableFields.map((editableField) => { switch (editableField.type) { case "boolean": return ( - <> - {editableField.label} + + + {editableField.label}{" "} + + {findLabel( + editableField.options, + modifiedPsychologist[editableField.field] + )} + + + {editableField.legend && ( + + {editableField.legend} + + )} { htmlFor={editableField.label} /> - > + ); case "select": return ( @@ -143,6 +202,7 @@ const PsychologistForm = ({ psychologist }: { psychologist: Psychologist }) => { //@ts-ignore required={editableField.required} label={editableField.label} + hint={editableField.legend} options={editableField.options.map((option) => ({ label: option, value: option, @@ -159,6 +219,7 @@ const PsychologistForm = ({ psychologist }: { psychologist: Psychologist }) => { key={editableField.label} required={editableField.required} label={editableField.label} + hint={editableField.legend} //@ts-ignore value={modifiedPsychologist[editableField.field]} onChange={(e) => @@ -168,7 +229,44 @@ const PsychologistForm = ({ psychologist }: { psychologist: Psychologist }) => { ); } })} - Enregistrer + {isSuperAdmin && ( + + + Afficher le téléphone :{" "} + + {findLabel( + [ + { label: "Oui", value: true }, + { label: "Non", value: false }, + ], + modifiedPsychologist["displayPhone"] + )} + + + + { + update("displayPhone", e.target.checked); + }} + /> + + + + )} + + {sending ? ( + <> + {" "} + Sauvegarde en cours ... + > + ) : ( + "Enregistrer" + )} + {result && ( { ); }; - export default PsychologistForm; diff --git a/src/components/Admin/PsychologistsForInstructors.tsx b/src/components/Admin/PsychologistsForInstructors.tsx index 06dbcaa4..d2e4b813 100644 --- a/src/components/Admin/PsychologistsForInstructors.tsx +++ b/src/components/Admin/PsychologistsForInstructors.tsx @@ -6,8 +6,10 @@ import { Psychologist } from "../../types/psychologist"; const PsychologistsForInstructors = ({ psychologists, + department, }: { psychologists: Partial[]; + department: string; }) => { const router = useRouter(); const [search, setSearch] = useState(""); @@ -75,15 +77,19 @@ const PsychologistsForInstructors = ({ ]; return ( <> - Psychologues - setSearch(e.target.value)} - /> - + Psychologues - CPAM {department} + {filteredPsychologists.length > 0 && ( + <> + setSearch(e.target.value)} + /> + + > + )} > ); }; diff --git a/src/pages/administration-annuaire/index.tsx b/src/pages/administration-annuaire/index.tsx index d2ecdd9a..7b4fbdee 100644 --- a/src/pages/administration-annuaire/index.tsx +++ b/src/pages/administration-annuaire/index.tsx @@ -13,9 +13,11 @@ import { Psychologist } from "../../types/psychologist"; const Admin = ({ psychologists, count, + department, }: { psychologists: Partial[]; count: number; + department: string | undefined; }) => { const router = useRouter(); @@ -29,7 +31,7 @@ const Admin = ({ Nombre de psychologues {count} - + {router.query.error === "NotFound" && ( )} - {psychologists.length ? ( - + {department ? ( + ) : ( )} @@ -56,10 +61,12 @@ export const getServerSideProps: GetServerSideProps = async (context) => { let psychologists = []; let count; + let department; if (session.user.isSuperAdmin) { count = await countAll(); } else if (session.user.isAdmin) { - psychologists = await getByDepartment(session.user.department as string); + department = session.user.department; + psychologists = await getByDepartment(department as string); psychologists = psychologists.map((psychologist) => { const { id, firstName, lastName } = psychologist; return { firstName, id, lastName }; @@ -74,7 +81,9 @@ export const getServerSideProps: GetServerSideProps = async (context) => { }; } - return { - props: { psychologists, count }, - }; + const props: any = { psychologists, count }; + if (department) { + props.department = department; + } + return { props: props }; }; diff --git a/src/pages/administration-annuaire/psychologists/[id].tsx b/src/pages/administration-annuaire/psychologists/[id].tsx index 0e25e852..893331bb 100644 --- a/src/pages/administration-annuaire/psychologists/[id].tsx +++ b/src/pages/administration-annuaire/psychologists/[id].tsx @@ -6,13 +6,22 @@ import Header from "../../../components/Admin/Header"; import PsychologistForm from "../../../components/Admin/PsychologistForm"; import { getOne } from "../../../services/psychologists"; -const EditablePsychologist = ({ psychologist }: { psychologist: string }) => { +const EditablePsychologist = ({ + psychologist, + isSuperAdmin, +}: { + psychologist: string; + isSuperAdmin: boolean; +}) => { return ( <> - + > ); @@ -39,6 +48,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => { return { props: { psychologist: JSON.stringify(psychologist), + isSuperAdmin: !!session.user.isSuperAdmin, }, }; }; diff --git a/src/pages/api/admin/psychologists/[id].ts b/src/pages/api/admin/psychologists/[id].ts index f9d02f2c..28ef9679 100644 --- a/src/pages/api/admin/psychologists/[id].ts +++ b/src/pages/api/admin/psychologists/[id].ts @@ -3,12 +3,19 @@ import { NextApiRequest, NextApiResponse } from "next"; import { getSession } from "next-auth/react"; import { handleApiError } from "../../../../services/api"; -import { getOne, update } from "../../../../services/psychologists"; +import { formatPsychologist } from "../../../../services/format-psychologists"; +import { + filterAllowedKeys, + getOne, + update, +} from "../../../../services/psychologists"; const updateSchema = Joi.object({ address: Joi.string().required(), + coordinates: Joi.object().allow(null), addressAdditional: Joi.string().allow("").allow(null), secondAddress: Joi.string().allow(""), + secondAddressCoordinates: Joi.object().allow(null), secondAddressAdditional: Joi.string().allow("").allow(null), cdsmsp: Joi.string().allow(""), displayEmail: Joi.boolean().required(), @@ -26,21 +33,31 @@ const updateSchema = Joi.object({ website: Joi.string().allow("").allow(null), }); -export const updatePsy = async (req: NextApiRequest, res: NextApiResponse) => { +export const updateIfExists = async (id: string, department: string, body) => { + const existingPsychologist = await getOne(id, department); + if (!existingPsychologist) return; + + const psy = await formatPsychologist(filterAllowedKeys(body)); + await updateSchema.validateAsync(psy, { + abortEarly: false, + }); + + return await update(id, psy); +}; + +const updatePsy = async (req: NextApiRequest, res: NextApiResponse) => { if (req.method === "PUT") { const session = await getSession({ req }); const id = req.query.id as string; - const existingPsychologist = await getOne( - id, - session.user.isSuperAdmin ? "" : (session.user.department as string) - ); - if (!existingPsychologist) { - return res.status(404).send("Psychologue non trouvé"); - } - await updateSchema.validateAsync(req.body); + const dep = session.user.isSuperAdmin + ? "" + : (session.user.department as string); + const updated = await updateIfExists(id, dep, req.body); - await update(id, req.body); + if (!updated) { + return res.status(404).send("Psychologue non trouvé"); + } return res.status(200).send("Psychologue mis à jour"); } }; diff --git a/src/services/__tests__/ds-parse-psychologists.spec.ts b/src/services/__tests__/ds-parse-psychologists.spec.ts index e0d51386..84361ed7 100644 --- a/src/services/__tests__/ds-parse-psychologists.spec.ts +++ b/src/services/__tests__/ds-parse-psychologists.spec.ts @@ -10,6 +10,7 @@ describe("parseDossierMetadata", () => { }); const dossier = { + id: "1", archived: false, champs: [ { @@ -35,6 +36,7 @@ describe("parseDossierMetadata", () => { expect(result).toEqual({ address: "12 Rue Neuve 31000 Toulouse", + demarcheSimplifieesId: "1", archived: false, department: "31", displayEmail: false, @@ -43,6 +45,7 @@ describe("parseDossierMetadata", () => { lastName: "SMITH", state: "Accepted", teleconsultation: true, + coordinates: null, }); expect(axios.get).toHaveBeenCalledWith( diff --git a/src/services/__tests__/psychologists.spec.ts b/src/services/__tests__/psychologists.spec.ts index f3d238da..0df097f7 100644 --- a/src/services/__tests__/psychologists.spec.ts +++ b/src/services/__tests__/psychologists.spec.ts @@ -1,13 +1,11 @@ /* eslint-disable jest/no-conditional-expect */ import { expect } from "@jest/globals"; -import { stub } from "sinon"; import { models } from "../../db/models"; import { getOnePsychologist } from "../../db/seeds/psychologist"; import { FILTER } from "../../types/enums/filters"; import { allPublics, PUBLIC } from "../../types/enums/public"; import { Psychologist } from "../../types/psychologist"; -import * as address from "../getAddressCoordinates"; import { countAll, getAll, @@ -16,7 +14,6 @@ import { getDateLatestArchived, getOne, saveMany, - update, updateState, } from "../psychologists"; @@ -213,10 +210,6 @@ describe("Service psychologists", () => { expect(result.public).not.toBe(PUBLIC.ADULTES) ); }); - - it.skip("Should sort by distance", () => { - // no idea how to do that - }); }); describe("saveMany", () => { @@ -241,82 +234,6 @@ describe("Service psychologists", () => { }); }); - describe("update", () => { - let getAddressCoordinatesStub; - beforeEach(() => { - getAddressCoordinatesStub = stub(address, "default"); - }); - - afterEach(() => { - getAddressCoordinatesStub.restore(); - }); - - it("Should update only updatable fields", async () => { - getAddressCoordinatesStub.returns({ latitude: 456, longitude: 123 }); - const initialPsy = getOnePsychologist(); - // @ts-ignore - await models.Psychologist.create(initialPsy); - - const modifiedPsy = getOnePsychologist(); - await update(initialPsy.id.toString(), modifiedPsy); - - const updateableFields = [ - "address", - "addressAdditional", - "secondAddress", - "secondAddressAdditional", - "coordinates", - "cdsmsp", - "displayEmail", - "displayPhone", - "email", - "firstName", - "languages", - "lastName", - "phone", - "public", - "teleconsultation", - "visible", - "website", - ]; - - const updatedPsy = await models.Psychologist.findOne({ - raw: true, - where: { id: initialPsy.id }, - }); - - expect(updatedPsy).toBeDefined(); - - Object.keys(updatedPsy).forEach((key) => { - if (key === "coordinates" || key === "secondAddressCoordinates") { - expect(updatedPsy[key].coordinates).toEqual([123, 456]); - expect(updatedPsy[key].type).toEqual("Point"); - } else if (updateableFields.includes(key)) { - expect(updatedPsy[key]).toEqual(modifiedPsy[key]); - } else if (key !== "createdAt" && key !== "updatedAt") { - expect(updatedPsy[key]).toEqual(initialPsy[key]); - } - }); - }); - - it("Should update psy event without coordinates", async () => { - getAddressCoordinatesStub.returns(null); - const initialPsy = getOnePsychologist(); - // @ts-ignore - await models.Psychologist.create(initialPsy); - - await update(initialPsy.id.toString(), initialPsy); - - const updatedPsy = await models.Psychologist.findOne({ - raw: true, - where: { id: initialPsy.id }, - }); - - // @ts-ignore - expect(updatedPsy.coordinates).toEqual(null); - }); - }); - describe("updateState", () => { const department = "updateState"; diff --git a/src/services/demarchesSimplifiees/parse-psychologists.ts b/src/services/demarchesSimplifiees/parse-psychologists.ts index 995e3312..95a89a55 100644 --- a/src/services/demarchesSimplifiees/parse-psychologists.ts +++ b/src/services/demarchesSimplifiees/parse-psychologists.ts @@ -1,11 +1,8 @@ -import Joi from "joi"; import pLimit from "p-limit"; -import { SRID } from "../../types/const/geometry"; -import { Coordinates, CoordinatesPostgis } from "../../types/coordinates"; import { DSPsychologist, Psychologist } from "../../types/psychologist"; import config from "../config"; -import getAddressCoordinates from "../getAddressCoordinates"; +import { formatLanguage, formatPsychologist } from "../format-psychologists"; const limit = pLimit(5); @@ -14,59 +11,14 @@ const extractDepartmentNumber = (dep: string): string => { }; const CHAMPS = JSON.parse(config.demarchesSimplifiees.champs); const CHAMP_LANGUAGE_OTHER = "Q2hhbXAtMjM0NjQzNA=="; -const websiteSchema = Joi.object({ - website: Joi.string().uri().required(), -}); -const emailSchema = Joi.object({ - email: Joi.string().email().required(), -}); -const frenchWord = new RegExp( - "(français|francais|langue française)(?:\\s?et\\s?)?[^a-zA-Z]*", - "ig" -); -const capitalizeFirstLetter = (word) => - word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); - -function formatFirstName(string) { - return string - .trim() - .split(" ") - .map((word) => word.split("-").map(capitalizeFirstLetter).join("-")) - .join(" "); -} - -const formatLanguage = (value) => { - if (!value) return; - const cleanFrench = value.trim().replace(frenchWord, ""); - return cleanFrench || undefined; -}; - -const parsers = { +const PARSERS = { displayEmail: (value) => value === "true", - email: (value) => - emailSchema.validate({ email: value }).error - ? undefined - : value.toLowerCase(), languages: formatLanguage, teleconsultation: (value) => value === "true", - website: (value) => - websiteSchema.validate({ website: value }).error - ? undefined - : value.toLowerCase(), }; const parseChampValue = (field, value) => - parsers[field] ? parsers[field](value) : value; - -export const formatCoordinates = ( - coordinates: Coordinates -): CoordinatesPostgis => { - return { - coordinates: [coordinates.longitude, coordinates.latitude], - crs: { properties: { name: "EPSG:" + SRID }, type: "name" }, - type: "POINT", - }; -}; + PARSERS[field] ? PARSERS[field](value) : value; const getDossierChamp = (dossier: DSPsychologist, id) => dossier.champs.find((champ) => champ.id === id); @@ -76,7 +28,7 @@ const addOtherLanguages = ( psychologist: Partial ) => { const dossierChamp = getDossierChamp(dossier, CHAMP_LANGUAGE_OTHER); - const otherLanguage = parseChampValue("languages", dossierChamp?.stringValue); + const otherLanguage = formatLanguage(dossierChamp?.stringValue); if (otherLanguage) { psychologist.languages = psychologist.languages ? psychologist.languages + ", " + otherLanguage @@ -91,9 +43,9 @@ export const parseDossierMetadata = async ( demarcheSimplifieesId: dossier.id, archived: dossier.archived, department: extractDepartmentNumber(dossier.groupeInstructeur.label), - firstName: formatFirstName(dossier.demandeur.prenom), + firstName: dossier.demandeur.prenom, id: dossier.number, - lastName: dossier.demandeur.nom.toUpperCase().trim(), + lastName: dossier.demandeur.nom, state: dossier.state, }; @@ -106,24 +58,7 @@ export const parseDossierMetadata = async ( }); addOtherLanguages(dossier, psychologist); - const coordinates = await getAddressCoordinates( - dossier.number.toString(), - psychologist.address - ); - if (coordinates) { - psychologist.coordinates = formatCoordinates(coordinates); - } - - if (psychologist.secondAddress) { - const coordinates = await getAddressCoordinates( - dossier.number.toString(), - psychologist.secondAddress - ); - if (coordinates) { - psychologist.secondAddressCoordinates = formatCoordinates(coordinates); - } - } - return psychologist as Psychologist; + return formatPsychologist(psychologist as Psychologist); }; const parsePsychologists = async ( diff --git a/src/services/format-psychologists.ts b/src/services/format-psychologists.ts new file mode 100644 index 00000000..bdde2b75 --- /dev/null +++ b/src/services/format-psychologists.ts @@ -0,0 +1,92 @@ +import Joi from "joi"; + +import { SRID } from "../types/const/geometry"; +import { Coordinates, CoordinatesPostgis } from "../types/coordinates"; +import { Psychologist } from "../types/psychologist"; +import getAddressCoordinates from "./getAddressCoordinates"; + +const websiteSchema = Joi.object({ + website: Joi.string().uri().required(), +}); +const emailSchema = Joi.object({ + email: Joi.string().email().required(), +}); +const frenchWord = new RegExp( + "(français|francais|langue française)(?:\\s?et\\s?)?[^a-zA-Z]*", + "ig" +); + +const capitalizeFirstLetter = (word) => + word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + +function formatFirstName(string) { + return string + .trim() + .split(" ") + .map((word) => word.split("-").map(capitalizeFirstLetter).join("-")) + .join(" "); +} + +export const formatLanguage = (value) => { + if (!value) return; + const cleanFrench = value.trim().replace(frenchWord, ""); + return cleanFrench || undefined; +}; + +const FORMATTERS = { + firstName: formatFirstName, + lastName: (value) => value.toUpperCase().trim(), + email: (value) => + emailSchema.validate({ email: value }).error + ? undefined + : value.toLowerCase(), + languages: formatLanguage, + website: (value) => + websiteSchema.validate({ website: value }).error + ? undefined + : value.toLowerCase(), +}; +const parseChampValue = (field, value) => + FORMATTERS[field] ? FORMATTERS[field](value) : value; + +export const formatCoordinates = ( + coordinates: Coordinates +): CoordinatesPostgis => { + return { + coordinates: [coordinates.longitude, coordinates.latitude], + crs: { properties: { name: "EPSG:" + SRID }, type: "name" }, + type: "POINT", + }; +}; + +export const formatPsychologist = async ( + psy: Partial +): Promise => { + const psychologist: Partial = {}; + + Object.keys(psy).forEach((field) => { + psychologist[field] = parseChampValue(field, psy[field]); + }); + + const identifier = psychologist.id?.toString() ?? psychologist.email; + const coordinates = await getAddressCoordinates( + identifier, + psychologist.address + ); + psychologist.coordinates = coordinates + ? formatCoordinates(coordinates) + : null; + + if (psychologist.secondAddress) { + const coordinates = await getAddressCoordinates( + identifier, + psychologist.secondAddress + ); + psychologist.secondAddressCoordinates = coordinates + ? formatCoordinates(coordinates) + : null; + } + return psychologist as Psychologist; +}; + +export default formatPsychologist; diff --git a/src/services/psychologists.ts b/src/services/psychologists.ts index a3979bdb..6019e3c1 100644 --- a/src/services/psychologists.ts +++ b/src/services/psychologists.ts @@ -6,8 +6,6 @@ import { SRID } from "../types/const/geometry"; import { FILTER } from "../types/enums/filters"; import { PUBLIC } from "../types/enums/public"; import { Psychologist } from "../types/psychologist"; -import { formatCoordinates } from "./demarchesSimplifiees/parse-psychologists"; -import getAddressCoordinates from "./getAddressCoordinates"; const limit = pLimit(5); @@ -27,6 +25,7 @@ export const getByDepartment = async (dep: string): Promise => { return models.Psychologist.findAll({ raw: true, where: { archived: false, department: dep, state: "accepte" }, + order: [["lastName", "ASC"]], }); }; @@ -114,49 +113,44 @@ export const saveMany = async (psychologists: Psychologist[]) => { ignoreDuplicates: true, }); }; - +const UPDATABLE_KEYS = [ + "address", + "addressAdditional", + "secondAddress", + "secondAddressAdditional", + "cdsmsp", + "coordinates", + "secondAddressCoordinates", + "displayEmail", + "email", + "firstName", + "languages", + "lastName", + "phone", + "displayPhone", + "public", + "teleconsultation", + "visible", + "website", +]; + +export const filterAllowedKeys = ( + psy: Partial +): Partial => { + return Object.keys(psy) + .filter((key) => UPDATABLE_KEYS.includes(key)) + .reduce((obj, key) => { + obj[key] = psy[key]; + return obj; + }, {}); +}; export const update = async ( id: string, psychologist: Partial ) => { - const displayName = psychologist.firstName + " " + psychologist.lastName; - const coordinates = await getAddressCoordinates( - displayName, - psychologist.address - ); - let secondAddressCoordinates; - if (psychologist.secondAddress) { - secondAddressCoordinates = await getAddressCoordinates( - displayName, - psychologist.secondAddress - ); - } - - return models.Psychologist.update( - { - address: psychologist.address, - addressAdditional: psychologist.addressAdditional, - secondAddress: psychologist.secondAddress, - secondAddressAdditional: psychologist.secondAddressAdditional, - cdsmsp: psychologist.cdsmsp, - coordinates: coordinates ? formatCoordinates(coordinates) : null, - secondAddressCoordinates: secondAddressCoordinates - ? formatCoordinates(secondAddressCoordinates) - : null, - displayEmail: psychologist.displayEmail, - email: psychologist.email, - firstName: psychologist.firstName, - languages: psychologist.languages, - lastName: psychologist.lastName, - phone: psychologist.phone, - displayPhone: psychologist.displayPhone, - public: psychologist.public, - teleconsultation: psychologist.teleconsultation, - visible: psychologist.visible, - website: psychologist.website, - }, - { where: { id } } - ); + return models.Psychologist.update(filterAllowedKeys(psychologist), { + where: { id }, + }); }; export const updateState = async (newStates: Partial[]) => {
+ Les champs suivis d’un astérisque ( *{" "} + ) sont obligatoires. +
+ {editableField.legend} +
{count}