diff --git a/src/CONST.js b/src/CONST.js index 4f304f08e320..14d51d8536a1 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -1061,6 +1061,9 @@ const CONST = { DELETE: 'delete', }, AMOUNT_MAX_LENGTH: 10, + RECEIPT_STATE: { + SCANREADY: 'SCANREADY', + }, FILE_TYPES: { HTML: 'html', DOC: 'doc', diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 91c35d4cd956..3aaa2ccca2a4 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1762,9 +1762,22 @@ function getIOUReportActionMessage(type, total, comment, currency, paymentType = * @param {String} [iouReportID] - Only required if the IOUReportActions type is oneOf(decline, cancel, pay). Generates a randomID as default. * @param {Boolean} [isSettlingUp] - Whether we are settling up an IOU. * @param {Boolean} [isSendMoneyFlow] - Whether this is send money flow + * @param {Object} [receipt] * @returns {Object} */ -function buildOptimisticIOUReportAction(type, amount, currency, comment, participants, transactionID, paymentType = '', iouReportID = '', isSettlingUp = false, isSendMoneyFlow = false) { +function buildOptimisticIOUReportAction( + type, + amount, + currency, + comment, + participants, + transactionID, + paymentType = '', + iouReportID = '', + isSettlingUp = false, + isSendMoneyFlow = false, + receipt = {}, +) { const IOUReportID = iouReportID || generateReportID(); const originalMessage = { @@ -1810,6 +1823,7 @@ function buildOptimisticIOUReportAction(type, amount, currency, comment, partici shouldShow: true, created: DateUtils.getDBTime(), pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + receipt, }; } diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 266720bc631f..f88f53467ae8 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -12,9 +12,10 @@ import * as NumberUtils from './NumberUtils'; * @param {String} [source] * @param {String} [originalTransactionID] * @param {String} [merchant] + * @param {Object} [receipt] * @returns {Object} */ -function buildOptimisticTransaction(amount, currency, reportID, comment = '', source = '', originalTransactionID = '', merchant = CONST.REPORT.TYPE.IOU) { +function buildOptimisticTransaction(amount, currency, reportID, comment = '', source = '', originalTransactionID = '', merchant = CONST.REPORT.TYPE.IOU, receipt = {}) { // transactionIDs are random, positive, 64-bit numeric strings. // Because JS can only handle 53-bit numbers, transactionIDs are strings in the front-end (just like reportActionID) const transactionID = NumberUtils.rand64(); @@ -36,6 +37,7 @@ function buildOptimisticTransaction(amount, currency, reportID, comment = '', so merchant, created: DateUtils.getDBTime(), pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + receipt, }; } diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 9cd28bccc6c7..0c14057da770 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -298,8 +298,10 @@ function buildOnyxDataForMoneyRequest( * @param {Number} payeeAccountID * @param {Object} participant * @param {String} comment + * @param {Object} [receipt] + * */ -function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, participant, comment) { +function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, participant, comment, receipt = undefined) { const payerEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login); const payerAccountID = Number(participant.accountID); const isPolicyExpenseChat = participant.isPolicyExpenseChat; @@ -343,8 +345,13 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part : ReportUtils.buildOptimisticIOUReport(payeeAccountID, payerAccountID, amount, chatReport.reportID, currency); } - // STEP 3: Build optimistic transaction - const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, iouReport.reportID, comment); + // STEP 3: Build optimistic receipt and transaction + const receiptObject = {}; + if (receipt && receipt.source) { + receiptObject.source = receipt.source; + receiptObject.state = CONST.IOU.RECEIPT_STATE.SCANREADY; + } + const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, iouReport.reportID, comment, '', '', undefined, receiptObject); // STEP 4: Build optimistic reportActions. We need: // 1. CREATED action for the chatReport @@ -363,6 +370,7 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part optimisticTransaction.transactionID, '', iouReport.reportID, + receiptObject, ); // Add optimistic personal details for participant @@ -413,6 +421,7 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part createdChatReportActionID: isNewChatReport ? optimisticCreatedActionForChat.reportActionID : 0, createdIOUReportActionID: isNewIOUReport ? optimisticCreatedActionForIOU.reportActionID : 0, reportPreviewReportActionID: reportPreviewAction.reportActionID, + receipt, }, {optimisticData, successData, failureData}, ); diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index 9faa5c39626c..4d714fff4f24 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -131,4 +131,35 @@ function appendTimeToFileName(fileName) { return newFileName; } -export {showGeneralErrorAlert, showSuccessAlert, showPermissionErrorAlert, splitExtensionFromFileName, getAttachmentName, getFileType, cleanFileName, appendTimeToFileName}; +/** + * Reads a locally uploaded file + * + * @param {String} path - the blob url of the locally uplodaded file + * @param {String} fileName + * @returns {Promise} + */ +const readFileAsync = (path, fileName) => + new Promise((resolve) => { + if (!path) { + resolve(); + } + + return fetch(path) + .then((res) => { + if (!res.ok) { + throw Error(res.statusText); + } + return res.blob(); + }) + .then((blob) => { + const file = new File([blob], cleanFileName(fileName)); + file.source = path; + resolve(file); + }) + .catch((e) => { + console.debug('[FileUtils] Could not read uploaded file', e); + resolve(); + }); + }); + +export {showGeneralErrorAlert, showSuccessAlert, showPermissionErrorAlert, splitExtensionFromFileName, getAttachmentName, getFileType, cleanFileName, appendTimeToFileName, readFileAsync}; diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 3703a342d00e..d8711de4fc1c 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -20,6 +20,7 @@ import ONYXKEYS from '../../../ONYXKEYS'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '../../../components/withCurrentUserPersonalDetails'; import reportPropTypes from '../../reportPropTypes'; import personalDetailsPropType from '../../personalDetailsPropType'; +import * as FileUtils from '../../../libs/fileDownload/FileUtils'; const propTypes = { report: reportPropTypes, @@ -109,6 +110,27 @@ function MoneyRequestConfirmPage(props) { Navigation.goBack(fallback); }; + /** + * @param {Array} selectedParticipants + * @param {String} trimmedComment + * @param {File} [receipt] + */ + const requestMoney = useCallback( + (selectedParticipants, trimmedComment, receipt) => { + IOU.requestMoney( + props.report, + props.iou.amount, + props.iou.currency, + props.currentUserPersonalDetails.login, + props.currentUserPersonalDetails.accountID, + selectedParticipants[0], + trimmedComment, + receipt, + ); + }, + [props.report, props.iou.amount, props.iou.currency, props.currentUserPersonalDetails.login, props.currentUserPersonalDetails.accountID], + ); + const createTransaction = useCallback( (selectedParticipants) => { const trimmedComment = props.iou.comment.trim(); @@ -141,17 +163,25 @@ function MoneyRequestConfirmPage(props) { return; } - IOU.requestMoney( - props.report, - props.iou.amount, - props.iou.currency, - props.currentUserPersonalDetails.login, - props.currentUserPersonalDetails.accountID, - selectedParticipants[0], - trimmedComment, - ); + if (props.iou.receiptPath && props.iou.receiptSource) { + FileUtils.readFileAsync(props.iou.receiptPath, props.iou.receiptSource).then((receipt) => { + requestMoney(selectedParticipants, trimmedComment, receipt); + }); + return; + } + + requestMoney(selectedParticipants, trimmedComment); }, - [props.iou.amount, props.iou.comment, props.currentUserPersonalDetails.login, props.currentUserPersonalDetails.accountID, props.iou.currency, props.report], + [ + props.iou.amount, + props.iou.comment, + props.currentUserPersonalDetails.login, + props.currentUserPersonalDetails.accountID, + props.iou.currency, + props.iou.receiptPath, + props.iou.receiptSource, + requestMoney, + ], ); /**