Skip to content

Commit

Permalink
Merge pull request #37813 from Krishna2323/krishna2323/feat/36288
Browse files Browse the repository at this point in the history
  • Loading branch information
cead22 authored Apr 30, 2024
2 parents 65d85fd + 22e6fcb commit bfa9990
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
42 changes: 42 additions & 0 deletions src/components/ReceiptAudit.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<View style={[styles.ph5, styles.mbn1]}>
<View style={[styles.flexRow, styles.alignItemsCenter]}>
<Text style={[styles.textLabelSupporting]}>{translate('common.receipt')}</Text>
{shouldShowAuditMessage && (
<>
<Text style={[styles.textLabelSupporting]}>{` • ${auditText}`}</Text>
<Icon
width={12}
height={12}
src={notes.length ? Expensicons.DotIndicator : Expensicons.Checkmark}
fill={notes.length ? theme.danger : theme.success}
additionalStyles={styles.ml1}
/>
</>
)}
</View>
</View>
);
}

function ReceiptAuditMessages({notes = []}: {notes?: string[]}) {
const styles = useThemeStyles();
return <View style={[styles.mtn1, styles.mb2, styles.ph5, styles.gap1]}>{notes.length > 0 && notes.map((message) => <Text style={[styles.textLabelError]}>{message}</Text>)}</View>;
}

export {ReceiptAuditHeader, ReceiptAuditMessages};
6 changes: 5 additions & 1 deletion src/components/ReceiptEmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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]}
>
<Icon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,16 @@ function MoneyRequestPreviewContent({
const isSettlementOrApprovalPartial = Boolean(iouReport?.pendingFields?.partial);
const isPartialHold = isSettlementOrApprovalPartial && isOnHold;
const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '', transactionViolations);
const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '', transactionViolations);
const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction);
const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction);
const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction);
const isCardTransaction = TransactionUtils.isCardTransaction(transaction);
const isSettled = ReportUtils.isSettled(iouReport?.reportID);
const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
const shouldShowRBR =
hasViolations || hasFieldErrors || (!(isSettled && !isSettlementOrApprovalPartial) && !(ReportUtils.isReportApproved(iouReport) && !isSettlementOrApprovalPartial) && isOnHold);
const isFullySettled = isSettled && !isSettlementOrApprovalPartial;
const isFullyApproved = ReportUtils.isReportApproved(iouReport) && !isSettlementOrApprovalPartial;
const shouldShowRBR = hasNoticeTypeViolations || hasViolations || hasFieldErrors || (!isFullySettled && !isFullyApproved && isOnHold);

