Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Wave 8 - ECard Transactions] Show EReceipts in ReportPreviews and Attachment View #29474

Merged
merged 17 commits into from
Oct 17, 2023
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
5 changes: 5 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,11 @@ export default {
I_AM_A_TEACHER: 'teachersunite/i-am-a-teacher',
INTRO_SCHOOL_PRINCIPAL: 'teachersunite/intro-school-principal',

ERECEIPT: {
route: 'eReceipt/:transactionID',
getRoute: (transactionID: string) => `eReceipt/${transactionID}`,
},

WORKSPACE_NEW: 'workspace/new',
WORKSPACE_NEW_ROOM: 'workspace/new-room',
WORKSPACE_INITIAL: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function extractAttachmentsFromReport(report, reportActions) {

const transaction = TransactionUtils.getTransaction(transactionID);
if (TransactionUtils.hasReceipt(transaction)) {
const {image} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename);
const {image} = ReceiptUtils.getThumbnailAndImageURIs(transaction);
attachments.unshift({
source: tryResolveUrlFromApiRoot(image),
isAuthTokenRequired: true,
Expand Down
16 changes: 15 additions & 1 deletion src/components/Attachments/AttachmentView/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {memo, useState} from 'react';
import {View, ActivityIndicator} from 'react-native';
import {View, ScrollView, ActivityIndicator} from 'react-native';
import _ from 'underscore';
import PropTypes from 'prop-types';
import Str from 'expensify-common/lib/str';
Expand All @@ -22,6 +22,7 @@ import * as TransactionUtils from '../../../libs/TransactionUtils';
import DistanceEReceipt from '../../DistanceEReceipt';
import useNetwork from '../../../hooks/useNetwork';
import ONYXKEYS from '../../../ONYXKEYS';
import EReceipt from '../../EReceipt';

const propTypes = {
...attachmentViewPropTypes,
Expand Down Expand Up @@ -101,6 +102,19 @@ function AttachmentView({
);
}

if (TransactionUtils.hasEReceipt(transaction)) {
return (
<View style={[styles.flex1, styles.alignItemsCenter]}>
<ScrollView
style={styles.w100}
contentContainerStyle={[styles.flexGrow1, styles.justifyContentCenter, styles.alignItemsCenter]}
>
<EReceipt transactionID={transaction.transactionID} />
</ScrollView>
</View>
);
}

// Check both source and file.name since PDFs dragged into the text field
// will appear with a source that is a blob
if ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/EReceipt.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function EReceipt({transaction, transactionID}) {
</View>
<View style={[styles.flexColumn, styles.justifyContentBetween, styles.alignItemsCenter, styles.ph9, styles.flex1]}>
<View style={[styles.alignItemsCenter, styles.alignSelfCenter, styles.flexColumn, styles.gap2, styles.mb8]}>
<View style={[styles.flexRow, styles.justifyContentCenter]}>
<View style={[styles.flexRow, styles.justifyContentCenter, StyleUtils.getWidthStyle(variables.eReceiptTextContainerWidth)]}>
<View style={[styles.flexColumn, styles.pt1]}>
<Text style={[styles.eReceiptCurrency, secondaryTextColorStyle]}>{currency}</Text>
</View>
Expand Down
3 changes: 1 addition & 2 deletions src/components/MoneyRequestConfirmationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,7 @@ function MoneyRequestConfirmationList(props) {
);
}, [confirm, props.bankAccountRoute, props.iouCurrencyCode, props.iouType, props.isReadOnly, props.policyID, selectedParticipants, splitOrRequestOptions, translate, formError]);

const {image: receiptImage, thumbnail: receiptThumbnail} =
props.receiptPath && props.receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(props.receiptPath, props.receiptFilename) : {};
const {image: receiptImage, thumbnail: receiptThumbnail} = props.receiptPath && props.receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction) : {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This caused a regression in #29855 , When creating a money request, the receipt image details are not saved in the transaction. Instead, they are stored in the IOU object. As a result, a placeholder is shown instead of the receipt image.


return (
<OptionsSelector
Expand Down
2 changes: 1 addition & 1 deletion src/components/ReportActionItem/MoneyRequestPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ function MoneyRequestPreview(props) {
!_.isEmpty(requestMerchant) && !props.isBillSplit && requestMerchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && requestMerchant !== CONST.TRANSACTION.DEFAULT_MERCHANT;
const shouldShowDescription = !_.isEmpty(description) && !shouldShowMerchant;

const receiptImages = hasReceipt ? [ReceiptUtils.getThumbnailAndImageURIs(props.transaction.receipt.source, props.transaction.filename || '')] : [];
const receiptImages = hasReceipt ? [ReceiptUtils.getThumbnailAndImageURIs(props.transaction)] : [];

const getSettledMessage = () => {
if (isExpensifyCardTransaction) {
Expand Down
3 changes: 2 additions & 1 deletion src/components/ReportActionItem/MoneyRequestView.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should
let receiptURIs;
let hasErrors = false;
if (hasReceipt) {
receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename);
receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction);
hasErrors = canEdit && TransactionUtils.hasMissingSmartscanFields(transaction);
}

Expand All @@ -170,6 +170,7 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should
<ReportActionItemImage
thumbnail={receiptURIs.thumbnail}
image={receiptURIs.image}
transaction={transaction}
enablePreviewModal
/>
</View>
Expand Down
50 changes: 36 additions & 14 deletions src/components/ReportActionItem/ReportActionItemImage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import _ from 'underscore';
import styles from '../../styles/styles';
import Image from '../Image';
import ThumbnailImage from '../ThumbnailImage';
Expand All @@ -10,6 +12,9 @@ import {ShowContextMenuContext} from '../ShowContextMenuContext';
import Navigation from '../../libs/Navigation/Navigation';
import PressableWithoutFocus from '../Pressable/PressableWithoutFocus';
import useLocalize from '../../hooks/useLocalize';
import EReceiptThumbnail from '../EReceiptThumbnail';
import transactionPropTypes from '../transactionPropTypes';
import * as TransactionUtils from '../../libs/TransactionUtils';

const propTypes = {
/** thumbnail URI for the image */
Expand All @@ -20,10 +25,14 @@ const propTypes = {

/** whether or not to enable the image preview modal */
enablePreviewModal: PropTypes.bool,

/* The transaction associated with this image, if any. Passed for handling eReceipts. */
transaction: transactionPropTypes,
};

const defaultProps = {
thumbnail: null,
transaction: {},
enablePreviewModal: false,
};

Expand All @@ -33,24 +42,37 @@ const defaultProps = {
* and optional preview modal as well.
*/

function ReportActionItemImage({thumbnail, image, enablePreviewModal}) {
function ReportActionItemImage({thumbnail, image, enablePreviewModal, transaction}) {
const {translate} = useLocalize();
const imageSource = tryResolveUrlFromApiRoot(image || '');
const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || '');
const isEReceipt = !_.isEmpty(transaction) && TransactionUtils.hasEReceipt(transaction);

let receiptImageComponent;

const receiptImageComponent = thumbnail ? (
<ThumbnailImage
previewSourceURL={thumbnailSource}
style={[styles.w100, styles.h100]}
isAuthTokenRequired
shouldDynamicallyResize={false}
/>
) : (
<Image
source={{uri: image}}
style={[styles.w100, styles.h100]}
/>
);
if (isEReceipt) {
receiptImageComponent = (
<View style={[styles.w100, styles.h100]}>
<EReceiptThumbnail transactionID={transaction.transactionID} />
</View>
);
} else if (thumbnail) {
receiptImageComponent = (
<ThumbnailImage
previewSourceURL={thumbnailSource}
style={[styles.w100, styles.h100]}
isAuthTokenRequired
shouldDynamicallyResize={false}
/>
);
} else {
receiptImageComponent = (
<Image
source={{uri: image}}
style={[styles.w100, styles.h100]}
/>
);
}

if (enablePreviewModal) {
return (
Expand Down
5 changes: 4 additions & 1 deletion src/components/ReportActionItem/ReportActionItemImages.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import Text from '../Text';
import ReportActionItemImage from './ReportActionItemImage';
import * as StyleUtils from '../../styles/StyleUtils';
import variables from '../../styles/variables';
import transactionPropTypes from '../transactionPropTypes';

const propTypes = {
/** array of image and thumbnail URIs */
images: PropTypes.arrayOf(
PropTypes.shape({
thumbnail: PropTypes.string,
image: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
transaction: transactionPropTypes,
}),
).isRequired,

Expand Down Expand Up @@ -68,7 +70,7 @@ function ReportActionItemImages({images, size, total, isHovered}) {

return (
<View style={[styles.reportActionItemImages, hoverStyle, heightStyle]}>
{_.map(shownImages, ({thumbnail, image}, index) => {
{_.map(shownImages, ({thumbnail, image, transaction}, index) => {
const isLastImage = index === numberOfShownImages - 1;

// Show a border to separate multiple images. Shown to the right for each except the last.
Expand All @@ -82,6 +84,7 @@ function ReportActionItemImages({images, size, total, isHovered}) {
<ReportActionItemImage
thumbnail={thumbnail}
image={image}
transaction={transaction}
/>
{isLastImage && remaining > 0 && (
<View style={[styles.reportActionItemImagesMoreContainer]}>
Expand Down
4 changes: 2 additions & 2 deletions src/components/ReportActionItem/ReportPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ function ReportPreview(props) {
const hasReceipts = transactionsWithReceipts.length > 0;
const isScanning = hasReceipts && ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action);
const hasErrors = hasReceipts && ReportUtils.hasMissingSmartscanFields(props.iouReportID);
const lastThreeTransactionsWithReceipts = ReportUtils.getReportPreviewDisplayTransactions(props.action);
const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, ({receipt, filename}) => ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename || ''));
const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3);
const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, (transaction) => ReceiptUtils.getThumbnailAndImageURIs(transaction));
const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(props.iouReportID);
const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts;
const previewSubtitle = hasOnlyOneReceiptRequest
Expand Down
2 changes: 1 addition & 1 deletion src/libs/CardUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function getCardDescription(cardID: number) {
return '';
}
const cardDescriptor = card.state === CONST.EXPENSIFY_CARD.STATE.NOT_ACTIVATED ? Localize.translateLocal('cardTransactions.notActivated') : card.lastFourPAN;
return `${card.bank} - ${cardDescriptor}`;
return cardDescriptor ? `${card.bank} - ${cardDescriptor}` : `${card.bank}`;
grgia marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
18 changes: 15 additions & 3 deletions src/libs/ReceiptUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import ReceiptHTML from '../../assets/images/receipt-html.png';
import ReceiptDoc from '../../assets/images/receipt-doc.png';
import ReceiptGeneric from '../../assets/images/receipt-generic.png';
import ReceiptSVG from '../../assets/images/receipt-svg.png';
import {Transaction} from '../types/onyx';
import ROUTES from '../ROUTES';

type ThumbnailAndImageURI = {
image: ImageSourcePropType | string;
thumbnail: string | null;
transaction?: Transaction;
};

type FileNameAndExtension = {
Expand All @@ -20,12 +23,21 @@ type FileNameAndExtension = {
/**
* Grab the appropriate receipt image and thumbnail URIs based on file type
*
* @param path URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg
* @param filename of uploaded image or last part of remote URI
* @param transaction
*/
function getThumbnailAndImageURIs(path: string, filename: string): ThumbnailAndImageURI {
function getThumbnailAndImageURIs(transaction: Transaction): ThumbnailAndImageURI {
// URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg
const path = transaction?.receipt?.source ?? '';
// filename of uploaded image or last part of remote URI
const filename = transaction?.filename ?? '';
const isReceiptImage = Str.isImage(filename);

const hasEReceipt = transaction?.hasEReceipt;

if (hasEReceipt) {
return {thumbnail: null, image: ROUTES.ERECEIPT.getRoute(transaction.transactionID), transaction};
}

// For local files, we won't have a thumbnail yet
if (isReceiptImage && (path.startsWith('blob:') || path.startsWith('file:'))) {
return {thumbnail: null, image: path};
Expand Down
24 changes: 0 additions & 24 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3800,29 +3800,6 @@ function getParticipantsIDs(report) {
return participants;
}

/**
* Get the last 3 transactions with receipts of an IOU report that will be displayed on the report preview
*
* @param {Object} reportPreviewAction
* @returns {Object}
*/
function getReportPreviewDisplayTransactions(reportPreviewAction) {
const transactionIDs = lodashGet(reportPreviewAction, ['childRecentReceiptTransactionIDs']);
return _.reduce(
_.keys(transactionIDs),
(transactions, transactionID) => {
if (transactionIDs[transactionID] !== null) {
const transaction = TransactionUtils.getTransaction(transactionID);
if (TransactionUtils.hasReceipt(transaction)) {
transactions.push(transaction);
}
}
return transactions;
},
[],
);
}

/**
* Return iou report action display message
*
Expand Down Expand Up @@ -4010,7 +3987,6 @@ export {
canEditMoneyRequest,
buildTransactionThread,
areAllRequestsBeingSmartScanned,
getReportPreviewDisplayTransactions,
getTransactionsWithReceipts,
hasNonReimbursableTransactions,
hasMissingSmartscanFields,
Expand Down
18 changes: 9 additions & 9 deletions src/libs/TransactionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,15 @@ function buildOptimisticTransaction(
};
}

/**
* Check if the transaction has an Ereceipt
*/
function hasEReceipt(transaction: Transaction | undefined | null): boolean {
return !!transaction?.hasEReceipt;
}

function hasReceipt(transaction: Transaction | undefined | null): boolean {
return !!transaction?.receipt?.state;
return !!transaction?.receipt?.state || hasEReceipt(transaction);
}

function isMerchantMissing(transaction: Transaction) {
Expand Down Expand Up @@ -365,13 +372,6 @@ function hasRoute(transaction: Transaction): boolean {
return !!transaction?.routes?.route0?.geometry?.coordinates;
}

/**
* Check if the transaction has an Ereceipt
*/
function hasEreceipt(transaction: Transaction): boolean {
return !!transaction?.hasEReceipt;
}

/**
* Get the transactions related to a report preview with receipts
* Get the details linked to the IOU reportAction
Expand Down Expand Up @@ -472,7 +472,7 @@ export {
getLinkedTransaction,
getAllReportTransactions,
hasReceipt,
hasEreceipt,
hasEReceipt,
hasRoute,
isReceiptBeingScanned,
getValidWaypoints,
Expand Down
Loading
Loading