From 136214c468e56fc4945ad9bb5973dd429c1a4de6 Mon Sep 17 00:00:00 2001 From: daledah Date: Wed, 25 Feb 2026 14:07:39 +0700 Subject: [PATCH 1/2] fix: GBR appears when there is report field error --- src/libs/ReportUtils.ts | 27 +++++++++++++++++++ .../OnyxDerived/configs/reportAttributes.ts | 18 +++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9bac157a49b87..bf8c0a8598601 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -12841,6 +12841,32 @@ function getReportFieldMaps(report: OnyxEntry, fieldList: Record, policy: OnyxEntry): boolean { + if (!report || !policy?.fieldList || !policy?.areReportFieldsEnabled) { + return false; + } + + if (!isPaidGroupPolicyExpenseReport(report) && !isInvoiceReport(report)) { + return false; + } + + const reportViolations = allReportsViolations?.[`${ONYXKEYS.COLLECTION.REPORT_VIOLATIONS}${report.reportID}`]; + const {fieldsByName} = getReportFieldMaps(report, policy.fieldList); + + return Object.values(fieldsByName).some((field) => { + if (field.target !== report.type) { + return false; + } + if (shouldHideSingleReportField(field)) { + return false; + } + if (isReportFieldDisabledForUser(report, field, policy)) { + return false; + } + return !!getFieldViolation(reportViolations, field); + }); +} + export { areAllRequestsBeingSmartScanned, buildOptimisticAddCommentReportAction, @@ -13024,6 +13050,7 @@ export { hasSmartscanError, hasUpdatedTotal, hasViolations, + hasVisibleReportFieldViolations, hasWarningTypeViolations, hasNoticeTypeViolations, hasAnyViolations, diff --git a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts index 69be37203a37c..ffb6f60a126ee 100644 --- a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts +++ b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts @@ -1,6 +1,6 @@ import type {OnyxEntry} from 'react-native-onyx'; import {computeReportName} from '@libs/ReportNameUtils'; -import {generateIsEmptyReport, generateReportAttributes, isArchivedReport, isValidReport} from '@libs/ReportUtils'; +import {generateIsEmptyReport, generateReportAttributes, hasVisibleReportFieldViolations, isArchivedReport, isValidReport} from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; import createOnyxDerivedValueConfig from '@userActions/OnyxDerived/createOnyxDerivedValueConfig'; import {hasKeyTriggeredCompute} from '@userActions/OnyxDerived/utils'; @@ -209,9 +209,23 @@ export default createOnyxDerivedValueConfig({ isReportArchived, }); + const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`]; + const hasFieldViolations = hasVisibleReportFieldViolations(report, policy); + let brickRoadStatus; // if report has errors or violations, show red dot - if (SidebarUtils.shouldShowRedBrickRoad(report, chatReport, reportActionsList, hasAnyViolations, reportErrors, transactions, transactionViolations, !!isReportArchived)) { + if ( + SidebarUtils.shouldShowRedBrickRoad( + report, + chatReport, + reportActionsList, + hasAnyViolations || hasFieldViolations, + reportErrors, + transactions, + transactionViolations, + !!isReportArchived, + ) + ) { brickRoadStatus = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; } // if report does not have error, check if it should show green dot From 86f2cef15e479590c022c7cb769258d42e19777f Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 5 Mar 2026 15:33:47 +0700 Subject: [PATCH 2/2] add test for the hasVisibleReportFieldViolations function --- tests/unit/ReportUtilsTest.ts | 80 +++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 45e2fee80b09d..91f6901e3c61f 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -97,6 +97,7 @@ import { hasActionWithErrorsForTransaction, hasEmptyReportsForPolicy, hasReceiptError, + hasVisibleReportFieldViolations, isAllowedToApproveExpenseReport, isArchivedNonExpenseReport, isArchivedReport, @@ -13481,4 +13482,83 @@ describe('ReportUtils', () => { await Onyx.clear(); }); }); + + describe('hasVisibleReportFieldViolations', () => { + const policyID = 'policy-field-violations'; + + const baseField: PolicyReportField = { + name: 'project', + fieldID: 'project_field', + defaultValue: '', + orderWeight: 1, + type: 'text', + deletable: true, + target: CONST.REPORT.TYPE.EXPENSE, + values: [], + keys: [], + externalIDs: [], + disabledOptions: [], + isTax: false, + }; + + const basePolicy = { + ...createRandomPolicy(Number(policyID), CONST.POLICY.TYPE.TEAM), + id: policyID, + type: CONST.POLICY.TYPE.TEAM, + role: CONST.POLICY.ROLE.ADMIN, + areReportFieldsEnabled: true, + fieldList: { + [`expensify_${baseField.fieldID}`]: baseField, + }, + }; + + const expenseReport: Report = { + reportID: 'report-field-violations', + type: CONST.REPORT.TYPE.EXPENSE, + policyID, + ownerAccountID: currentUserAccountID, + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, + }; + + beforeEach(async () => { + await Onyx.clear(); + await Onyx.merge(ONYXKEYS.SESSION, {accountID: currentUserAccountID, email: currentUserEmail}); + await waitForBatchedUpdates(); + }); + + it('should return false when policy does not have areReportFieldsEnabled enabled', () => { + const policyWithFieldsDisabled = {...basePolicy, areReportFieldsEnabled: false}; + + expect(hasVisibleReportFieldViolations(expenseReport, policyWithFieldsDisabled)).toBe(false); + }); + + it('should return false when the report is not an expense report or invoice report', () => { + const chatReport: Report = { + reportID: 'chat-report-field-violations', + type: CONST.REPORT.TYPE.CHAT, + policyID, + }; + + expect(hasVisibleReportFieldViolations(chatReport, basePolicy)).toBe(false); + }); + + it('should return true when expense report has a required field with no value', async () => { + const fieldWithNoValue: PolicyReportField = { + ...baseField, + value: null, + defaultValue: '', + }; + + const policyWithEmptyField = { + ...basePolicy, + fieldList: {[`expensify_${fieldWithNoValue.fieldID}`]: fieldWithNoValue}, + }; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, policyWithEmptyField); + await waitForBatchedUpdates(); + + expect(hasVisibleReportFieldViolations(expenseReport, policyWithEmptyField)).toBe(true); + }); + }); });