From 4e9acba2084784e277348e1f7e745101bbc50c2d Mon Sep 17 00:00:00 2001
From: jakubstec <jakub.stec@swmansion.com>
Date: Wed, 22 Apr 2026 10:41:43 +0200
Subject: [PATCH 1/4] feat: add tests in IOUTest.ts for createSplitsAndOnyxData
 function

---
 tests/actions/IOUTest.ts | 275 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 274 insertions(+), 1 deletion(-)

diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts
index 67a7a95d433c..fc793d93a048 100644
--- a/tests/actions/IOUTest.ts
+++ b/tests/actions/IOUTest.ts
@@ -11,6 +11,7 @@ import {clearAllRelatedReportActionErrors} from '@libs/actions/ClearReportAction
 import {
     calculateDiffAmount,
     createDistanceRequest,
+    createSplitsAndOnyxData,
     handleNavigateAfterExpenseCreate,
     initMoneyRequest,
     removeMoneyRequestOdometerImage,
@@ -61,7 +62,7 @@ import type {Participant} from '@src/types/onyx/Report';
 import type ReportAction from '@src/types/onyx/ReportAction';
 import type {ReportActions} from '@src/types/onyx/ReportAction';
 import type Transaction from '@src/types/onyx/Transaction';
-import type {WaypointCollection} from '@src/types/onyx/Transaction';
+import type {SplitShares, WaypointCollection} from '@src/types/onyx/Transaction';
 import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
 import SafeString from '@src/utils/SafeString';
@@ -7578,4 +7579,276 @@ describe('actions/IOU', () => {
             expect(updatedTransaction?.comment?.odometerEndImage).toBeUndefined();
         });
     });
