From 9946af17fabe853b67571c813a8e956a0af6af66 Mon Sep 17 00:00:00 2001 From: TaduJR Date: Thu, 25 Dec 2025 11:10:50 +0300 Subject: [PATCH 1/3] fix: Change copy of system message that generates when a previously uncategorized expense is categorized --- src/libs/ModifiedExpenseMessage.ts | 18 ++++++----- tests/unit/ModifiedExpenseMessageTest.ts | 39 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 18ffffe3a266..1b4240e86d03 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -62,13 +62,12 @@ function buildMessageFragmentForValue( removalFragments: string[], changeFragments: string[], shouldConvertToLowercase = true, + isCategoryField = false, ) { const newValueToDisplay = valueInQuotes ? `"${newValue}"` : newValue; - // If the valueName is category and the old value was Uncategorized, show it in lowercase without quotes let oldValueToDisplay; - // eslint-disable-next-line @typescript-eslint/no-deprecated - if (valueName.includes(translateLocal('common.category').toLowerCase()) && isCategoryMissing(oldValue)) { + if (isCategoryField && isCategoryMissing(oldValue)) { oldValueToDisplay = oldValue.toLowerCase(); } else if (valueInQuotes) { oldValueToDisplay = `"${oldValue}"`; @@ -78,11 +77,14 @@ function buildMessageFragmentForValue( const displayValueName = shouldConvertToLowercase ? valueName.toLowerCase() : valueName; const isOldValuePartialMerchant = valueName === translate('common.merchant') && oldValue === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; + const isOldCategoryMissing = isCategoryField && isCategoryMissing(oldValue); + const isNewCategoryMissing = isCategoryField && isCategoryMissing(newValue); - // In case of a partial merchant value, we want to avoid user seeing the "(none)" value in the message. - if (!oldValue || isOldValuePartialMerchant) { - const fragment = translate('iou.setTheRequest', {valueName: displayValueName, newValueToDisplay}); - setFragments.push(fragment); + if (!oldValue || isOldValuePartialMerchant || isOldCategoryMissing) { + if (!(isOldCategoryMissing && isNewCategoryMissing)) { + const fragment = translate('iou.setTheRequest', {valueName: displayValueName, newValueToDisplay}); + setFragments.push(fragment); + } } else if (!newValue || newValue === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT) { const fragment = translate('iou.removedTheRequest', {valueName: displayValueName, oldValueToDisplay}); removalFragments.push(fragment); @@ -352,6 +354,7 @@ function getForReportAction({ changeFragments, // Don't convert to lowercase when we have source attribution (to preserve any HTML links) false, + true, ); } @@ -617,6 +620,7 @@ function getForReportActionTemp({ changeFragments, // Don't convert to lowercase when we have source attribution (to preserve any HTML links) false, + true, ); } diff --git a/tests/unit/ModifiedExpenseMessageTest.ts b/tests/unit/ModifiedExpenseMessageTest.ts index b10b1b281a67..cf4446a6d305 100644 --- a/tests/unit/ModifiedExpenseMessageTest.ts +++ b/tests/unit/ModifiedExpenseMessageTest.ts @@ -739,6 +739,45 @@ describe('ModifiedExpenseMessage', () => { }); }); + describe('when the category is changed from Uncategorized with AI attribution', () => { + const reportAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE, + originalMessage: { + category: '6403 Travel - Member Services', + oldCategory: 'Uncategorized', + source: CONST.CATEGORY_SOURCE.AI, + } as OriginalMessageModifiedExpense, + }; + + it('returns the correct text message without showing previously uncategorized', () => { + const expectedResult = `set the category based on past activity to "6403 Travel - Member Services"`; + + const result = getForReportAction({reportAction, policyID: report.policyID}); + + expect(result).toEqual(expectedResult); + }); + }); + + describe('when the category is cleared from Uncategorized (both missing)', () => { + const reportAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE, + originalMessage: { + category: '', + oldCategory: 'Uncategorized', + } as OriginalMessageModifiedExpense, + }; + + it('returns the generic changed expense message since no meaningful change occurred', () => { + const expectedResult = `changed the expense`; + + const result = getForReportAction({reportAction, policyID: report.policyID}); + + expect(result).toEqual(expectedResult); + }); + }); + describe('when the category is removed with AI attribution', () => { const reportAction = { ...createRandomReportAction(1), From 60725af6052646b550bafb632d35eab36094e71a Mon Sep 17 00:00:00 2001 From: TaduJR Date: Sat, 27 Dec 2025 16:06:18 +0300 Subject: [PATCH 2/3] fix: Use 'set' instead of 'changed' for previously uncategorized expenses --- src/libs/ModifiedExpenseMessage.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 1b4240e86d03..17efe5f86140 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -62,10 +62,12 @@ function buildMessageFragmentForValue( removalFragments: string[], changeFragments: string[], shouldConvertToLowercase = true, - isCategoryField = false, ) { const newValueToDisplay = valueInQuotes ? `"${newValue}"` : newValue; + // eslint-disable-next-line @typescript-eslint/no-deprecated + const isCategoryField = valueName.includes(translateLocal('common.category').toLowerCase()); + let oldValueToDisplay; if (isCategoryField && isCategoryMissing(oldValue)) { oldValueToDisplay = oldValue.toLowerCase(); @@ -354,7 +356,6 @@ function getForReportAction({ changeFragments, // Don't convert to lowercase when we have source attribution (to preserve any HTML links) false, - true, ); } @@ -620,7 +621,6 @@ function getForReportActionTemp({ changeFragments, // Don't convert to lowercase when we have source attribution (to preserve any HTML links) false, - true, ); } From 1ff2e2e21bd08ce763dc98243f2e1a8379ec7080 Mon Sep 17 00:00:00 2001 From: TaduJR Date: Thu, 1 Jan 2026 17:29:42 +0300 Subject: [PATCH 3/3] fix: Use 'set' instead of 'changed' for previously uncategorized expenses --- src/libs/ModifiedExpenseMessage.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 17efe5f86140..c65514062ab3 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -64,19 +64,11 @@ function buildMessageFragmentForValue( shouldConvertToLowercase = true, ) { const newValueToDisplay = valueInQuotes ? `"${newValue}"` : newValue; + const oldValueToDisplay = valueInQuotes ? `"${oldValue}"` : oldValue; // eslint-disable-next-line @typescript-eslint/no-deprecated const isCategoryField = valueName.includes(translateLocal('common.category').toLowerCase()); - let oldValueToDisplay; - if (isCategoryField && isCategoryMissing(oldValue)) { - oldValueToDisplay = oldValue.toLowerCase(); - } else if (valueInQuotes) { - oldValueToDisplay = `"${oldValue}"`; - } else { - oldValueToDisplay = oldValue; - } - const displayValueName = shouldConvertToLowercase ? valueName.toLowerCase() : valueName; const isOldValuePartialMerchant = valueName === translate('common.merchant') && oldValue === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; const isOldCategoryMissing = isCategoryField && isCategoryMissing(oldValue);