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
37 changes: 37 additions & 0 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1051,7 +1051,7 @@
};

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

Check warning on line 1054 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 @@ -1059,7 +1059,7 @@
});

const defaultAvatarBuildingIconTestID = 'SvgDefaultAvatarBuilding Icon';
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.SESSION,
callback: (value) => {
// When signed out, val is undefined
Expand All @@ -1077,7 +1077,7 @@
let allPersonalDetails: OnyxEntry<PersonalDetailsList>;
let allPersonalDetailLogins: string[];
let currentUserPersonalDetails: OnyxEntry<PersonalDetails>;
Onyx.connect({

Check warning on line 1080 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 @@ -1089,7 +1089,7 @@
});

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

Check warning on line 1092 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 @@ -1097,7 +1097,7 @@

let allPolicies: OnyxCollection<Policy>;
let policiesArray: Policy[] = [];
Onyx.connect({

Check warning on line 1100 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 @@ -1107,7 +1107,7 @@
});

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

Check warning on line 1110 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 @@ -1115,7 +1115,7 @@

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

Check warning on line 1118 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 @@ -1151,14 +1151,14 @@
});

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

Check warning on line 1154 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 deprecatedAllTransactions: OnyxCollection<Transaction> = {};
let deprecatedReportsTransactions: Record<string, Transaction[]> = {};
Onyx.connect({

Check warning on line 1161 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 @@ -1184,7 +1184,7 @@
});

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

Check warning on line 1187 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 @@ -9206,6 +9206,42 @@
return transactions.some((transaction) => hasViolation(transaction, transactionViolations, currentUserEmailParam ?? '', currentUserAccountIDParam, report, policy, shouldShowInReview));
}

function hasVisibleReportFieldViolations(report: OnyxEntry<Report>, policy: OnyxEntry<Policy>, reportViolations?: OnyxEntry<ReportViolations>): boolean {
if (!report || !policy?.fieldList || !policy?.areReportFieldsEnabled) {
return false;
}

const isPaidGroupPolicyReport = isExpenseReport(report) && (policy?.type === CONST.POLICY.TYPE.CORPORATE || policy?.type === CONST.POLICY.TYPE.TEAM);
if (!isPaidGroupPolicyReport && !isInvoiceReport(report)) {
return false;
}

// We only show the RBR to the submitter for expense reports
if (isPaidGroupPolicyReport && !isCurrentUserSubmitter(report)) {
return false;
}

// Allow both open and processing reports to show RBR for field violations (expense reports only)
if (isPaidGroupPolicyReport && !isOpenOrProcessingReport(report)) {
return false;
}

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);
});
}

/**
* Checks to see if a report contains a violation of type `warning`
*/
Expand Down Expand Up @@ -13333,6 +13369,7 @@
hasSmartscanError,
hasUpdatedTotal,
hasViolations,
hasVisibleReportFieldViolations,
hasWarningTypeViolations,
hasNoticeTypeViolations,
hasAnyViolations,
Expand Down
15 changes: 11 additions & 4 deletions src/libs/actions/OnyxDerived/configs/reportAttributes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {OnyxCollection, 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 All @@ -19,7 +19,8 @@ const prepareReportKeys = (keys: string[]) => {
key
.replace(ONYXKEYS.COLLECTION.REPORT_METADATA, ONYXKEYS.COLLECTION.REPORT)
.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, ONYXKEYS.COLLECTION.REPORT)
.replace(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, ONYXKEYS.COLLECTION.REPORT),
.replace(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, ONYXKEYS.COLLECTION.REPORT)
.replace(ONYXKEYS.COLLECTION.REPORT_VIOLATIONS, ONYXKEYS.COLLECTION.REPORT),
),
),
];
Expand Down Expand Up @@ -70,10 +71,11 @@ export default createOnyxDerivedValueConfig({
ONYXKEYS.SESSION,
ONYXKEYS.COLLECTION.POLICY,
ONYXKEYS.COLLECTION.POLICY_TAGS,
ONYXKEYS.COLLECTION.REPORT_VIOLATIONS,
ONYXKEYS.COLLECTION.REPORT_METADATA,
],
compute: (
[reports, preferredLocale, transactionViolations, reportActions, reportNameValuePairs, transactions, personalDetails, session, policies, policyTags],
[reports, preferredLocale, transactionViolations, reportActions, reportNameValuePairs, transactions, personalDetails, session, policies, policyTags, reportViolations],
{currentValue, sourceValues},
) => {
// Check if display names changed when personal details are updated
Expand Down Expand Up @@ -115,6 +117,7 @@ export default createOnyxDerivedValueConfig({
const reportMetadataUpdates = sourceValues?.[ONYXKEYS.COLLECTION.REPORT_METADATA] ?? {};
const reportActionsUpdates = sourceValues?.[ONYXKEYS.COLLECTION.REPORT_ACTIONS] ?? {};
const reportNameValuePairsUpdates = sourceValues?.[ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS] ?? {};
const reportViolationsUpdates = sourceValues?.[ONYXKEYS.COLLECTION.REPORT_VIOLATIONS] ?? {};
const transactionsUpdates = sourceValues?.[ONYXKEYS.COLLECTION.TRANSACTION];
const transactionViolationsUpdates = sourceValues?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS];
let dataToIterate = Object.keys(reports);
Expand All @@ -139,6 +142,7 @@ export default createOnyxDerivedValueConfig({
...Object.keys(reportMetadataUpdates),
...Object.keys(reportActionsUpdates),
...Object.keys(reportNameValuePairsUpdates),
...Object.keys(reportViolationsUpdates),
...Array.from(reportUpdatesRelatedToReportActions),
];

Expand Down Expand Up @@ -220,14 +224,17 @@ export default createOnyxDerivedValueConfig({
isReportArchived,
});

const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`];
const hasFieldViolations = hasVisibleReportFieldViolations(report, policy, reportViolations?.[`${ONYXKEYS.COLLECTION.REPORT_VIOLATIONS}${report.reportID}`]);

let brickRoadStatus;
let actionBadge;
let actionTargetReportActionID;
const reasonAndReportAction = SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad(
report,
chatReport,
reportActionsList,
hasAnyViolations,
hasAnyViolations || hasFieldViolations,
reportErrors,
transactions,
transactionViolations,
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/OnyxDerivedTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ describe('OnyxDerived', () => {
const transaction = createRandomTransaction(1);

// When the report attributes are recomputed with both report and transaction updates
reportAttributes.compute([reports, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined], {});
reportAttributes.compute([reports, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined], {});
const reportAttributesComputedValue = reportAttributes.compute(
[reports, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined],
[reports, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined],
{
sourceValues: {
[ONYXKEYS.COLLECTION.REPORT]: {
Expand Down
80 changes: 80 additions & 0 deletions tests/unit/ReportUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ import {
hasActionWithErrorsForTransaction,
hasEmptyReportsForPolicy,
hasReceiptError,
hasVisibleReportFieldViolations,
isAllowedToApproveExpenseReport,
isArchivedNonExpenseReport,
isArchivedReport,
Expand Down Expand Up @@ -14120,6 +14121,85 @@ describe('ReportUtils', () => {
});
});

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 = {
Expand Down
Loading