+
+    describe('createSplitsAndOnyxData', () => {
+        const mockPersonalDetails: PersonalDetailsList = {
+            [RORY_ACCOUNT_ID]: {accountID: RORY_ACCOUNT_ID, login: RORY_EMAIL, displayName: 'Rory'},
+            [CARLOS_ACCOUNT_ID]: {accountID: CARLOS_ACCOUNT_ID, login: CARLOS_EMAIL, displayName: 'Carlos'},
+            [JULES_ACCOUNT_ID]: {accountID: JULES_ACCOUNT_ID, login: JULES_EMAIL, displayName: 'Jules'},
+            [VIT_ACCOUNT_ID]: {accountID: VIT_ACCOUNT_ID, login: VIT_EMAIL, displayName: 'Vit'},
+        };
+
+        const baseTransactionParams = {
+            amount: 400,
+            currency: CONST.CURRENCY.USD,
+            created: '2024-01-01',
+            merchant: 'Test Merchant',
+            comment: 'Test split',
+            tag: '',
+            category: '',
+            taxCode: '',
+            taxAmount: 0,
+            splitShares: {} as SplitShares,
+        };
+
+        const buildParams = (
+            overrides: {
+                participants?: IOUParticipant[];
+                existingSplitChatReportID?: string;
+                transactionParamOverrides?: Partial<typeof baseTransactionParams>;
+            } = {},
+        ) => ({
+            participants: overrides.participants ?? [{accountID: CARLOS_ACCOUNT_ID, login: CARLOS_EMAIL}],
+            currentUserLogin: RORY_EMAIL,
+            currentUserAccountID: RORY_ACCOUNT_ID,
+            existingSplitChatReportID: overrides.existingSplitChatReportID ?? '',
+            transactionParams: {
+                ...baseTransactionParams,
+                ...overrides.transactionParamOverrides,
+            },
+            policyRecentlyUsedCategories: undefined,
+            policyRecentlyUsedTags: undefined,
+            isASAPSubmitBetaEnabled: false,
+            transactionViolations: {},
+            quickAction: undefined,
+            policyRecentlyUsedCurrencies: [],
+            betas: [CONST.BETAS.ALL],
+            personalDetails: mockPersonalDetails,
+        });
+
+        it('returns valid splitData with chatReportID, transactionID, and reportActionID', () => {
+            // Given a basic 1:1 split between the current user and one participant
+
+            // When creating splits and Onyx data
+            const result = createSplitsAndOnyxData(buildParams());
+
+            // Then splitData should contain all required identifiers
+            expect(result.splitData.chatReportID).toBeTruthy();
+            expect(result.splitData.transactionID).toBeTruthy();
+            expect(result.splitData.reportActionID).toBeTruthy();
+        });
+
+        it('includes createdReportActionID in splitData for a new chat', () => {
+            // Given no existing split chat report
+
+            // When creating splits and Onyx data
+            const result = createSplitsAndOnyxData(buildParams());
+
+            // Then splitData should include a createdReportActionID for the new chat
+            expect(result.splitData.createdReportActionID).toBeTruthy();
+        });
+
+        it('omits createdReportActionID from splitData when using an existing chat', async () => {
+            // Given an existing chat report already in Onyx
+            const existingReportID = rand64();
+            await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${existingReportID}`, {
+                reportID: existingReportID,
+                type: CONST.REPORT.TYPE.CHAT,
+                participants: {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT},
+            });
+            await waitForBatchedUpdates();
+
+            // When creating splits referencing that existing chat
+            const result = createSplitsAndOnyxData(buildParams({existingSplitChatReportID: existingReportID}));
+
+            // Then splitData should not include a createdReportActionID
+            expect(result.splitData.createdReportActionID).toBeUndefined();
+        });
+
+        it('splits amount equally among all participants when no splitShares are provided', () => {
+            // Given a $400 expense split between the current user and 3 other participants
+            const amount = 400;
+
+            // When creating splits without custom splitShares
+            const result = createSplitsAndOnyxData(
+                buildParams({
+                    participants: [
+                        {accountID: CARLOS_ACCOUNT_ID, login: CARLOS_EMAIL},
+                        {accountID: JULES_ACCOUNT_ID, login: JULES_EMAIL},
+                        {accountID: VIT_ACCOUNT_ID, login: VIT_EMAIL},
+                    ],
+                    transactionParamOverrides: {amount},
+                }),
+            );
+
+            // Then each of the 4 splits (current user + 3 others) should be $100
+            expect(result.splits).toHaveLength(4);
+            for (const split of result.splits) {
+                expect(split.amount).toBe(amount / 4);
+            }
+        });
+
+        it('respects custom splitShares amounts when provided', () => {
+            // Given a $200 expense with custom split: current user pays $150, Carlos pays $50
+            const splitShares: SplitShares = {
+                [RORY_ACCOUNT_ID]: {amount: 150},
+                [CARLOS_ACCOUNT_ID]: {amount: 50},
+            };
+
+            // When creating splits with those custom splitShares
+            const result = createSplitsAndOnyxData(
+                buildParams({
+                    transactionParamOverrides: {amount: 200, splitShares},
+                }),
+            );
+
+            // Then each participant's split should reflect the custom amounts
+            const currentUserSplit = result.splits.find((s) => s.accountID === RORY_ACCOUNT_ID);
+            const carlosSplit = result.splits.find((s) => s.accountID === CARLOS_ACCOUNT_ID);
+
+            expect(currentUserSplit?.amount).toBe(150);
+            expect(carlosSplit?.amount).toBe(50);
+        });
+
+        it('uses SET method for the split chat report in optimisticData when creating a new chat', () => {
+            // Given no existing split chat report
+
+            // When creating splits and Onyx data
+            const result = createSplitsAndOnyxData(buildParams());
+
+            // Then the chat report update should use SET to write the new report atomically
+            const splitChatReportUpdate = result.onyxData.optimisticData?.find(
+                (update) =>
+                    update.key.startsWith(ONYXKEYS.COLLECTION.REPORT) &&
+                    !update.key.includes(ONYXKEYS.COLLECTION.REPORT_ACTIONS) &&
+                    !update.key.includes(ONYXKEYS.COLLECTION.REPORT_METADATA),
+            );
+
+            expect(splitChatReportUpdate?.onyxMethod).toBe(Onyx.METHOD.SET);
+        });
+
+        it('uses MERGE method for the split chat report in optimisticData when reusing an existing chat', async () => {
+            // Given an existing chat report already in Onyx
+            const existingReportID = rand64();
+            await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${existingReportID}`, {
+                reportID: existingReportID,
+                type: CONST.REPORT.TYPE.CHAT,
+                participants: {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT},
+            });
+            await waitForBatchedUpdates();
+
+            // When creating splits referencing that existing chat
+            const result = createSplitsAndOnyxData(buildParams({existingSplitChatReportID: existingReportID}));
+
+            // Then the chat report update should use MERGE to preserve existing fields
+            const splitChatReportUpdate = result.onyxData.optimisticData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.REPORT}${existingReportID}`);
+
+            expect(splitChatReportUpdate?.onyxMethod).toBe(Onyx.METHOD.MERGE);
+        });
+
+        it('adds isOptimisticReport:true to REPORT_METADATA in optimisticData for a new chat', () => {
+            // Given no existing split chat report
+
+            // When creating splits and Onyx data
+            const result = createSplitsAndOnyxData(buildParams());
+
+            // Then optimisticData should flag the new report as optimistic
+            const reportMetaUpdate = result.onyxData.optimisticData?.find((update) => update.key.startsWith(ONYXKEYS.COLLECTION.REPORT_METADATA));
+
+            expect(reportMetaUpdate?.value).toMatchObject({isOptimisticReport: true});
+        });
+
+        it('does not include REPORT_METADATA isOptimisticReport in optimisticData for an existing chat', async () => {
+            // Given an existing chat report already in Onyx
+            const existingReportID = rand64();
+            await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${existingReportID}`, {
+                reportID: existingReportID,
+                type: CONST.REPORT.TYPE.CHAT,
+                participants: {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT},
+            });
+            await waitForBatchedUpdates();
+
+            // When creating splits referencing that existing chat
+            const result = createSplitsAndOnyxData(buildParams({existingSplitChatReportID: existingReportID}));
+
+            // Then no REPORT_METADATA entry should be written for the existing report
+            const reportMetaUpdate = result.onyxData.optimisticData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.REPORT_METADATA}${existingReportID}`);
+
+            expect(reportMetaUpdate).toBeUndefined();
+        });
+
+        it('clears pendingAction and pendingFields on the split transaction in successData', () => {
+            // Given a basic split
+
+            // When creating splits and Onyx data
+            const result = createSplitsAndOnyxData(buildParams());
+            const {transactionID} = result.splitData;
+
+            // Then successData should clear pending state on the split transaction
+            const txSuccessUpdate = result.onyxData.successData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
+
+            expect(txSuccessUpdate?.value).toMatchObject({pendingAction: null, pendingFields: null});
+        });
+
+        it('includes errors on the split transaction in failureData', () => {
+            // Given a basic split
+
+            // When creating splits and Onyx data
+            const result = createSplitsAndOnyxData(buildParams());
+            const {transactionID} = result.splitData;
+
+            // Then failureData should include an errors entry on the split transaction for user-visible feedback
+            const txFailureUpdate = result.onyxData.failureData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
+
+            expect(txFailureUpdate?.value).toHaveProperty('errors');
+        });
+
+        it('sets policy recently used tags in optimisticData for a policy expense chat participant with a tag', async () => {
+            // Given a workspace expense chat with a known tag list in Onyx
+            const policyID = 'test_policy_999';
+            const tagListName = 'Department';
+            const tagName = 'Engineering';
+
+            const existingExpenseChatID = rand64();
+            await Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, {
+                [`${ONYXKEYS.COLLECTION.REPORT}${existingExpenseChatID}`]: {
+                    reportID: existingExpenseChatID,
+                    type: CONST.REPORT.TYPE.CHAT,
+                    chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT,
+                    policyID,
+                    isOwnPolicyExpenseChat: true,
+                    participants: {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT},
+                },
+            } as OnyxMergeCollectionInput<typeof ONYXKEYS.COLLECTION.REPORT>);
+            await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {
+                [tagListName]: {
+                    name: tagListName,
+                    tags: {[tagName]: {name: tagName, enabled: true}},
+                },
+            });
+            await waitForBatchedUpdates();
+
+            // When splitting an expense with a tag inside that workspace chat
+            const result = createSplitsAndOnyxData(
+                buildParams({
+                    existingSplitChatReportID: existingExpenseChatID,
+                    participants: [
+                        {
+                            accountID: CARLOS_ACCOUNT_ID,
+                            login: CARLOS_EMAIL,
+                            isPolicyExpenseChat: true,
+                            isOwnPolicyExpenseChat: true,
+                            policyID,
+                        },
+                    ],
+                    transactionParamOverrides: {tag: tagName},
+                }),
+            );
+
+            // Then optimisticData should update POLICY_RECENTLY_USED_TAGS with the used tag
+            const recentlyUsedTagsUpdate = result.onyxData.optimisticData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`);
+
+            expect(recentlyUsedTagsUpdate?.value).toMatchObject({[tagListName]: [tagName]});
+        });
+    });
 });

