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
2 changes: 1 addition & 1 deletion src/components/LHNOptionsList/OptionRowLHNData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function OptionRowLHNData({

const optionItemRef = useRef<OptionData>();

const shouldDisplayViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(fullReport, transactionViolations);
const shouldDisplayViolations = ReportUtils.shouldDisplayTransactionThreadViolations(fullReport, transactionViolations, parentReportAction);
const shouldDisplayReportViolations = ReportUtils.isReportOwner(fullReport) && ReportUtils.hasReportViolations(reportID);

const optionItem = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ function MoneyRequestPreviewContent({
const isOnHold = TransactionUtils.isOnHold(transaction);
const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial;
const isPartialHold = isSettlementOrApprovalPartial && isOnHold;
const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '-1', transactionViolations, true);
const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '-1', transactionViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport);
const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '-1', transactionViolations);
const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '-1', transactionViolations) && ReportUtils.isPaidGroupPolicy(iouReport);
const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction);
const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction);
const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction);
Expand Down
5 changes: 2 additions & 3 deletions src/components/ReportActionItem/ReportPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,8 @@ function ReportPreview({
const hasErrors =
(hasMissingSmartscanFields && !iouSettled) ||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
ReportUtils.hasViolations(iouReportID, transactionViolations, true) ||
ReportUtils.hasNoticeTypeViolations(iouReportID, transactionViolations, true) ||
ReportUtils.hasWarningTypeViolations(iouReportID, transactionViolations, true) ||
ReportUtils.hasViolations(iouReportID, transactionViolations) ||
ReportUtils.hasWarningTypeViolations(iouReportID, transactionViolations) ||
(ReportUtils.isReportOwner(iouReport) && ReportUtils.hasReportViolations(iouReportID)) ||
ReportUtils.hasActionsWithErrors(iouReportID);
const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3);
Expand Down
2 changes: 1 addition & 1 deletion src/libs/DebugUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ function getReasonForShowingRowInLHN(report: OnyxEntry<Report>, hasRBR = false):
return null;
}

const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations);
const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations);

