diff --git a/packages/commons/src/events/CountryConfigQueryInput.ts b/packages/commons/src/events/CountryConfigQueryInput.ts index b7127785c9f..6f3006de117 100644 --- a/packages/commons/src/events/CountryConfigQueryInput.ts +++ b/packages/commons/src/events/CountryConfigQueryInput.ts @@ -18,7 +18,6 @@ import { Exact, ExactStatus, QueryInput, - Within, ExactUserType } from './EventIndex' @@ -39,16 +38,12 @@ export const SerializedQueryExpression = z createdAt: z.optional(DateCondition), updatedAt: z.optional(DateCondition), 'legalStatuses.REGISTERED.createdAt': z.optional(DateCondition), - 'legalStatuses.REGISTERED.createdAtLocation': z.optional( - z.union([Within, Exact]) - ), + 'legalStatuses.DECLARED.createdAtLocation': z.optional(SerializableWithin), + 'legalStatuses.REGISTERED.createdAtLocation': + z.optional(SerializableWithin), 'legalStatuses.REGISTERED.registrationNumber': z.optional(Exact), - createdAtLocation: z.optional( - z.union([SerializableWithin, SerializableExact]) - ), - updatedAtLocation: z.optional( - z.union([SerializableWithin, SerializableExact]) - ), + createdAtLocation: z.optional(SerializableWithin), + updatedAtLocation: z.optional(SerializableWithin), assignedTo: z.optional(SerializableExact), createdBy: z.optional(SerializableExact), createdByUserType: ExactUserType, diff --git a/packages/commons/src/events/EventIndex.ts b/packages/commons/src/events/EventIndex.ts index 37c2496abd6..c337e0d9615 100644 --- a/packages/commons/src/events/EventIndex.ts +++ b/packages/commons/src/events/EventIndex.ts @@ -175,15 +175,11 @@ export const QueryExpression = z createdAt: z.optional(DateCondition), updatedAt: z.optional(DateCondition), 'legalStatuses.REGISTERED.acceptedAt': z.optional(DateCondition), - 'legalStatuses.DECLARED.createdAtLocation': z.optional( - z.union([Within, Exact]) - ), - 'legalStatuses.REGISTERED.createdAtLocation': z.optional( - z.union([Within, Exact]) - ), + 'legalStatuses.DECLARED.createdAtLocation': z.optional(Within).nullable(), + 'legalStatuses.REGISTERED.createdAtLocation': z.optional(Within).nullable(), 'legalStatuses.REGISTERED.registrationNumber': z.optional(Exact), - createdAtLocation: z.optional(z.union([Within, Exact])), - updatedAtLocation: z.optional(z.union([Within, Exact])), + createdAtLocation: z.optional(Within), + updatedAtLocation: z.optional(Within), assignedTo: z.optional(Exact), createdByUserType: z.optional(ExactUserType), createdBy: z.optional(Exact), diff --git a/packages/commons/src/events/serializers/user/deserializer.ts b/packages/commons/src/events/serializers/user/deserializer.ts index 27b7a079f5d..86a18d5a6e5 100644 --- a/packages/commons/src/events/serializers/user/deserializer.ts +++ b/packages/commons/src/events/serializers/user/deserializer.ts @@ -44,11 +44,15 @@ import { SerializedUserField } from './serializer' */ function userDeserializer( serializedUserField: SerializedUserField | string, - user: User + user: User, + fallback: string = '' ): string { if (typeof serializedUserField === 'string') { return serializedUserField } + if (typeof serializedUserField !== 'object') { + return fallback + } if ( serializedUserField.$userField === 'name' || serializedUserField.$userField === 'signature' || @@ -58,7 +62,7 @@ function userDeserializer( `Deserializer for ${serializedUserField.$userField} is not implemented yet` ) } - return user[serializedUserField.$userField] + return user[serializedUserField.$userField] ?? fallback } function deserializeQueryExpression( @@ -80,34 +84,32 @@ function deserializeQueryExpression( ...expression.updatedBy, term: userDeserializer(expression.updatedBy.term, user) }, - createdAtLocation: - expression.createdAtLocation && - (expression.createdAtLocation.type === 'within' - ? { - ...expression.createdAtLocation, - location: userDeserializer( - expression.createdAtLocation.location, - user - ) - } - : { - ...expression.createdAtLocation, - term: userDeserializer(expression.createdAtLocation.term, user) - }), - updatedAtLocation: - expression.updatedAtLocation && - (expression.updatedAtLocation.type === 'within' - ? { - ...expression.updatedAtLocation, - location: userDeserializer( - expression.updatedAtLocation.location, - user - ) - } - : { - ...expression.updatedAtLocation, - term: userDeserializer(expression.updatedAtLocation.term, user) - }) + createdAtLocation: expression.createdAtLocation && { + ...expression.createdAtLocation, + location: userDeserializer(expression.createdAtLocation.location, user) + }, + updatedAtLocation: expression.updatedAtLocation && { + ...expression.updatedAtLocation, + location: userDeserializer(expression.updatedAtLocation.location, user) + }, + ['legalStatuses.DECLARED.createdAtLocation']: expression[ + 'legalStatuses.DECLARED.createdAtLocation' + ] && { + ...expression['legalStatuses.DECLARED.createdAtLocation'], + location: userDeserializer( + expression['legalStatuses.DECLARED.createdAtLocation'].location, + user + ) + }, + ['legalStatuses.REGISTERED.createdAtLocation']: expression[ + 'legalStatuses.REGISTERED.createdAtLocation' + ] && { + ...expression['legalStatuses.REGISTERED.createdAtLocation'], + location: userDeserializer( + expression['legalStatuses.REGISTERED.createdAtLocation'].location, + user + ) + } } } diff --git a/packages/commons/src/events/serializers/user/serializer.ts b/packages/commons/src/events/serializers/user/serializer.ts index ee959b7e982..7a14c711306 100644 --- a/packages/commons/src/events/serializers/user/serializer.ts +++ b/packages/commons/src/events/serializers/user/serializer.ts @@ -19,7 +19,8 @@ export const SerializedUserField = z.object({ 'role', 'signature', 'avatar', - 'primaryOfficeId' + 'primaryOfficeId', + 'administrativeAreaId' ]), $location: z.string().optional() }) diff --git a/packages/events/src/router/locations/index.ts b/packages/events/src/router/locations/index.ts index c61dbaca929..c691dc279cc 100644 --- a/packages/events/src/router/locations/index.ts +++ b/packages/events/src/router/locations/index.ts @@ -13,6 +13,7 @@ import * as z from 'zod/v4' import { Location, LocationType, SCOPES, UUID } from '@opencrvs/commons' import { router, userAndSystemProcedure } from '@events/router/trpc' import { + getChildLocations, getLocations, setLocations, syncLocations @@ -56,6 +57,14 @@ export const locationRouter = router({ locationType: input?.locationType }) ), + getChild: userAndSystemProcedure + .input( + z.object({ + parentId: UUID + }) + ) + .output(z.array(Location)) + .query(async ({ input }) => getChildLocations(input.parentId)), set: userAndSystemProcedure .use( requiresAnyOfScopes([SCOPES.USER_DATA_SEEDING, SCOPES.CONFIG_UPDATE_ALL]) diff --git a/packages/events/src/service/indexing/indexing.test.ts b/packages/events/src/service/indexing/indexing.test.ts index e647f5e63c4..0dd231da31d 100644 --- a/packages/events/src/service/indexing/indexing.test.ts +++ b/packages/events/src/service/indexing/indexing.test.ts @@ -225,13 +225,13 @@ const rangeRegisteredAtPayload: QueryType = { ] } -const exactRegisteredAtLocationPayload: QueryType = { +const withinRegisteredAtLocationPayload: QueryType = { type: 'and', clauses: [ { 'legalStatuses.REGISTERED.createdAtLocation': { - type: 'exact', - term: RANDOM_UUID + type: 'within', + location: RANDOM_UUID }, eventType: TENNIS_CLUB_MEMBERSHIP } @@ -256,7 +256,7 @@ const fullAndPayload: QueryType = { trackingId: { type: 'exact', term: 'ABC123' }, createdAt: { type: 'range', gte: '2024-01-01', lte: '2024-12-31' }, updatedAt: { type: 'exact', term: '2024-06-01' }, - createdAtLocation: { type: 'exact', term: RANDOM_UUID }, + createdAtLocation: { type: 'within', location: RANDOM_UUID }, updatedAtLocation: { type: 'within', location: RANDOM_UUID @@ -367,9 +367,9 @@ describe('test buildElasticQueryFromSearchPayload', () => { }) }) - test('builds query with exact legalStatuses.REGISTERED.createdAtLocation', async () => { + test('builds query with legalStatuses.REGISTERED.createdAtLocation', async () => { const result = await buildElasticQueryFromSearchPayload( - exactRegisteredAtLocationPayload, + withinRegisteredAtLocationPayload, [tennisClubMembershipEvent] ) expect(result).toEqual({ @@ -438,18 +438,7 @@ describe('test buildElasticQueryFromSearchPayload', () => { }, { term: { updatedAt: '2024-06-01' } }, { term: { createdAtLocation: RANDOM_UUID } }, - { - bool: { - minimum_should_match: 1, - should: [ - { - term: { - updatedAtLocation: RANDOM_UUID - } - } - ] - } - }, + { term: { updatedAtLocation: RANDOM_UUID } }, { match: { 'declaration.applicant____name.__fullname': 'John Doe' diff --git a/packages/events/src/service/indexing/indexing.ts b/packages/events/src/service/indexing/indexing.ts index 56500a159c0..b21deb03b6e 100644 --- a/packages/events/src/service/indexing/indexing.ts +++ b/packages/events/src/service/indexing/indexing.ts @@ -499,7 +499,7 @@ export async function findRecordsByQuery({ const events = response.hits.hits .map((hit) => hit._source) .filter((event): event is EncodedEventIndex => event !== undefined) - .map((eventIndex) => { + .map((eventIndex: EncodedEventIndex) => { const eventConfig = getEventConfigById(eventConfigs, eventIndex.type) const decodedEventIndex = decodeEventIndex(eventConfig, eventIndex) const eventIndexWithoutLocationHierarchy = diff --git a/packages/events/src/service/indexing/query.ts b/packages/events/src/service/indexing/query.ts index 51cb50c8881..a0ecde7240c 100644 --- a/packages/events/src/service/indexing/query.ts +++ b/packages/events/src/service/indexing/query.ts @@ -21,8 +21,7 @@ import { SearchScopeAccessLevels, timePeriodToDateRange } from '@opencrvs/commons/events' -import { getOrThrow, ResolvedRecordScopeV2, UUID } from '@opencrvs/commons' -import { getChildLocations } from '../locations/locations' +import { getOrThrow, ResolvedRecordScopeV2 } from '@opencrvs/commons' import { encodeFieldId, generateQueryForAddressField, @@ -156,10 +155,7 @@ function typedKeys(obj: T): (keyof T)[] { return Object.keys(obj) as (keyof T)[] } -async function buildClause( - clause: QueryExpression, - eventConfigs: EventConfig[] -) { +function buildClause(clause: QueryExpression, eventConfigs: EventConfig[]) { const must: estypes.QueryDslQueryContainer[] = [] for (const key of typedKeys(clause)) { @@ -216,22 +212,14 @@ async function buildClause( case 'legalStatuses.REGISTERED.createdAtLocation': { const value = clause[key] - if (value.type === 'exact') { - must.push({ term: { [key]: value.term } }) - } else { - const childLocations = await getChildLocations(value.location as UUID) - const locationIds = [ - value.location, - ...childLocations.map((location) => location.id) - ] - - must.push({ - bool: { - should: locationIds.map((id) => ({ term: { [key]: id } })), - minimum_should_match: 1 - } - }) + /** + * If value.location === '', it represents the root location, + * so no additional restriction needs to be applied. + */ + if (value.location !== '') { + must.push({ term: { [key]: value.location } }) } + break } @@ -282,7 +270,7 @@ async function buildClauseOrQuery( // eslint-disable-next-line @typescript-eslint/no-use-before-define return buildElasticQueryFromSearchPayload(clause, eventConfigs) } else { - const must = await buildClause(clause, eventConfigs) + const must = buildClause(clause, eventConfigs) return { bool: { must,