From fc728080ac801143f462c0a1a01613ac3c2e4c75 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Tue, 21 Apr 2026 14:43:11 +0200 Subject: [PATCH 1/9] fix: recalculate affiliations on update member orgs Signed-off-by: Umberto Sgueglia --- .../common_services/src/services/common.member.service.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services/libs/common_services/src/services/common.member.service.ts b/services/libs/common_services/src/services/common.member.service.ts index 70c70e0647..1eb8bed8eb 100644 --- a/services/libs/common_services/src/services/common.member.service.ts +++ b/services/libs/common_services/src/services/common.member.service.ts @@ -84,6 +84,8 @@ export class CommonMemberService extends LoggerBase { return moment(v).toISOString() } + const affectedOrgIds = new Set() + await captureApiChange( options, memberEditOrganizationsAction(memberId, async (captureOldState, captureNewState) => { @@ -108,6 +110,7 @@ export class CommonMemberService extends LoggerBase { for (const item of toDelete) { await deleteMemberOrganizations(this.qx, memberId, [item.id]) ;(item as any).delete = true + affectedOrgIds.add(item.organizationId) } } @@ -157,12 +160,17 @@ export class CommonMemberService extends LoggerBase { await addOrgsToSegments(this.qx, segmentIds, [org.id]) newOrgs.push(newOrg) + affectedOrgIds.add(org.id) } } captureNewState(newOrgs) }), ) + + if (affectedOrgIds.size > 0) { + await this.startAffiliationRecalculation(memberId, [...affectedOrgIds], true) + } } public async findAffiliation( From 88b2969179476196d2cc76192317565318f80e14 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Tue, 21 Apr 2026 14:57:20 +0200 Subject: [PATCH 2/9] feat: add optional flag to trigger affiliation workflow Signed-off-by: Umberto Sgueglia --- backend/src/database/repositories/memberRepository.ts | 1 + .../libs/common_services/src/services/common.member.service.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/database/repositories/memberRepository.ts b/backend/src/database/repositories/memberRepository.ts index acaa9b1af1..8c6dff39e2 100644 --- a/backend/src/database/repositories/memberRepository.ts +++ b/backend/src/database/repositories/memberRepository.ts @@ -876,6 +876,7 @@ class MemberRepository { data.organizationsReplace, options.currentSegments.map((s) => s.id), options, + false, // MemberService.update triggers startAffiliationRecalculation after commit ) if (data.noMerge) { diff --git a/services/libs/common_services/src/services/common.member.service.ts b/services/libs/common_services/src/services/common.member.service.ts index 1eb8bed8eb..71082fdcc5 100644 --- a/services/libs/common_services/src/services/common.member.service.ts +++ b/services/libs/common_services/src/services/common.member.service.ts @@ -75,6 +75,7 @@ export class CommonMemberService extends LoggerBase { replace: any, segmentIds: string[], options?: any, + triggerRecalc = true, ): Promise { if (!organizations) { return @@ -168,7 +169,7 @@ export class CommonMemberService extends LoggerBase { }), ) - if (affectedOrgIds.size > 0) { + if (triggerRecalc && affectedOrgIds.size > 0) { await this.startAffiliationRecalculation(memberId, [...affectedOrgIds], true) } } From 277b0474d68790e85209343b0b2021568dcec04d Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Tue, 21 Apr 2026 17:18:37 +0200 Subject: [PATCH 3/9] feat: adding logs Signed-off-by: Umberto Sgueglia --- .../src/services/common.member.service.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/services/libs/common_services/src/services/common.member.service.ts b/services/libs/common_services/src/services/common.member.service.ts index 71082fdcc5..7bc36fe0f5 100644 --- a/services/libs/common_services/src/services/common.member.service.ts +++ b/services/libs/common_services/src/services/common.member.service.ts @@ -170,7 +170,16 @@ export class CommonMemberService extends LoggerBase { ) if (triggerRecalc && affectedOrgIds.size > 0) { + this.log.info( + { memberId, affectedOrgIds: [...affectedOrgIds] }, + 'Member organizations updated — triggering affiliation recalculation', + ) await this.startAffiliationRecalculation(memberId, [...affectedOrgIds], true) + } else { + this.log.info( + { memberId, affectedOrgIds: [...affectedOrgIds], triggerRecalc }, + 'Member organizations updated — skipping affiliation recalculation', + ) } } @@ -250,6 +259,7 @@ export class CommonMemberService extends LoggerBase { organizationIds: string[], syncToOpensearch = false, ): Promise { + this.log.info({ memberId, organizationIds, syncToOpensearch }, 'Starting affiliation recalculation workflow') await this.temporal.workflow.start('memberUpdate', { taskQueue: 'profiles', workflowId: `${TemporalWorkflowId.MEMBER_UPDATE}/${DEFAULT_TENANT_ID}/${memberId}`, From ee5d81ec189e16891cfb17259e1158ff5c6b2393 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Tue, 21 Apr 2026 17:21:54 +0200 Subject: [PATCH 4/9] fix: lint Signed-off-by: Umberto Sgueglia --- .../common_services/src/services/common.member.service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/libs/common_services/src/services/common.member.service.ts b/services/libs/common_services/src/services/common.member.service.ts index 7bc36fe0f5..42b5253bfa 100644 --- a/services/libs/common_services/src/services/common.member.service.ts +++ b/services/libs/common_services/src/services/common.member.service.ts @@ -259,7 +259,10 @@ export class CommonMemberService extends LoggerBase { organizationIds: string[], syncToOpensearch = false, ): Promise { - this.log.info({ memberId, organizationIds, syncToOpensearch }, 'Starting affiliation recalculation workflow') + this.log.info( + { memberId, organizationIds, syncToOpensearch }, + 'Starting affiliation recalculation workflow', + ) await this.temporal.workflow.start('memberUpdate', { taskQueue: 'profiles', workflowId: `${TemporalWorkflowId.MEMBER_UPDATE}/${DEFAULT_TENANT_ID}/${memberId}`, From 39dc8459109350f775f273286eb6f36b2a25aa74 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Wed, 22 Apr 2026 10:59:09 +0200 Subject: [PATCH 5/9] fix: review comments Signed-off-by: Umberto Sgueglia --- .../database/repositories/memberRepository.ts | 1 - backend/src/services/memberService.ts | 28 ++++++++++++++++++- .../src/services/common.member.service.ts | 26 +++-------------- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/backend/src/database/repositories/memberRepository.ts b/backend/src/database/repositories/memberRepository.ts index 8c6dff39e2..acaa9b1af1 100644 --- a/backend/src/database/repositories/memberRepository.ts +++ b/backend/src/database/repositories/memberRepository.ts @@ -876,7 +876,6 @@ class MemberRepository { data.organizationsReplace, options.currentSegments.map((s) => s.id), options, - false, // MemberService.update triggers startAffiliationRecalculation after commit ) if (data.noMerge) { diff --git a/backend/src/services/memberService.ts b/backend/src/services/memberService.ts index da6b57463d..0585bd3685 100644 --- a/backend/src/services/memberService.ts +++ b/backend/src/services/memberService.ts @@ -20,6 +20,7 @@ import { insertMemberSegmentAggregates, queryMembersAdvanced, } from '@crowd/data-access-layer/src/members' +import { fetchMemberOrganizations } from '@crowd/data-access-layer/src/members/organizations' import { QueryExecutor, optionsQx } from '@crowd/data-access-layer/src/queryExecutor' import { fetchManySegments } from '@crowd/data-access-layer/src/segments' import { LoggerBase } from '@crowd/logging' @@ -276,6 +277,12 @@ export default class MemberService extends LoggerBase { data.displayName = data.username[data.platform].username } + const existingOrgIds = existing && data.organizations + ? (await fetchMemberOrganizations(optionsQx(this.options), existing.id)).map( + (o) => o.organizationId, + ) + : [] + const transaction = await SequelizeRepository.createTransaction(this.options) try { @@ -505,6 +512,17 @@ export default class MemberService extends LoggerBase { await SequelizeRepository.commitTransaction(transaction) + if (data.organizations) { + const commonMemberService = new CommonMemberService( + optionsQx(this.options), + this.options.temporal, + this.options.log, + ) + const newOrgIds = data.organizations.map((o) => o.id) + const allAffectedOrgIds = [...new Set([...existingOrgIds, ...newOrgIds])] + await commonMemberService.startAffiliationRecalculation(record.id, allAffectedOrgIds, false) + } + if (syncToOpensearch) { await searchSyncService.triggerMemberSync(record.id) } @@ -800,6 +818,12 @@ export default class MemberService extends LoggerBase { ) { let transaction try { + const existingOrgIds = data.organizations + ? (await fetchMemberOrganizations(optionsQx(this.options), id)).map( + (o) => o.organizationId, + ) + : [] + const repoOptions = await SequelizeRepository.createTransactionalRepositoryOptions( this.options, ) @@ -824,9 +848,11 @@ export default class MemberService extends LoggerBase { this.options.temporal, this.options.log, ) + const newOrgIds = (data.organizations || []).map((o) => o.id) + const allAffectedOrgIds = [...new Set([...existingOrgIds, ...newOrgIds])] await commonMemberService.startAffiliationRecalculation( id, - (data.organizations || []).map((o) => o.id), + allAffectedOrgIds, syncToOpensearch, ) diff --git a/services/libs/common_services/src/services/common.member.service.ts b/services/libs/common_services/src/services/common.member.service.ts index 42b5253bfa..6b1020c0ca 100644 --- a/services/libs/common_services/src/services/common.member.service.ts +++ b/services/libs/common_services/src/services/common.member.service.ts @@ -75,7 +75,6 @@ export class CommonMemberService extends LoggerBase { replace: any, segmentIds: string[], options?: any, - triggerRecalc = true, ): Promise { if (!organizations) { return @@ -85,8 +84,6 @@ export class CommonMemberService extends LoggerBase { return moment(v).toISOString() } - const affectedOrgIds = new Set() - await captureApiChange( options, memberEditOrganizationsAction(memberId, async (captureOldState, captureNewState) => { @@ -111,7 +108,6 @@ export class CommonMemberService extends LoggerBase { for (const item of toDelete) { await deleteMemberOrganizations(this.qx, memberId, [item.id]) ;(item as any).delete = true - affectedOrgIds.add(item.organizationId) } } @@ -122,10 +118,10 @@ export class CommonMemberService extends LoggerBase { if ( !originalOrgs.some( (w) => - w.organizationId === item.id && - w.title === (item.title || null) && - w.dateStart === (item.startDate || null) && - w.dateEnd === (item.endDate || null), + w.organizationId === org.id && + w.title === (org.title || null) && + w.dateStart === (org.startDate || null) && + w.dateEnd === (org.endDate || null), ) ) { const newOrg = { @@ -161,26 +157,12 @@ export class CommonMemberService extends LoggerBase { await addOrgsToSegments(this.qx, segmentIds, [org.id]) newOrgs.push(newOrg) - affectedOrgIds.add(org.id) } } captureNewState(newOrgs) }), ) - - if (triggerRecalc && affectedOrgIds.size > 0) { - this.log.info( - { memberId, affectedOrgIds: [...affectedOrgIds] }, - 'Member organizations updated — triggering affiliation recalculation', - ) - await this.startAffiliationRecalculation(memberId, [...affectedOrgIds], true) - } else { - this.log.info( - { memberId, affectedOrgIds: [...affectedOrgIds], triggerRecalc }, - 'Member organizations updated — skipping affiliation recalculation', - ) - } } public async findAffiliation( From 2294c0d7df4809819ba1e425761994b5d4838698 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Wed, 22 Apr 2026 11:01:20 +0200 Subject: [PATCH 6/9] fix: lint Signed-off-by: Umberto Sgueglia --- backend/src/services/memberService.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/backend/src/services/memberService.ts b/backend/src/services/memberService.ts index 0585bd3685..43217f3b11 100644 --- a/backend/src/services/memberService.ts +++ b/backend/src/services/memberService.ts @@ -277,11 +277,12 @@ export default class MemberService extends LoggerBase { data.displayName = data.username[data.platform].username } - const existingOrgIds = existing && data.organizations - ? (await fetchMemberOrganizations(optionsQx(this.options), existing.id)).map( - (o) => o.organizationId, - ) - : [] + const existingOrgIds = + existing && data.organizations + ? (await fetchMemberOrganizations(optionsQx(this.options), existing.id)).map( + (o) => o.organizationId, + ) + : [] const transaction = await SequelizeRepository.createTransaction(this.options) @@ -819,9 +820,7 @@ export default class MemberService extends LoggerBase { let transaction try { const existingOrgIds = data.organizations - ? (await fetchMemberOrganizations(optionsQx(this.options), id)).map( - (o) => o.organizationId, - ) + ? (await fetchMemberOrganizations(optionsQx(this.options), id)).map((o) => o.organizationId) : [] const repoOptions = await SequelizeRepository.createTransactionalRepositoryOptions( From 3cc079737aaf0e4f3008301e097b3a0723af6903 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Wed, 22 Apr 2026 13:43:03 +0200 Subject: [PATCH 7/9] fix: review Signed-off-by: Umberto Sgueglia --- backend/src/services/memberService.ts | 20 ++++++++++++------- .../src/services/common.member.service.ts | 9 ++++++--- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/backend/src/services/memberService.ts b/backend/src/services/memberService.ts index 43217f3b11..6818640b43 100644 --- a/backend/src/services/memberService.ts +++ b/backend/src/services/memberService.ts @@ -277,12 +277,7 @@ export default class MemberService extends LoggerBase { data.displayName = data.username[data.platform].username } - const existingOrgIds = - existing && data.organizations - ? (await fetchMemberOrganizations(optionsQx(this.options), existing.id)).map( - (o) => o.organizationId, - ) - : [] + let existingOrgIds: string[] = [] const transaction = await SequelizeRepository.createTransaction(this.options) @@ -444,6 +439,13 @@ export default class MemberService extends LoggerBase { let record if (existing) { const { id } = existing + + if (data.organizations) { + existingOrgIds = (await fetchMemberOrganizations(optionsQx(this.options), id)).map( + (o) => o.organizationId, + ) + } + delete existing.id const toUpdate = CommonMemberService.membersMerge(existing, data) @@ -521,7 +523,11 @@ export default class MemberService extends LoggerBase { ) const newOrgIds = data.organizations.map((o) => o.id) const allAffectedOrgIds = [...new Set([...existingOrgIds, ...newOrgIds])] - await commonMemberService.startAffiliationRecalculation(record.id, allAffectedOrgIds, false) + await commonMemberService.startAffiliationRecalculation( + record.id, + allAffectedOrgIds, + syncToOpensearch, + ) } if (syncToOpensearch) { diff --git a/services/libs/common_services/src/services/common.member.service.ts b/services/libs/common_services/src/services/common.member.service.ts index 6b1020c0ca..769edda25c 100644 --- a/services/libs/common_services/src/services/common.member.service.ts +++ b/services/libs/common_services/src/services/common.member.service.ts @@ -84,6 +84,10 @@ export class CommonMemberService extends LoggerBase { return moment(v).toISOString() } + const normalizedOrgs = organizations.map((item) => + typeof item === 'string' ? { id: item } : item, + ) + await captureApiChange( options, memberEditOrganizationsAction(memberId, async (captureOldState, captureNewState) => { @@ -95,7 +99,7 @@ export class CommonMemberService extends LoggerBase { if (replace) { const toDelete = originalOrgs.filter( (originalOrg: any) => - !organizations.find( + !normalizedOrgs.find( (newOrg) => originalOrg.organizationId === newOrg.id && (originalOrg.title === (newOrg.title || null) || @@ -111,8 +115,7 @@ export class CommonMemberService extends LoggerBase { } } - for (const item of organizations) { - const org = typeof item === 'string' ? { id: item } : item + for (const org of normalizedOrgs) { // we don't need to touch exactly same existing work experiences if ( From eb78d2bb383e52959dc66ffff9a8333645f9854f Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Wed, 22 Apr 2026 13:47:04 +0200 Subject: [PATCH 8/9] fix: lint Signed-off-by: Umberto Sgueglia --- .../libs/common_services/src/services/common.member.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/services/libs/common_services/src/services/common.member.service.ts b/services/libs/common_services/src/services/common.member.service.ts index 769edda25c..22177d9d13 100644 --- a/services/libs/common_services/src/services/common.member.service.ts +++ b/services/libs/common_services/src/services/common.member.service.ts @@ -116,7 +116,6 @@ export class CommonMemberService extends LoggerBase { } for (const org of normalizedOrgs) { - // we don't need to touch exactly same existing work experiences if ( !originalOrgs.some( From b65c266e017d465173caa12660b3b5f9c94a8bca Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Wed, 22 Apr 2026 14:05:35 +0200 Subject: [PATCH 9/9] fix: add recalculation to data_sink_worker Signed-off-by: Umberto Sgueglia --- .../src/service/member.service.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/services/apps/data_sink_worker/src/service/member.service.ts b/services/apps/data_sink_worker/src/service/member.service.ts index 8aa78b1cac..ec0849465e 100644 --- a/services/apps/data_sink_worker/src/service/member.service.ts +++ b/services/apps/data_sink_worker/src/service/member.service.ts @@ -481,6 +481,17 @@ export default class MemberService extends LoggerBase { this.log, 'memberService -> create -> addToMember', ) + + const commonMemberService = new CommonMemberService( + dbStoreQx(this.store), + this.temporal, + this.log, + ) + await commonMemberService.startAffiliationRecalculation( + id, + orgsToAdd.map((o) => o.id), + false, + ) } } @@ -717,6 +728,17 @@ export default class MemberService extends LoggerBase { this.log, 'memberService -> update -> addToMember', ) + + const commonMemberService = new CommonMemberService( + dbStoreQx(this.store), + this.temporal, + this.log, + ) + await commonMemberService.startAffiliationRecalculation( + id, + orgsToAdd.map((o) => o.id), + false, + ) } } } catch (err) {