const reason = ReportUtils.reasonForReportToBeInOptionList({
report,
Expand Down
2 changes: 1 addition & 1 deletion src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1704,7 +1704,7 @@ function getOptions(
// Filter out all the reports that shouldn't be displayed
const filteredReportOptions = options.reports.filter((option) => {
const report = option.item;
const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations);
const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations);

return ReportUtils.shouldReportBeInOptionList({
report,
Expand Down
90 changes: 60 additions & 30 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6324,53 +6324,65 @@ function shouldHideReport(report: OnyxEntry<Report>, currentReportId: string): b
}

/**
* Should we display a RBR on the LHN on this report due to violations?
* Checks to see if a report's parentAction is an expense that contains a violation type of either violation or warning
*/
function shouldDisplayViolationsRBRInLHN(report: OnyxEntry<Report>, transactionViolations: OnyxCollection<TransactionViolation[]>): boolean {
// We only show the RBR in the highest level, which is the workspace chat
if (!report || !isPolicyExpenseChat(report)) {
function doesTransactionThreadHaveViolations(
report: OnyxInputOrEntry<Report>,
transactionViolations: OnyxCollection<TransactionViolation[]>,
parentReportAction: OnyxInputOrEntry<ReportAction>,
): boolean {
if (!ReportActionsUtils.isMoneyRequestAction(parentReportAction)) {
return false;
}

// We only show the RBR to the submitter
if (!isCurrentUserSubmitter(report.reportID ?? '')) {
const {IOUTransactionID, IOUReportID} = ReportActionsUtils.getOriginalMessage(parentReportAction) ?? {};
if (!IOUTransactionID || !IOUReportID) {
return false;
}

// Get all potential reports, which are the ones that are:
// - Owned by the same user
// - Are either open or submitted
// - Belong to the same workspace
// And if any have a violation, then it should have a RBR
const allReports = Object.values(ReportConnection.getAllReports() ?? {}) as Report[];
const potentialReports = allReports.filter((r) => r.ownerAccountID === currentUserAccountID && (r.stateNum ?? 0) <= 1 && r.policyID === report.policyID);
return potentialReports.some(
(potentialReport) => hasViolations(potentialReport.reportID, transactionViolations) || hasWarningTypeViolations(potentialReport.reportID, transactionViolations),
if (!isCurrentUserSubmitter(IOUReportID)) {
return false;
}
if (report?.stateNum !== CONST.REPORT.STATE_NUM.OPEN && report?.stateNum !== CONST.REPORT.STATE_NUM.SUBMITTED) {
return false;
}
return (
TransactionUtils.hasViolation(IOUTransactionID, transactionViolations) ||
TransactionUtils.hasWarningTypeViolation(IOUTransactionID, transactionViolations) ||
(isPaidGroupPolicy(report) && TransactionUtils.hasModifiedAmountOrDateViolation(IOUTransactionID, transactionViolations))
);
}

/**
* Checks to see if a report contains a violation
* Checks if we should display violation - we display violations when the expense has violation and it is not settled
*/
function hasViolations(reportID: string, transactionViolations: OnyxCollection<TransactionViolation[]>, shouldShowInReview?: boolean): boolean {
const transactions = reportsTransactions[reportID] ?? [];
return transactions.some((transaction) => TransactionUtils.hasViolation(transaction.transactionID, transactionViolations, shouldShowInReview));
function shouldDisplayTransactionThreadViolations(
report: OnyxEntry<Report>,
transactionViolations: OnyxCollection<TransactionViolation[]>,
parentReportAction: OnyxEntry<ReportAction>,
): boolean {
if (!ReportActionsUtils.isMoneyRequestAction(parentReportAction)) {
return false;
}
const {IOUReportID} = ReportActionsUtils.getOriginalMessage(parentReportAction) ?? {};
if (isSettled(IOUReportID) || isReportApproved(IOUReportID?.toString())) {
return false;
}
return doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction);
}

/**
* Checks to see if a report contains a violation of type `warning`
* Checks to see if a report contains a violation
*/
function hasWarningTypeViolations(reportID: string, transactionViolations: OnyxCollection<TransactionViolation[]>, shouldShowInReview?: boolean): boolean {
function hasViolations(reportID: string, transactionViolations: OnyxCollection<TransactionViolation[]>): boolean {
const transactions = reportsTransactions[reportID] ?? [];
return transactions.some((transaction) => TransactionUtils.hasWarningTypeViolation(transaction.transactionID, transactionViolations, shouldShowInReview));
return transactions.some((transaction) => TransactionUtils.hasViolation(transaction.transactionID, transactionViolations));
}

/**
* Checks to see if a report contains a violation of type `notice`
* Checks to see if a report contains a violation of type `warning`
*/
function hasNoticeTypeViolations(reportID: string, transactionViolations: OnyxCollection<TransactionViolation[]>, shouldShowInReview?: boolean): boolean {
function hasWarningTypeViolations(reportID: string, transactionViolations: OnyxCollection<TransactionViolation[]>): boolean {
const transactions = reportsTransactions[reportID] ?? [];
return transactions.some((transaction) => TransactionUtils.hasNoticeTypeViolation(transaction.transactionID, transactionViolations, shouldShowInReview));
return transactions.some((transaction) => TransactionUtils.hasWarningTypeViolation(transaction.transactionID, transactionViolations));
}

function hasReportViolations(reportID: string) {
Expand All @@ -6392,6 +6404,23 @@ function shouldAdminsRoomBeVisible(report: OnyxEntry<Report>): boolean {
return true;
}

/**
* Check whether report has violations
*/
function shouldShowViolations(report: Report, transactionViolations: OnyxCollection<TransactionViolation[]>) {
const {parentReportID, parentReportActionID} = report ?? {};
const canGetParentReport = parentReportID && parentReportActionID && allReportActions;
if (!canGetParentReport) {
return false;
}
const parentReportActions = allReportActions ? allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`] ?? {} : {};
const parentReportAction = parentReportActions[parentReportActionID] ?? null;
if (!parentReportAction) {
return false;
}
return shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction);
}

type ReportErrorsAndReportActionThatRequiresAttention = {
errors: ErrorFields;
reportAction?: OnyxEntry<ReportAction>;
Expand Down Expand Up @@ -6474,7 +6503,7 @@ function hasReportErrorsOtherThanFailedReceipt(report: Report, doesReportHaveVio
let doesTransactionThreadReportHasViolations = false;
if (oneTransactionThreadReportID) {
const transactionReport = getReport(oneTransactionThreadReportID);
doesTransactionThreadReportHasViolations = !!transactionReport && shouldDisplayViolationsRBRInLHN(transactionReport, transactionViolations);
doesTransactionThreadReportHasViolations = !!transactionReport && shouldShowViolations(transactionReport, transactionViolations);
}
return (
doesTransactionThreadReportHasViolations ||
Expand Down Expand Up @@ -8468,6 +8497,7 @@ export {
chatIncludesConcierge,
createDraftTransactionAndNavigateToParticipantSelector,
doesReportBelongToWorkspace,
doesTransactionThreadHaveViolations,
findLastAccessedReport,
findSelfDMReportID,
formatReportLastMessageText,
Expand Down Expand Up @@ -8571,7 +8601,6 @@ export {
hasUpdatedTotal,
hasViolations,
hasWarningTypeViolations,
hasNoticeTypeViolations,
isActionCreator,
isAdminRoom,
isAdminsOnlyPostingRoom,
Expand Down Expand Up @@ -8673,7 +8702,7 @@ export {
shouldDisableRename,
shouldDisableThread,
shouldDisplayThreadReplies,
shouldDisplayViolationsRBRInLHN,
shouldDisplayTransactionThreadViolations,
shouldReportBeInOptionList,
shouldReportShowSubscript,
shouldShowFlagComment,
Expand Down Expand Up @@ -8721,6 +8750,7 @@ export {
buildOptimisticChangeFieldAction,
isPolicyRelatedReport,
hasReportErrorsOtherThanFailedReceipt,
shouldShowViolations,
getAllReportErrors,
getAllReportActionsErrorsAndReportActionThatRequiresAttention,
hasInvoiceReports,
Expand Down
21 changes: 16 additions & 5 deletions src/libs/SidebarUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function getOrderedReportIDs(
return;
}
const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1');
const doesReportHaveViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations);
const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations);
const isHidden = ReportUtils.getReportNotificationPreference(report) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
const isFocused = report.reportID === currentReportId;
const hasErrorsOtherThanFailedReceipt = ReportUtils.hasReportErrorsOtherThanFailedReceipt(report, doesReportHaveViolations, transactionViolations);
Expand Down Expand Up @@ -239,11 +239,22 @@ function getReasonAndReportActionThatHasRedBrickRoad(
): ReasonAndReportActionThatHasRedBrickRoad | null {
const {errors, reportAction} = ReportUtils.getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions);
const hasErrors = Object.keys(errors).length !== 0;
const oneTransactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, ReportActionsUtils.getAllReportActions(report.reportID));

if (ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations)) {
return {
reason: CONST.RBR_REASONS.HAS_TRANSACTION_THREAD_VIOLATIONS,
};
if (oneTransactionThreadReportID) {
const oneTransactionThreadReport = ReportUtils.getReport(oneTransactionThreadReportID);

if (
ReportUtils.shouldDisplayTransactionThreadViolations(
oneTransactionThreadReport,
transactionViolations,
ReportActionsUtils.getAllReportActions(report.reportID)[oneTransactionThreadReport?.parentReportActionID ?? '-1'],
)
) {
return {
reason: CONST.RBR_REASONS.HAS_TRANSACTION_THREAD_VIOLATIONS,
};
}
}

if (hasErrors) {
Expand Down
33 changes: 19 additions & 14 deletions src/libs/TransactionUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -866,37 +866,41 @@ function isOnHoldByTransactionID(transactionID: string): boolean {
/**
* Checks if any violations for the provided transaction are of type 'violation'
*/
function hasViolation(transactionID: string, transactionViolations: OnyxCollection<TransactionViolations>, showInReview?: boolean): boolean {
function hasViolation(transactionID: string, transactionViolations: OnyxCollection<TransactionViolations>): boolean {
return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some(
(violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && (showInReview === undefined || showInReview === (violation.showInReview ?? false)),
(violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION,
);
}

/**
* Checks if any violations for the provided transaction are of type 'notice'
*/
function hasNoticeTypeViolation(transactionID: string, transactionViolations: OnyxCollection<TransactionViolation[]>, showInReview?: boolean): boolean {
return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some(
(violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && (showInReview === undefined || showInReview === (violation.showInReview ?? false)),
);
function hasNoticeTypeViolation(transactionID: string, transactionViolations: OnyxCollection<TransactionViolation[]>): boolean {
return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE);
}

/**
* Checks if any violations for the provided transaction are of type 'warning'
*/
function hasWarningTypeViolation(transactionID: string, transactionViolations: OnyxCollection<TransactionViolation[]>, showInReview?: boolean | null): boolean {
const violations = transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID];
const warningTypeViolations =
violations?.filter(
(violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING && (showInReview === null || showInReview === (violation.showInReview ?? false)),
) ?? [];

function hasWarningTypeViolation(transactionID: string, transactionViolations: OnyxCollection<TransactionViolation[]>): boolean {
const warningTypeViolations = transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter(
(violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING,
);
const hasOnlyDupeDetectionViolation = warningTypeViolations?.every((violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION);
if (!Permissions.canUseDupeDetection(allBetas ?? []) && hasOnlyDupeDetectionViolation) {
return false;
}

return warningTypeViolations.length > 0;
return !!warningTypeViolations && warningTypeViolations.length > 0;
}

/**
* Checks if any violations for the provided transaction are of modifiedAmount or modifiedDate
*/
function hasModifiedAmountOrDateViolation(transactionID: string, transactionViolations: OnyxCollection<TransactionViolation[]>): boolean {
return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some(
(violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.MODIFIED_AMOUNT || violation.name === CONST.VIOLATIONS.MODIFIED_DATE,
);
}

/**
Expand Down Expand Up @@ -1288,6 +1292,7 @@ export {
shouldShowBrokenConnectionViolation,
hasNoticeTypeViolation,
hasWarningTypeViolation,
hasModifiedAmountOrDateViolation,
isCustomUnitRateIDForP2P,
getRateID,
getTransaction,
Expand Down
8 changes: 7 additions & 1 deletion src/libs/WorkspacesSettingsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,13 @@ const getBrickRoadForPolicy = (report: Report, altReportActions?: OnyxCollection
if (oneTransactionThreadReportID && !doesReportContainErrors) {
const oneTransactionThreadReport = ReportUtils.getReport(oneTransactionThreadReportID);

if (ReportUtils.shouldDisplayViolationsRBRInLHN(oneTransactionThreadReport, allTransactionViolations)) {
if (
ReportUtils.shouldDisplayTransactionThreadViolations(
oneTransactionThreadReport,
allTransactionViolations,
reportActions[oneTransactionThreadReport?.parentReportActionID ?? '-1'],
)
) {
doesReportContainErrors = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/pages/Debug/Report/DebugReportPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@ function DebugReportPage({
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`);
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID ?? '-1'}`);
const parentReportAction = parentReportActions && report?.parentReportID ? parentReportActions[report?.parentReportActionID ?? '-1'] : undefined;

const metadata = useMemo<Metadata[]>(() => {
if (!report) {
return [];
}

const shouldDisplayViolations = ReportUtils.shouldDisplayViolationsRBRInLHN(report, transactionViolations);
const shouldDisplayViolations = ReportUtils.shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction);
const shouldDisplayReportViolations = ReportUtils.isReportOwner(report) && ReportUtils.hasReportViolations(reportID);
const hasViolations = !!shouldDisplayViolations || shouldDisplayReportViolations;
const {reason: reasonGBR, reportAction: reportActionGBR} = DebugUtils.getReasonAndReportActionForGBRInLHNRow(report) ?? {};
Expand Down Expand Up @@ -111,7 +113,7 @@ function DebugReportPage({
: undefined,
},
];
}, [report, reportActions, reportID, transactionViolations, translate]);
}, [parentReportAction, report, reportActions, reportID, transactionViolations, translate]);

if (!report) {
return <NotFoundPage />;
Expand Down
3 changes: 0 additions & 3 deletions src/types/onyx/TransactionViolation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,6 @@ type TransactionViolation = {

/** Additional violation information to provide the user */
data?: TransactionViolationData;

/** Indicates if this violation should be shown in review */
showInReview?: boolean;
};

/** Collection of transaction violations */
Expand Down
Loading