Skip to content

Add Scan to Split Bill and implement IOU action startSplitBill #28710

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

Merged
merged 41 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d03ead5
Display 'Split' bill button in Scan flow
youssef-lr Oct 3, 2023
6231e7c
Hide $0 amounts in Split Bill
youssef-lr Oct 3, 2023
60ffec4
Add IOU action startSplitBillRequest
youssef-lr Oct 3, 2023
facfd16
Hide split amount if it's O
youssef-lr Oct 3, 2023
ec7610d
Add receipt status text in SplitBillDetailsPage
youssef-lr Oct 3, 2023
057fc00
Allow editing merchant and date when creating split bill
youssef-lr Oct 3, 2023
7361985
Fix navigation to newly created chat
youssef-lr Oct 4, 2023
04ec01c
Show TabSelector when using Split Bill from workspace chat
youssef-lr Oct 4, 2023
1404029
Display receipt in SplitBillDetailsPage
youssef-lr Oct 4, 2023
fbdf93d
Cleanup
youssef-lr Oct 4, 2023
9fd07bc
Fix splitting bill with unkown users
youssef-lr Oct 4, 2023
66c0ae2
Merge branch 'main' into youssef_startSplitBill
youssef-lr Oct 4, 2023
a51725a
Cleanup
youssef-lr Oct 4, 2023
3820323
Add JSDocs
youssef-lr Oct 4, 2023
51fbbf6
Remove created and merchant
youssef-lr Oct 5, 2023
6f8b808
Only display scan status when receipt is being scanned
youssef-lr Oct 5, 2023
19e5994
Cleanup
youssef-lr Oct 5, 2023
a1b79b0
Fix GH actions
youssef-lr Oct 5, 2023
2af470b
Sve policy expense chat reportID in splits array
youssef-lr Oct 6, 2023
53f28f3
Create optimistic details of participants if needed
youssef-lr Oct 6, 2023
98bd046
Merge branch 'main' into youssef_startSplitBill
youssef-lr Oct 6, 2023
027cc04
Show amount in split details page once it's created
youssef-lr Oct 6, 2023
23e5e2b
Show SmartScan fields in ReadOnly split details page
youssef-lr Oct 6, 2023
9b2b942
Rename receiptSource to receiptFilename
youssef-lr Oct 6, 2023
424132f
Add null check
youssef-lr Oct 6, 2023
2f99094
Bug fix
youssef-lr Oct 8, 2023
66616b4
Merge branch 'main' into youssef_startSplitBill
youssef-lr Oct 8, 2023
ceb1872
Apply suggestions from code review
youssef-lr Oct 9, 2023
10d24d0
Merge branch 'main' into youssef_startSplitBill
youssef-lr Oct 9, 2023
5c47140
Hide showMore button when there are no more fields to display
youssef-lr Oct 9, 2023
4083b0c
Merge branch 'main' into youssef_startSplitBill
youssef-lr Oct 9, 2023
e6de6c5
Fix navigation to split chat report
youssef-lr Oct 9, 2023
a5d29ba
Remove unnecessary param
youssef-lr Oct 10, 2023
92c9a42
Reverse changes in TransactionUtils
youssef-lr Oct 10, 2023
6c8f574
Apply suggestions from code review
youssef-lr Oct 10, 2023
5f7656b
Tidy up code
youssef-lr Oct 10, 2023
5527d4c
Add error to split chat created action on failure
youssef-lr Oct 10, 2023
3a7504e
Merge branch 'youssef_startSplitBill' of github.com:Expensify/App int…
youssef-lr Oct 10, 2023
25f0a2a
fix lint
youssef-lr Oct 10, 2023
ab6a140
Add comment
youssef-lr Oct 10, 2023
993f0ce
Save splits optimistically in the transaction comment
youssef-lr Oct 10, 2023
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
75 changes: 50 additions & 25 deletions src/components/MoneyRequestConfirmationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ const propTypes = {
/** File path of the receipt */
receiptPath: PropTypes.string,

/** File source of the receipt */
receiptSource: PropTypes.string,
/** File name of the receipt */
receiptFilename: PropTypes.string,

/** List styles for OptionsSelector */
listStyles: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),
Expand All @@ -141,6 +141,9 @@ const propTypes = {
/** Whether the money request is a distance request */
isDistanceRequest: PropTypes.bool,

/** Whether the receipt associated with this report is being scanned */
isScanning: PropTypes.bool,

/** A flag for verifying that the current report is a sub-report of a workspace chat */
isPolicyExpenseChat: PropTypes.bool,

Expand Down Expand Up @@ -171,14 +174,15 @@ const defaultProps = {
reportID: '',
...withCurrentUserPersonalDetailsDefaultProps,
receiptPath: '',
receiptSource: '',
receiptFilename: '',
listStyles: [],
policyCategories: {},
policyTags: {},
transactionID: '',
transaction: {},
mileageRate: {unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate: 0, currency: 'USD'},
isDistanceRequest: false,
isScanning: false,
isPolicyExpenseChat: false,
};

Expand All @@ -188,9 +192,6 @@ function MoneyRequestConfirmationList(props) {
const {onSendMoney, onConfirm, onSelectParticipant, transaction} = props;
const {translate, toLocaleDigit} = useLocalize();

// A flag and a toggler for showing the rest of the form fields
const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false);
const shouldShowAllFields = props.isDistanceRequest || shouldExpandFields;
const isTypeRequest = props.iouType === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST;

const {unit, rate, currency} = props.mileageRate;
Expand All @@ -201,6 +202,14 @@ function MoneyRequestConfirmationList(props) {
const shouldShowCategories =
props.isPolicyExpenseChat && Permissions.canUseCategories(props.betas) && (props.iouCategory || OptionsListUtils.hasEnabledOptions(_.values(props.policyCategories)));

// A flag for showing SmartScan fields: date, merchant, and amount, only when we don't have a receiptPath (e.g. manual request)
// or in the split details page which is ReadOnly
const shouldShowSmartScanFields = (!props.receiptPath || props.isReadOnly) && !props.isScanning;

// A flag and a toggler for showing the rest of the form fields
const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false);
const shouldShowAllFields = props.isDistanceRequest || shouldExpandFields || !shouldShowSmartScanFields;

// Fetches the first tag list of the policy
const policyTag = PolicyUtils.getTag(props.policyTags);
const policyTagList = lodashGet(policyTag, 'tags', {});
Expand Down Expand Up @@ -245,7 +254,10 @@ function MoneyRequestConfirmationList(props) {
const getParticipantsWithAmount = useCallback(
(participantsList) => {
const iouAmount = IOUUtils.calculateAmount(participantsList.length, props.iouAmount, props.iouCurrencyCode);
return OptionsListUtils.getIOUConfirmationOptionsFromParticipants(participantsList, CurrencyUtils.convertToDisplayString(iouAmount, props.iouCurrencyCode));
return OptionsListUtils.getIOUConfirmationOptionsFromParticipants(
participantsList,
props.iouAmount > 0 ? CurrencyUtils.convertToDisplayString(iouAmount, props.iouCurrencyCode) : '',
);
},
[props.iouAmount, props.iouCurrencyCode],
);
Expand All @@ -254,7 +266,9 @@ function MoneyRequestConfirmationList(props) {

const splitOrRequestOptions = useMemo(() => {
let text;
if (props.receiptPath || isDistanceRequestWithoutRoute) {
if (props.receiptPath && props.hasMultipleParticipants && props.iouAmount === 0) {
text = translate('iou.split');
} else if (props.receiptPath || isDistanceRequestWithoutRoute) {
text = translate('iou.request');
} else {
const translationKey = props.hasMultipleParticipants ? 'iou.splitAmount' : 'iou.requestAmount';
Expand All @@ -266,7 +280,7 @@ function MoneyRequestConfirmationList(props) {
value: props.hasMultipleParticipants ? CONST.IOU.MONEY_REQUEST_TYPE.SPLIT : CONST.IOU.MONEY_REQUEST_TYPE.REQUEST,
},
];
}, [props.hasMultipleParticipants, props.receiptPath, translate, formattedAmount, isDistanceRequestWithoutRoute]);
}, [props.hasMultipleParticipants, props.iouAmount, props.receiptPath, translate, formattedAmount, isDistanceRequestWithoutRoute]);

const selectedParticipants = useMemo(() => _.filter(props.selectedParticipants, (participant) => participant.selected), [props.selectedParticipants]);
const payeePersonalDetails = useMemo(() => props.payeePersonalDetails || props.currentUserPersonalDetails, [props.payeePersonalDetails, props.currentUserPersonalDetails]);
Expand All @@ -290,7 +304,7 @@ function MoneyRequestConfirmationList(props) {
const myIOUAmount = IOUUtils.calculateAmount(selectedParticipants.length, props.iouAmount, props.iouCurrencyCode, true);
const formattedPayeeOption = OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(
payeePersonalDetails,
CurrencyUtils.convertToDisplayString(myIOUAmount, props.iouCurrencyCode),
props.iouAmount > 0 ? CurrencyUtils.convertToDisplayString(myIOUAmount, props.iouCurrencyCode) : '',
);

sections.push(
Expand Down Expand Up @@ -459,6 +473,9 @@ 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) : {};

return (
<OptionsSelector
sections={optionSelectorSections}
Expand All @@ -483,12 +500,17 @@ function MoneyRequestConfirmationList(props) {
<ConfirmedRoute transactionID={props.transactionID} />
</View>
)}
{!_.isEmpty(props.receiptPath) ? (
{(receiptImage || receiptThumbnail) && (
<Image
style={styles.moneyRequestImage}
source={{uri: ReceiptUtils.getThumbnailAndImageURIs(props.receiptPath, props.receiptSource).image}}
source={{uri: receiptThumbnail || receiptImage}}
// AuthToken is required when retrieving the image from the server
// but we don't need it to load the blob:// or file:// image when starting a money request / split bill
// So if we have a thumbnail, it means we're retrieving the image from the server
isAuthTokenRequired={!_.isEmpty(receiptThumbnail)}
/>
) : (
)}
{shouldShowSmartScanFields && (
<MenuItemWithTopDescription
shouldShowRightIcon={!props.isReadOnly && !props.isDistanceRequest}
title={formattedAmount}
Expand Down Expand Up @@ -527,16 +549,18 @@ function MoneyRequestConfirmationList(props) {
)}
{shouldShowAllFields && (
<>
<MenuItemWithTopDescription
shouldShowRightIcon={!props.isReadOnly && isTypeRequest}
title={props.iouCreated || format(new Date(), CONST.DATE.FNS_FORMAT_STRING)}
description={translate('common.date')}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_DATE.getRoute(props.iouType, props.reportID))}
disabled={didConfirm || props.isReadOnly || !isTypeRequest}
/>
{props.isDistanceRequest ? (
{shouldShowSmartScanFields && (
<MenuItemWithTopDescription
shouldShowRightIcon={!props.isReadOnly && isTypeRequest}
title={props.iouCreated || format(new Date(), CONST.DATE.FNS_FORMAT_STRING)}
description={translate('common.date')}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_DATE.getRoute(props.iouType, props.reportID))}
disabled={didConfirm || props.isReadOnly}
/>
)}
{props.isDistanceRequest && (
<MenuItemWithTopDescription
shouldShowRightIcon={!props.isReadOnly && isTypeRequest}
title={props.iouMerchant}
Expand All @@ -546,15 +570,16 @@ function MoneyRequestConfirmationList(props) {
onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_DISTANCE.getRoute(props.iouType, props.reportID))}
disabled={didConfirm || props.isReadOnly || !isTypeRequest}
/>
) : (
)}
{shouldShowSmartScanFields && (
Copy link
Contributor

@ntdiary ntdiary Oct 24, 2023

Choose a reason for hiding this comment

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

Coming from #29374. We should hide merchant field for distance request.
When testing a common part, it would be better to also test the affected feature (e.g., here the fields for scan request were modified, then we also need to test the distance request).

<MenuItemWithTopDescription
shouldShowRightIcon={!props.isReadOnly && isTypeRequest}
title={props.iouMerchant}
description={translate('common.merchant')}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_MERCHANT.getRoute(props.iouType, props.reportID))}
disabled={didConfirm || props.isReadOnly || !isTypeRequest}
disabled={didConfirm || props.isReadOnly}
/>
)}
{shouldShowCategories && (
Expand Down
2 changes: 1 addition & 1 deletion src/components/MoneyRequestHeaderStatusBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function MoneyRequestHeaderStatusBar() {
const {translate} = useLocalize();

return (
<View style={[styles.dFlex, styles.flexRow, styles.alignItemsCenter, styles.flexGrow1, styles.overflowHidden, styles.ph5, styles.pb3, styles.borderBottom, styles.w100]}>
<View style={[styles.dFlex, styles.flexRow, styles.alignItemsCenter, styles.overflowHidden, styles.ph5, styles.pb3, styles.borderBottom, styles.w100]}>
<View style={[styles.moneyRequestHeaderStatusBarBadge]}>
<Text style={[styles.textStrong, styles.textLabel]}>{translate('iou.receiptStatusTitle')}</Text>
</View>
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 @@ -317,7 +317,7 @@ function MoneyRequestPreview(props) {
)}
{shouldShowDescription && <Text style={[styles.colorMuted]}>{description}</Text>}
</View>
{props.isBillSplit && !_.isEmpty(participantAccountIDs) && (
{props.isBillSplit && !_.isEmpty(participantAccountIDs) && requestAmount > 0 && (
<Text style={[styles.textLabel, styles.colorMuted, styles.ml1, styles.amountSplitPadding]}>
{props.translate('iou.amountEach', {
amount: CurrencyUtils.convertToDisplayString(
Expand Down
Loading
Loading