Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1021,7 +1021,7 @@
};

let conciergeReportIDOnyxConnect: OnyxEntry<string>;
Onyx.connect({

Check warning on line 1024 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.CONCIERGE_REPORT_ID,
callback: (value) => {
conciergeReportIDOnyxConnect = value;
Expand All @@ -1029,7 +1029,7 @@
});

const defaultAvatarBuildingIconTestID = 'SvgDefaultAvatarBuilding Icon';
Onyx.connect({

Check warning on line 1032 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
// When signed out, val is undefined
Expand All @@ -1047,7 +1047,7 @@
let allPersonalDetails: OnyxEntry<PersonalDetailsList>;
let allPersonalDetailLogins: string[];
let currentUserPersonalDetails: OnyxEntry<PersonalDetails>;
Onyx.connect({

Check warning on line 1050 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (value) => {
if (currentUserAccountID) {
Expand All @@ -1059,7 +1059,7 @@
});

let allReportsDraft: OnyxCollection<Report>;
Onyx.connect({

Check warning on line 1062 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_DRAFT,
waitForCollectionCallback: true,
callback: (value) => (allReportsDraft = value),
Expand All @@ -1068,7 +1068,7 @@
let allPolicies: OnyxCollection<Policy>;
let hasPolicies: boolean;
let policiesArray: Policy[] = [];
Onyx.connect({

Check warning on line 1071 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -1079,7 +1079,7 @@
});

let allPolicyDrafts: OnyxCollection<Policy>;
Onyx.connect({

Check warning on line 1082 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY_DRAFTS,
waitForCollectionCallback: true,
callback: (value) => (allPolicyDrafts = value),
Expand All @@ -1087,7 +1087,7 @@

let allReports: OnyxCollection<Report>;
let reportsByPolicyID: ReportByPolicyMap;
Onyx.connect({

Check warning on line 1090 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand Down Expand Up @@ -1123,14 +1123,14 @@
});

let betaConfiguration: OnyxEntry<BetaConfiguration> = {};
Onyx.connect({

Check warning on line 1126 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.BETA_CONFIGURATION,
callback: (value) => (betaConfiguration = value ?? {}),
});

let allTransactions: OnyxCollection<Transaction> = {};
let reportsTransactions: Record<string, Transaction[]> = {};
Onyx.connect({

Check warning on line 1133 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -1156,7 +1156,7 @@
});

let allReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 1159 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => {
Expand Down Expand Up @@ -13058,6 +13058,32 @@
return {fieldValues, fieldsByName};
}

function hasVisibleReportFieldViolations(report: OnyxEntry<Report>, policy: OnyxEntry<Policy>): 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,
Expand Down Expand Up @@ -13243,6 +13269,7 @@
hasSmartscanError,
hasUpdatedTotal,
hasViolations,
hasVisibleReportFieldViolations,
hasWarningTypeViolations,
hasNoticeTypeViolations,
hasAnyViolations,
Expand Down
18 changes: 16 additions & 2 deletions src/libs/actions/OnyxDerived/configs/reportAttributes.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -199,9 +199,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
Expand Down
81 changes: 81 additions & 0 deletions tests/unit/ReportUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ import {
hasActionWithErrorsForTransaction,
hasEmptyReportsForPolicy,
hasReceiptError,
hasVisibleReportFieldViolations,
isAllowedToApproveExpenseReport,
isArchivedNonExpenseReport,
isArchivedReport,
Expand Down Expand Up @@ -13695,6 +13696,86 @@ 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);
});
});

describe('getAddExpenseDropdownOptions', () => {
const mockTranslate: LocaleContextProps['translate'] = (path, ...params) => translate(CONST.LOCALES.EN, path, ...params);
const mockIcons = {Location: jest.fn(), ReceiptPlus: jest.fn()} as unknown as Record<'Location' | 'ReceiptPlus', IconAsset>;
Expand Down
Loading