From af7ce0b7147db4e278fcde349c8a5f32cbd864e7 Mon Sep 17 00:00:00 2001
From: jakubstec <jakub.stec@swmansion.com>
Date: Wed, 22 Apr 2026 11:59:17 +0200
Subject: [PATCH 2/4] feat: refactor tests, move onyx.connect usage from
 createSplitsAndOnyxDatato upper functions

---
 src/libs/actions/IOU/Split.ts |  5 +++++
 src/libs/actions/IOU/index.ts |  8 +++++---
 tests/actions/IOUTest.ts      | 14 +++++++++-----
 3 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/src/libs/actions/IOU/Split.ts b/src/libs/actions/IOU/Split.ts
index ed98acd71020..d75b013b9eb2 100644
--- a/src/libs/actions/IOU/Split.ts
+++ b/src/libs/actions/IOU/Split.ts
@@ -101,6 +101,7 @@ import {
     getMoneyRequestParticipantsFromReport,
     getMoneyRequestPolicyTags,
     getOrCreateOptimisticSplitChatReport,
+    getPolicyTags,
     getReceiptError,
     getReportPreviewAction,
     getUserAccountID,
@@ -237,6 +238,8 @@ function splitBill({
         policyRecentlyUsedCurrencies,
         betas,
         personalDetails,
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        policyTagsCollection: getPolicyTags(),
     });
 
     const parameters: SplitBillParams = {
@@ -332,6 +335,8 @@ function splitBillAndOpenReport({
         policyRecentlyUsedCurrencies,
         betas,
         personalDetails,
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        policyTagsCollection: getPolicyTags(),
     });
 
     const parameters: SplitBillParams = {
diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts
index 230326ed742c..ca1a95f50413 100644
--- a/src/libs/actions/IOU/index.ts
+++ b/src/libs/actions/IOU/index.ts
@@ -372,6 +372,7 @@ type CreateSplitsAndOnyxDataParams = {
     policyRecentlyUsedCurrencies: string[];
     betas: OnyxEntry<OnyxTypes.Beta[]>;
     personalDetails: OnyxEntry<OnyxTypes.PersonalDetailsList>;
+    policyTagsCollection?: OnyxCollection<OnyxTypes.PolicyTagLists>;
 };
 
 let allPersonalDetails: OnyxTypes.PersonalDetailsList = {};
@@ -2664,6 +2665,7 @@ function createSplitsAndOnyxData({
     policyRecentlyUsedCurrencies,
     betas,
     personalDetails,
+    policyTagsCollection,
 }: CreateSplitsAndOnyxDataParams): SplitsAndOnyxData {
     const currentUserEmailForIOUSplit = addSMSDomainIfPhoneNumber(currentUserLogin);
     const participantAccountIDs = participants.map((participant) => Number(participant.accountID));
@@ -2888,6 +2890,7 @@ function createSplitsAndOnyxData({
         // participant.login is undefined when the request is initiated from a group DM with an unknown user, so we need to add a default
         const email = isOwnPolicyExpenseChat || isPolicyExpenseChat ? '' : addSMSDomainIfPhoneNumber(participant.login ?? '').toLowerCase();
         const accountID = isOwnPolicyExpenseChat || isPolicyExpenseChat ? 0 : Number(participant.accountID);
+        const participantPolicyTags = policyTagsCollection?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${participant.policyID}`];
 
         if (isPendingDistanceSplitBill) {
             const individualSplit = {
@@ -3058,9 +3061,7 @@ function createSplitsAndOnyxData({
         // Add tag to optimistic policy recently used tags when a participant is a workspace
         const optimisticPolicyRecentlyUsedTags = isPolicyExpenseChat
             ? buildOptimisticPolicyRecentlyUsedTags({
-                  // TODO: Replace getPolicyTagsData (https://github.com/Expensify/App/issues/72721) and getPolicyRecentlyUsedTagsData (https://github.com/Expensify/App/issues/71491) with useOnyx hook
-                  // eslint-disable-next-line @typescript-eslint/no-deprecated
-                  policyTags: getPolicyTagsData(participant.policyID),
+                  policyTags: participantPolicyTags ?? {},
                   policyRecentlyUsedTags,
                   transactionTags: tag,
               })
@@ -3265,6 +3266,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest
             policyRecentlyUsedCurrencies,
             betas,
             personalDetails,
+            policyTagsCollection: allPolicyTags,
         });
         onyxData = splitOnyxData;
 
diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts
index fc793d93a048..a77f9506bac7 100644
--- a/tests/actions/IOUTest.ts
+++ b/tests/actions/IOUTest.ts
@@ -7606,12 +7606,13 @@ describe('actions/IOU', () => {
                 participants?: IOUParticipant[];
                 existingSplitChatReportID?: string;
                 transactionParamOverrides?: Partial<typeof baseTransactionParams>;
+                policyTagsCollection?: OnyxCollection<PolicyTagLists>;
             } = {},
         ) => ({
             participants: overrides.participants ?? [{accountID: CARLOS_ACCOUNT_ID, login: CARLOS_EMAIL}],
             currentUserLogin: RORY_EMAIL,
             currentUserAccountID: RORY_ACCOUNT_ID,
-            existingSplitChatReportID: overrides.existingSplitChatReportID ?? '',
+            existingSplitChatReportID: overrides.existingSplitChatReportID,
             transactionParams: {
                 ...baseTransactionParams,
                 ...overrides.transactionParamOverrides,
@@ -7624,6 +7625,7 @@ describe('actions/IOU', () => {
             policyRecentlyUsedCurrencies: [],
             betas: [CONST.BETAS.ALL],
             personalDetails: mockPersonalDetails,
+            policyTagsCollection: overrides.policyTagsCollection ?? undefined,
         });
 
         it('returns valid splitData with chatReportID, transactionID, and reportActionID', () => {
@@ -7804,7 +7806,7 @@ describe('actions/IOU', () => {
         });
 
         it('sets policy recently used tags in optimisticData for a policy expense chat participant with a tag', async () => {
-            // Given a workspace expense chat with a known tag list in Onyx
+            // Given a workspace expense chat with a known tag list
             const policyID = 'test_policy_999';
             const tagListName = 'Department';
             const tagName = 'Engineering';
@@ -7820,13 +7822,14 @@ describe('actions/IOU', () => {
                     participants: {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT},
                 },
             } as OnyxMergeCollectionInput<typeof ONYXKEYS.COLLECTION.REPORT>);
-            await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {
+            await waitForBatchedUpdates();
+
+            const policyTagsList = {
                 [tagListName]: {
                     name: tagListName,
                     tags: {[tagName]: {name: tagName, enabled: true}},
                 },
-            });
-            await waitForBatchedUpdates();
+            };
 
             // When splitting an expense with a tag inside that workspace chat
             const result = createSplitsAndOnyxData(
@@ -7842,6 +7845,7 @@ describe('actions/IOU', () => {
                         },
                     ],
                     transactionParamOverrides: {tag: tagName},
+                    policyTagsCollection: {[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`]: policyTagsList} as unknown as OnyxCollection<PolicyTagLists>,
                 }),
             );
 

From 24443e9756ccfba64e92838529fc9bb6be0f91a2 Mon Sep 17 00:00:00 2001
From: jakubstec <jakub.stec@swmansion.com>
Date: Wed, 22 Apr 2026 15:06:05 +0200
Subject: [PATCH 3/4] feat: add policy tags filtering to only pass participants
 related tags

---
 src/libs/actions/IOU/Split.ts |  5 +++--
 src/libs/actions/IOU/index.ts | 21 +++++++++++++++++----
 tests/actions/IOUTest.ts      |  6 +++---
 3 files changed, 23 insertions(+), 9 deletions(-)

diff --git a/src/libs/actions/IOU/Split.ts b/src/libs/actions/IOU/Split.ts
index d75b013b9eb2..ba47eb2986d6 100644
--- a/src/libs/actions/IOU/Split.ts
+++ b/src/libs/actions/IOU/Split.ts
@@ -92,6 +92,7 @@ import {getCleanUpTransactionThreadReportOnyxData} from './DeleteMoneyRequest';
 import {
     buildMinimalTransactionForFormula,
     buildOnyxDataForMoneyRequest,
+    buildParticipantsPolicyTags,
     createSplitsAndOnyxData,
     dismissModalAndOpenReportInInboxTab,
     getAllPersonalDetails,
@@ -239,7 +240,7 @@ function splitBill({
         betas,
         personalDetails,
         // eslint-disable-next-line @typescript-eslint/no-deprecated
-        policyTagsCollection: getPolicyTags(),
+        participantsPolicyTags: buildParticipantsPolicyTags(participants, getPolicyTags()),
     });
 
     const parameters: SplitBillParams = {
@@ -336,7 +337,7 @@ function splitBillAndOpenReport({
         betas,
         personalDetails,
         // eslint-disable-next-line @typescript-eslint/no-deprecated
-        policyTagsCollection: getPolicyTags(),
+        participantsPolicyTags: buildParticipantsPolicyTags(participants, getPolicyTags()),
     });
 
     const parameters: SplitBillParams = {
diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts
index ca1a95f50413..0894a4a79110 100644
--- a/src/libs/actions/IOU/index.ts
+++ b/src/libs/actions/IOU/index.ts
@@ -372,7 +372,7 @@ type CreateSplitsAndOnyxDataParams = {
     policyRecentlyUsedCurrencies: string[];
     betas: OnyxEntry<OnyxTypes.Beta[]>;
     personalDetails: OnyxEntry<OnyxTypes.PersonalDetailsList>;
-    policyTagsCollection?: OnyxCollection<OnyxTypes.PolicyTagLists>;
+    participantsPolicyTags?: Record<string, OnyxTypes.PolicyTagLists>;
 };
 
 let allPersonalDetails: OnyxTypes.PersonalDetailsList = {};
@@ -591,6 +591,18 @@ function getPolicyTags(): OnyxCollection<OnyxTypes.PolicyTagLists> {
     return allPolicyTags;
 }
 
+function buildParticipantsPolicyTags(participants: Participant[], policyTagsSource: OnyxCollection<OnyxTypes.PolicyTagLists>): Record<string, OnyxTypes.PolicyTagLists> {
+    return participants.reduce<Record<string, OnyxTypes.PolicyTagLists>>((acc, participant) => {
+        if (participant.policyID) {
+            const tags = policyTagsSource?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${participant.policyID}`];
+            if (tags) {
+                acc[participant.policyID] = tags;
+            }
+        }
+        return acc;
+    }, {});
+}
+
 /**
  * @deprecated This function uses Onyx.connect and should be replaced with useOnyx for reactive data access.
  * TODO: remove `getPolicyTagsData` from this file (https://github.com/Expensify/App/issues/72721)
@@ -2665,7 +2677,7 @@ function createSplitsAndOnyxData({
     policyRecentlyUsedCurrencies,
     betas,
     personalDetails,
-    policyTagsCollection,
+    participantsPolicyTags,
 }: CreateSplitsAndOnyxDataParams): SplitsAndOnyxData {
     const currentUserEmailForIOUSplit = addSMSDomainIfPhoneNumber(currentUserLogin);
     const participantAccountIDs = participants.map((participant) => Number(participant.accountID));
@@ -2890,7 +2902,7 @@ function createSplitsAndOnyxData({
         // participant.login is undefined when the request is initiated from a group DM with an unknown user, so we need to add a default
         const email = isOwnPolicyExpenseChat || isPolicyExpenseChat ? '' : addSMSDomainIfPhoneNumber(participant.login ?? '').toLowerCase();
         const accountID = isOwnPolicyExpenseChat || isPolicyExpenseChat ? 0 : Number(participant.accountID);
-        const participantPolicyTags = policyTagsCollection?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${participant.policyID}`];
+        const participantPolicyTags = participant.policyID ? participantsPolicyTags?.[participant.policyID] : undefined;
 
         if (isPendingDistanceSplitBill) {
             const individualSplit = {
@@ -3266,7 +3278,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest
             policyRecentlyUsedCurrencies,
             betas,
             personalDetails,
-            policyTagsCollection: allPolicyTags,
+            participantsPolicyTags: buildParticipantsPolicyTags(participants, allPolicyTags),
         });
         onyxData = splitOnyxData;
 
@@ -3804,6 +3816,7 @@ export {
     maybeUpdateReportNameForFormulaTitle,
     getSearchOnyxUpdate,
     getPolicyTags,
+    buildParticipantsPolicyTags,
     // eslint-disable-next-line @typescript-eslint/no-deprecated
     getMoneyRequestPolicyTags,
     setMoneyRequestTimeRate,
diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts
index a77f9506bac7..62c559006cd3 100644
--- a/tests/actions/IOUTest.ts
+++ b/tests/actions/IOUTest.ts
@@ -7606,7 +7606,7 @@ describe('actions/IOU', () => {
                 participants?: IOUParticipant[];
                 existingSplitChatReportID?: string;
                 transactionParamOverrides?: Partial<typeof baseTransactionParams>;
-                policyTagsCollection?: OnyxCollection<PolicyTagLists>;
+                participantsPolicyTags?: Record<string, PolicyTagLists>;
             } = {},
         ) => ({
             participants: overrides.participants ?? [{accountID: CARLOS_ACCOUNT_ID, login: CARLOS_EMAIL}],
@@ -7625,7 +7625,7 @@ describe('actions/IOU', () => {
             policyRecentlyUsedCurrencies: [],
             betas: [CONST.BETAS.ALL],
             personalDetails: mockPersonalDetails,
-            policyTagsCollection: overrides.policyTagsCollection ?? undefined,
+            participantsPolicyTags: overrides.participantsPolicyTags,
         });
 
         it('returns valid splitData with chatReportID, transactionID, and reportActionID', () => {
@@ -7845,7 +7845,7 @@ describe('actions/IOU', () => {
                         },
                     ],
                     transactionParamOverrides: {tag: tagName},
-                    policyTagsCollection: {[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`]: policyTagsList} as unknown as OnyxCollection<PolicyTagLists>,
+                    participantsPolicyTags: {[policyID]: policyTagsList} as unknown as Record<string, PolicyTagLists>,
                 }),
             );
 

From ab15100d3786ccbb3ed9dfdc89aa2b928e0665d8 Mon Sep 17 00:00:00 2001
From: jakubstec <jakub.stec@swmansion.com>
Date: Thu, 23 Apr 2026 09:45:42 +0200
Subject: [PATCH 4/4] apply review changes

---
 src/libs/actions/IOU/Split.ts |  7 ++-----
 src/libs/actions/IOU/index.ts | 12 ++++++------
 tests/actions/IOUTest.ts      |  2 +-
 3 files changed, 9 insertions(+), 12 deletions(-)

diff --git a/src/libs/actions/IOU/Split.ts b/src/libs/actions/IOU/Split.ts
index ba47eb2986d6..0a762cbc419a 100644
--- a/src/libs/actions/IOU/Split.ts
+++ b/src/libs/actions/IOU/Split.ts
@@ -102,7 +102,6 @@ import {
     getMoneyRequestParticipantsFromReport,
     getMoneyRequestPolicyTags,
     getOrCreateOptimisticSplitChatReport,
-    getPolicyTags,
     getReceiptError,
     getReportPreviewAction,
     getUserAccountID,
@@ -239,8 +238,7 @@ function splitBill({
         policyRecentlyUsedCurrencies,
         betas,
         personalDetails,
-        // eslint-disable-next-line @typescript-eslint/no-deprecated
-        participantsPolicyTags: buildParticipantsPolicyTags(participants, getPolicyTags()),
+        participantsPolicyTags: buildParticipantsPolicyTags(participants),
     });
 
     const parameters: SplitBillParams = {
@@ -336,8 +334,7 @@ function splitBillAndOpenReport({
         policyRecentlyUsedCurrencies,
         betas,
         personalDetails,
-        // eslint-disable-next-line @typescript-eslint/no-deprecated
-        participantsPolicyTags: buildParticipantsPolicyTags(participants, getPolicyTags()),
+        participantsPolicyTags: buildParticipantsPolicyTags(participants),
     });
 
     const parameters: SplitBillParams = {
diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts
index 0894a4a79110..ec4102000ef4 100644
--- a/src/libs/actions/IOU/index.ts
+++ b/src/libs/actions/IOU/index.ts
@@ -372,7 +372,7 @@ type CreateSplitsAndOnyxDataParams = {
     policyRecentlyUsedCurrencies: string[];
     betas: OnyxEntry<OnyxTypes.Beta[]>;
     personalDetails: OnyxEntry<OnyxTypes.PersonalDetailsList>;
-    participantsPolicyTags?: Record<string, OnyxTypes.PolicyTagLists>;
+    participantsPolicyTags: Record<string, OnyxTypes.PolicyTagLists>;
 };
 
 let allPersonalDetails: OnyxTypes.PersonalDetailsList = {};
@@ -591,10 +591,10 @@ function getPolicyTags(): OnyxCollection<OnyxTypes.PolicyTagLists> {
     return allPolicyTags;
 }
 
-function buildParticipantsPolicyTags(participants: Participant[], policyTagsSource: OnyxCollection<OnyxTypes.PolicyTagLists>): Record<string, OnyxTypes.PolicyTagLists> {
+function buildParticipantsPolicyTags(participants: Participant[]): Record<string, OnyxTypes.PolicyTagLists> {
     return participants.reduce<Record<string, OnyxTypes.PolicyTagLists>>((acc, participant) => {
         if (participant.policyID) {
-            const tags = policyTagsSource?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${participant.policyID}`];
+            const tags = allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${participant.policyID}`];
             if (tags) {
                 acc[participant.policyID] = tags;
             }
@@ -2902,7 +2902,7 @@ function createSplitsAndOnyxData({
         // participant.login is undefined when the request is initiated from a group DM with an unknown user, so we need to add a default
         const email = isOwnPolicyExpenseChat || isPolicyExpenseChat ? '' : addSMSDomainIfPhoneNumber(participant.login ?? '').toLowerCase();
         const accountID = isOwnPolicyExpenseChat || isPolicyExpenseChat ? 0 : Number(participant.accountID);
-        const participantPolicyTags = participant.policyID ? participantsPolicyTags?.[participant.policyID] : undefined;
+        const participantPolicyTags = participant.policyID ? participantsPolicyTags?.[participant.policyID] : {};
 
         if (isPendingDistanceSplitBill) {
             const individualSplit = {
@@ -3073,7 +3073,7 @@ function createSplitsAndOnyxData({
         // Add tag to optimistic policy recently used tags when a participant is a workspace
         const optimisticPolicyRecentlyUsedTags = isPolicyExpenseChat
             ? buildOptimisticPolicyRecentlyUsedTags({
-                  policyTags: participantPolicyTags ?? {},
+                  policyTags: participantPolicyTags,
                   policyRecentlyUsedTags,
                   transactionTags: tag,
               })
@@ -3278,7 +3278,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest
             policyRecentlyUsedCurrencies,
             betas,
             personalDetails,
-            participantsPolicyTags: buildParticipantsPolicyTags(participants, allPolicyTags),
+            participantsPolicyTags: buildParticipantsPolicyTags(participants),
         });
         onyxData = splitOnyxData;
 
diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts
index 62c559006cd3..9519024e88c7 100644
--- a/tests/actions/IOUTest.ts
+++ b/tests/actions/IOUTest.ts
@@ -7625,7 +7625,7 @@ describe('actions/IOU', () => {
             policyRecentlyUsedCurrencies: [],
             betas: [CONST.BETAS.ALL],
             personalDetails: mockPersonalDetails,
-            participantsPolicyTags: overrides.participantsPolicyTags,
+            participantsPolicyTags: overrides.participantsPolicyTags ?? {},
         });
 
         it('returns valid splitData with chatReportID, transactionID, and reportActionID', () => {
