From c718599d2246da5174f32c54dade2d9f9b0efbfc Mon Sep 17 00:00:00 2001 From: freddieptf Date: Wed, 30 Oct 2024 00:23:59 +0300 Subject: [PATCH 1/2] error if user tries to create a place with an in-use name --- README.md | 1 + src/config/chis-ke/config.json | 1 + src/config/index.ts | 1 + src/lib/cht-api.ts | 15 +++++++++++++++ src/services/upload.new.ts | 6 ++++++ test/services/upload-manager.spec.ts | 12 ++++++++++++ 6 files changed, 36 insertions(+) diff --git a/README.md b/README.md index 30e905f0..82c1fb7a 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ Property | Type | Description `contact_types.contact_properties` | Array | Defines the attributes which are collected and set on the user's primary contact doc. See [ConfigProperty](#ConfigProperty). `contact_types.deactivate_users_on_replace` | boolean | Controls what should happen to the defunct contact and user documents when a user is replaced. When `false`, the contact and user account will be deleted. When `true`, the contact will be unaltered and the user account will be assigned the role `deactivated`. This allows for account restoration. `contact_types.hint` | string | Provide a brief hint or description to clarify the expected input for the property. +`contact_types.dedup_property` | string | The place's `property_name` used to prevent creation of duplicates under a certain parent. `logoBase64` | Image in base64 | Logo image for your project #### ConfigProperty diff --git a/src/config/chis-ke/config.json b/src/config/chis-ke/config.json index e528a4d4..a8b37611 100644 --- a/src/config/chis-ke/config.json +++ b/src/config/chis-ke/config.json @@ -215,6 +215,7 @@ "user_role": ["community_health_assistant"], "username_from_place": true, "deactivate_users_on_replace": false, + "dedup_property": "code", "hierarchy": [ { "friendly_name": "Sub County", diff --git a/src/config/index.ts b/src/config/index.ts index 69b32141..ffdeb33a 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -26,6 +26,7 @@ export type ContactType = { contact_properties: ContactProperty[]; deactivate_users_on_replace: boolean; hint?: string; + dedup_property?: string; }; export type HierarchyConstraint = { diff --git a/src/lib/cht-api.ts b/src/lib/cht-api.ts index eb183b8f..abfb7b06 100644 --- a/src/lib/cht-api.ts +++ b/src/lib/cht-api.ts @@ -173,6 +173,21 @@ export class ChtApi { return { parent, sibling }; }; + contactByType = async (placeType: string, property: string, value: string): Promise<{ name: String; parent: String }[]> => { + const url = `medic/_design/medic-client/_view/contacts_by_type_freetext`; + const params = { + include_docs: true, + key: JSON.stringify([placeType, property + ':' + value.toLowerCase()]) + }; + const resp = await this.axiosInstance.get(url, { params }); + return resp.data.rows.map((row: any) => { + return { + name: row.doc.name, + parent: row.doc.parent?._id + }; + }); + }; + getPlacesWithType = async (placeType: string) : Promise => { const url = `medic/_design/medic-client/_view/contacts_by_type_freetext`; diff --git a/src/services/upload.new.ts b/src/services/upload.new.ts index 95b60f0a..7be393c2 100644 --- a/src/services/upload.new.ts +++ b/src/services/upload.new.ts @@ -14,6 +14,12 @@ export class UploadNewPlace implements Uploader { }; handlePlacePayload = async (place: Place, payload: PlacePayload): Promise => { + if (place.type.dedup_property) { + const contacts = await this.chtApi.contactByType(payload.contact_type, place.type.dedup_property, payload[place.type.dedup_property]); + if (contacts.some(c => c.parent === payload.parent)) { + throw new Error(place.type.friendly + ` with ${place.type.dedup_property} "${payload[place.type.dedup_property]}" already exists`); + } + } return await this.chtApi.createPlace(payload); }; diff --git a/test/services/upload-manager.spec.ts b/test/services/upload-manager.spec.ts index f9a831d7..872d814b 100644 --- a/test/services/upload-manager.spec.ts +++ b/test/services/upload-manager.spec.ts @@ -77,6 +77,17 @@ describe('services/upload-manager.ts', () => { expect(chtApi.createUser.callCount).to.eq(placeCount); expect(places.find(p => !p.isCreated)).to.be.undefined; }); + + it('err when creating place with duplicate name in parent place', async () => { + const { fakeFormData, contactType, sessionCache, chtApi } = await createMocks(); + contactType.dedup_property = 'name'; + const place = await PlaceFactory.createOne(fakeFormData, contactType, sessionCache, chtApi); + chtApi.contactByType.resolves([{ name: '', parent: place.asChtPayload('test').parent }]); + const uploadManager = new UploadManager(); + await uploadManager.doUpload([place], chtApi); + expect(chtApi.contactByType.calledOnce).to.be.true; + expect(place.isCreated).to.be.false; + }); it('required attributes can be inherited during replacement', async () => { const { remotePlace, sessionCache, contactType, fakeFormData, chtApi } = await createMocks(); @@ -392,6 +403,7 @@ async function createMocks() { updateContactParent: sinon.stub().resolves('created-contact-id'), createUser: sinon.stub().resolves(), + contactByType: sinon.stub().resolves([]), getParentAndSibling: sinon.stub().resolves({ parent: {}, sibling: {} }), createContact: sinon.stub().resolves('replacement-contact-id'), updatePlace: sinon.stub().resolves({ From 4458fd09cc528a28037d9dcd37ed881d7e9ad6a8 Mon Sep 17 00:00:00 2001 From: freddieptf Date: Tue, 5 Nov 2024 20:43:04 +0300 Subject: [PATCH 2/2] dedup_property for chp areas is name --- src/config/chis-ke/config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config/chis-ke/config.json b/src/config/chis-ke/config.json index a8b37611..5b543404 100644 --- a/src/config/chis-ke/config.json +++ b/src/config/chis-ke/config.json @@ -288,6 +288,7 @@ "user_role": ["community_health_volunteer"], "username_from_place": false, "deactivate_users_on_replace": false, + "dedup_property": "name", "hierarchy": [ { "friendly_name": "Sub County",