/*
Show the merchant for IOUs and expenses only if:
Expand Down Expand Up @@ -178,6 +180,8 @@ function MoneyRequestPreviewContent({
} else if (!(isSettled && !isSettlementOrApprovalPartial) && isOnHold) {
message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.hold')}`;
}
} else if (hasNoticeTypeViolations && transaction && !ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID)) {
message += ` • ${translate('violations.reviewRequired')}`;
} else if (ReportUtils.isPaidGroupPolicyExpenseReport(iouReport) && ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID) && !isPartialHold) {
message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.approved')}`;
} else if (iouReport?.isWaitingOnBankAccount) {
Expand Down
28 changes: 23 additions & 5 deletions src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import ConfirmedRoute from '@components/ConfirmedRoute';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import {useSession} from '@components/OnyxProvider';
import {ReceiptAuditHeader, ReceiptAuditMessages} from '@components/ReceiptAudit';
import ReceiptEmptyState from '@components/ReceiptEmptyState';
import SpacerView from '@components/SpacerView';
import Switch from '@components/Switch';
Expand Down Expand Up @@ -99,6 +101,7 @@ function MoneyRequestView({
}: MoneyRequestViewProps) {
const theme = useTheme();
const styles = useThemeStyles();
const session = useSession();
const StyleUtils = useStyleUtils();
const {isOffline} = useNetwork();
const {isSmallScreenWidth} = useWindowDimensions();
Expand All @@ -125,7 +128,7 @@ function MoneyRequestView({
const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction);
const formattedTransactionAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : '';
const hasPendingWaypoints = transaction?.pendingFields?.waypoints;
const hasPendingWaypoints = Boolean(transaction?.pendingFields?.waypoints);
const showMapAsImage = isDistanceRequest && hasPendingWaypoints;
const formattedOriginalAmount = transactionOriginalAmount && transactionOriginalCurrency && CurrencyUtils.convertToDisplayString(transactionOriginalAmount, transactionOriginalCurrency);
const isCardTransaction = TransactionUtils.isCardTransaction(transaction);
Expand All @@ -151,9 +154,14 @@ function MoneyRequestView({
const canEditMerchant = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.MERCHANT);
const canEditDate = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE);
const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT);
const hasReceipt = TransactionUtils.hasReceipt(transaction);
const isReceiptBeingScanned = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction);
const didRceiptScanSucceed = hasReceipt && TransactionUtils.didRceiptScanSucceed(transaction);
// TODO: remove the !isTrackExpense from this condition after this fix: https://github.com/Expensify/Expensify/issues/382786
const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE) && !isTrackExpense;

const isAdmin = policy?.role === 'admin';
const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID;
// A flag for verifying that the current report is a sub-report of a workspace chat
// if the policy of the report is either Collect or Control, then this report must be tied to workspace chat
const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report);
Expand Down Expand Up @@ -238,7 +246,6 @@ function MoneyRequestView({
}
}

const hasReceipt = TransactionUtils.hasReceipt(transaction);
let receiptURIs;
const hasErrors = canEdit && TransactionUtils.hasMissingSmartscanFields(transaction);
if (hasReceipt) {
Expand Down Expand Up @@ -324,12 +331,20 @@ function MoneyRequestView({
</OfflineWithFeedback>
);

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 (
<View style={[StyleUtils.getReportWelcomeContainerStyle(isSmallScreenWidth, true, shouldShowAnimatedBackground)]}>
{shouldShowAnimatedBackground && <AnimatedEmptyStateBackground />}
<View style={shouldShowAnimatedBackground && [StyleUtils.getReportWelcomeTopMarginStyle(isSmallScreenWidth, true)]}>
{/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */}
{(showMapAsImage || hasReceipt) && (
<ReceiptAuditHeader
notes={noticeTypeViolations}
shouldShowAuditMessage={Boolean(shouldShowNotesViolations && didRceiptScanSucceed)}
/>
{shouldShowMapOrReceipt && (
<OfflineWithFeedback
pendingAction={pendingAction}
errors={transaction?.errors}
Expand Down Expand Up @@ -359,9 +374,10 @@ function MoneyRequestView({
</View>
</OfflineWithFeedback>
)}
{!hasReceipt && canEditReceipt && (
{shouldShowReceiptEmptyState && (
<ReceiptEmptyState
hasError={hasErrors}
disabled={!canEditReceipt}
onPress={() =>
Navigation.navigate(
ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(
Expand All @@ -375,6 +391,8 @@ function MoneyRequestView({
}
/>
)}
{!shouldShowReceiptEmptyState && !shouldShowMapOrReceipt && <View style={{marginVertical: 6}} />}
{shouldShowNotesViolations && <ReceiptAuditMessages notes={noticeTypeViolations} />}
{canUseViolations && <ViolationMessages violations={getViolationsForField('receipt')} />}
<OfflineWithFeedback pendingAction={getPendingFieldAction('amount')}>
<MenuItemWithTopDescription
Expand Down
2 changes: 1 addition & 1 deletion src/components/ViolationMessages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function ViolationMessages({violations, isLast, containerStyle, t
const violationMessages = useMemo(() => violations.map((violation) => [violation.name, ViolationsUtils.getViolationTranslation(violation, translate)]), [translate, violations]);

return (
<View style={[styles.mtn2, isLast ? styles.mb2 : styles.mb1, containerStyle]}>
<View style={[styles.mtn1, isLast ? styles.mb2 : styles.mb1, containerStyle, styles.gap1]}>
{violationMessages.map(([name, message]) => (
<Text
key={`violationMessages.${name}`}
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ export default {
nonBillable: 'Non-billable',
tag: 'Tag',
receipt: 'Receipt',
verified: 'Verified',
replace: 'Replace',
distance: 'Distance',
mile: 'mile',
Expand Down Expand Up @@ -635,6 +636,7 @@ export default {
canceled: 'Canceled',
posted: 'Posted',
deleteReceipt: 'Delete receipt',
receiptIssuesFound: (count: number) => `${count === 1 ? 'Issue' : 'Issues'} found`,
fieldPending: 'Pending...',
defaultRate: 'Default rate',
receiptScanning: 'Scan in progress…',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ export default {
nonBillable: 'No facturable',
tag: 'Etiqueta',
receipt: 'Recibo',
verified: 'Verificado',
replace: 'Sustituir',
distance: 'Distancia',
mile: 'milla',
Expand Down Expand Up @@ -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…',
Expand Down
13 changes: 13 additions & 0 deletions src/libs/TransactionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,10 @@ function isReceiptBeingScanned(transaction: OnyxEntry<Transaction>): boolean {
return [CONST.IOU.RECEIPT_STATE.SCANREADY, CONST.IOU.RECEIPT_STATE.SCANNING].some((value) => value === transaction?.receipt?.state);
}

function didRceiptScanSucceed(transaction: OnyxEntry<Transaction>): 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
*/
Expand Down Expand Up @@ -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<TransactionViolation[]>): boolean {
return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'notice'));
}

function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection<TransactionViolation[]>): TransactionViolation[] | null {
return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null;
}
Expand Down Expand Up @@ -691,6 +702,7 @@ export {
hasEReceipt,
hasRoute,
isReceiptBeingScanned,
didRceiptScanSucceed,
getValidWaypoints,
isDistanceRequest,
isFetchingWaypointsFromServer,
Expand All @@ -711,6 +723,7 @@ export {
waypointHasValidAddress,
getRecentTransactions,
hasViolation,
hasNoticeTypeViolation,
isCustomUnitRateIDForP2P,
getRateID,
};
Expand Down

0 comments on commit bfa9990

Please sign in to comment.