diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx
index 5db2839654a0..6f5c54325d88 100644
--- a/src/components/MoneyRequestHeader.tsx
+++ b/src/components/MoneyRequestHeader.tsx
@@ -78,7 +78,7 @@ function MoneyRequestHeader({
// Only the requestor can take delete the expense, admins can only edit it.
const isActionOwner = typeof parentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && parentReportAction.actorAccountID === session?.accountID;
const isPolicyAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;
- const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && (session?.accountID ?? null) === moneyRequestReport?.managerID;
+ const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID;
const deleteTransaction = useCallback(() => {
if (parentReportAction) {
diff --git a/src/components/ReceiptAudit.tsx b/src/components/ReceiptAudit.tsx
new file mode 100644
index 000000000000..ac1b36c6bf32
--- /dev/null
+++ b/src/components/ReceiptAudit.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import {View} from 'react-native';
+import useLocalize from '@hooks/useLocalize';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Icon from './Icon';
+import * as Expensicons from './Icon/Expensicons';
+import Text from './Text';
+
+function ReceiptAuditHeader({notes, shouldShowAuditMessage}: {notes: string[]; shouldShowAuditMessage: boolean}) {
+ const styles = useThemeStyles();
+ const theme = useTheme();
+ const {translate} = useLocalize();
+
+ const auditText = notes.length > 0 ? translate('iou.receiptIssuesFound', notes.length) : translate('common.verified');
+ return (
+
+
+ {translate('common.receipt')}
+ {shouldShowAuditMessage && (
+ <>
+ {` • ${auditText}`}
+
+ >
+ )}
+
+
+ );
+}
+
+function ReceiptAuditMessages({notes = []}: {notes?: string[]}) {
+ const styles = useThemeStyles();
+ return {notes.length > 0 && notes.map((message) => {message})};
+}
+
+export {ReceiptAuditHeader, ReceiptAuditMessages};
diff --git a/src/components/ReceiptEmptyState.tsx b/src/components/ReceiptEmptyState.tsx
index 9884e97a3fa0..71d64c7483f1 100644
--- a/src/components/ReceiptEmptyState.tsx
+++ b/src/components/ReceiptEmptyState.tsx
@@ -12,10 +12,12 @@ type ReceiptEmptyStateProps = {
/** Callback to be called on onPress */
onPress?: () => void;
+
+ disabled?: boolean;
};
// Returns an SVG icon indicating that the user should attach a receipt
-function ReceiptEmptyState({hasError = false, onPress = () => {}}: ReceiptEmptyStateProps) {
+function ReceiptEmptyState({hasError = false, onPress = () => {}, disabled = false}: ReceiptEmptyStateProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -24,6 +26,8 @@ function ReceiptEmptyState({hasError = false, onPress = () => {}}: ReceiptEmptyS
accessibilityRole="imagebutton"
accessibilityLabel={translate('receipt.upload')}
onPress={onPress}
+ disabled={disabled}
+ disabledStyle={styles.cursorDefault}
style={[styles.alignItemsCenter, styles.justifyContentCenter, styles.moneyRequestViewImage, styles.moneyRequestAttachReceipt, hasError && styles.borderColorDanger]}
>
);
+ const shouldShowMapOrReceipt = showMapAsImage || hasReceipt;
+ const shouldShowReceiptEmptyState = !hasReceipt && (canEditReceipt || isAdmin || isApprover);
+ const noticeTypeViolations = transactionViolations?.filter((violation) => violation.type === 'notice').map((v) => ViolationsUtils.getViolationTranslation(v, translate)) ?? [];
+ const shouldShowNotesViolations = !isReceiptBeingScanned && canUseViolations && ReportUtils.isPaidGroupPolicy(report);
+
return (
{shouldShowAnimatedBackground && }
- {/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */}
- {(showMapAsImage || hasReceipt) && (
+
+ {shouldShowMapOrReceipt && (
)}
- {!hasReceipt && canEditReceipt && (
+ {shouldShowReceiptEmptyState && (
Navigation.navigate(
ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(
@@ -375,6 +391,8 @@ function MoneyRequestView({
}
/>
)}
+ {!shouldShowReceiptEmptyState && !shouldShowMapOrReceipt && }
+ {shouldShowNotesViolations && }
{canUseViolations && }
violations.map((violation) => [violation.name, ViolationsUtils.getViolationTranslation(violation, translate)]), [translate, violations]);
return (
-
+
{violationMessages.map(([name, message]) => (
`${count === 1 ? 'Issue' : 'Issues'} found`,
fieldPending: 'Pending...',
defaultRate: 'Default rate',
receiptScanning: 'Scan in progress…',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index c7a1592c6d06..f132fe15027a 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -287,6 +287,7 @@ export default {
nonBillable: 'No facturable',
tag: 'Etiqueta',
receipt: 'Recibo',
+ verified: 'Verificado',
replace: 'Sustituir',
distance: 'Distancia',
mile: 'milla',
@@ -628,6 +629,7 @@ export default {
canceled: 'Canceló',
posted: 'Contabilizado',
deleteReceipt: 'Eliminar recibo',
+ receiptIssuesFound: (count: number) => `${count === 1 ? 'Problema encontrado' : 'Problemas encontrados'}`,
fieldPending: 'Pendiente...',
defaultRate: 'Tasa predeterminada',
receiptScanning: 'Escaneo en curso…',
diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts
index 73efe4083623..74bc15f32487 100644
--- a/src/libs/TransactionUtils.ts
+++ b/src/libs/TransactionUtils.ts
@@ -491,6 +491,10 @@ function isReceiptBeingScanned(transaction: OnyxEntry): boolean {
return [CONST.IOU.RECEIPT_STATE.SCANREADY, CONST.IOU.RECEIPT_STATE.SCANNING].some((value) => value === transaction?.receipt?.state);
}
+function didRceiptScanSucceed(transaction: OnyxEntry): boolean {
+ return [CONST.IOU.RECEIPT_STATE.SCANCOMPLETE].some((value) => value === transaction?.receipt?.state);
+}
+
/**
* Check if the transaction has a non-smartscanning receipt and is missing required fields
*/
@@ -606,6 +610,13 @@ function hasViolation(transactionID: string, transactionViolations: OnyxCollecti
);
}
+/**
+ * Checks if any violations for the provided transaction are of type 'notice'
+ */
+function hasNoticeTypeViolation(transactionID: string, transactionViolations: OnyxCollection): boolean {
+ return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'notice'));
+}
+
function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection): TransactionViolation[] | null {
return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null;
}
@@ -691,6 +702,7 @@ export {
hasEReceipt,
hasRoute,
isReceiptBeingScanned,
+ didRceiptScanSucceed,
getValidWaypoints,
isDistanceRequest,
isFetchingWaypointsFromServer,
@@ -711,6 +723,7 @@ export {
waypointHasValidAddress,
getRecentTransactions,
hasViolation,
+ hasNoticeTypeViolation,
isCustomUnitRateIDForP2P,
getRateID,
};