Skip to content

Commit

Permalink
Merge pull request Expensify#32486 from waterim/feat-32222-Make-Merch…
Browse files Browse the repository at this point in the history
…ant-field-required-for-requests-on-group-policies

Feature: Update merchant to be empty and with validation
  • Loading branch information
mountiny authored Dec 29, 2023
2 parents 6779c7b + 513b612 commit d4e0db5
Show file tree
Hide file tree
Showing 12 changed files with 112 additions and 29 deletions.
10 changes: 7 additions & 3 deletions src/components/MoneyRequestConfirmationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ function MoneyRequestConfirmationList(props) {
return (props.hasSmartScanFailed && TransactionUtils.hasMissingSmartscanFields(transaction)) || (didConfirmSplit && TransactionUtils.areRequiredFieldsEmpty(transaction));
}, [props.isEditingSplitBill, props.hasSmartScanFailed, transaction, didConfirmSplit]);

const isMerchantEmpty = !props.iouMerchant || props.iouMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
const shouldDisplayMerchantError = props.isPolicyExpenseChat && !props.isScanRequest && isMerchantEmpty;

useEffect(() => {
if (shouldDisplayFieldError && props.hasSmartScanFailed) {
setFormError('iou.receiptScanningFailed');
Expand Down Expand Up @@ -500,7 +503,7 @@ function MoneyRequestConfirmationList(props) {
}

const shouldShowSettlementButton = props.iouType === CONST.IOU.TYPE.SEND;
const shouldDisableButton = selectedParticipants.length === 0;
const shouldDisableButton = selectedParticipants.length === 0 || shouldDisplayMerchantError;

const button = shouldShowSettlementButton ? (
<SettlementButton
Expand Down Expand Up @@ -552,6 +555,7 @@ function MoneyRequestConfirmationList(props) {
props.iouCurrencyCode,
props.policyID,
selectedParticipants.length,
shouldDisplayMerchantError,
confirm,
splitOrRequestOptions,
formError,
Expand Down Expand Up @@ -680,7 +684,7 @@ function MoneyRequestConfirmationList(props) {
{shouldShowMerchant && (
<MenuItemWithTopDescription
shouldShowRightIcon={!props.isReadOnly}
title={props.iouMerchant}
title={isMerchantEmpty ? '' : props.iouMerchant}
description={translate('common.merchant')}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
Expand All @@ -694,7 +698,7 @@ function MoneyRequestConfirmationList(props) {
disabled={didConfirm}
interactive={!props.isReadOnly}
brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
error={shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction) ? translate('common.error.enterMerchant') : ''}
error={shouldDisplayMerchantError || (shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction)) ? translate('common.error.enterMerchant') : ''}
/>
)}
{shouldShowCategories && (
Expand Down
77 changes: 70 additions & 7 deletions src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ const propTypes = {
/** Should the list be read only, and not editable? */
isReadOnly: PropTypes.bool,

/** Whether the money request is a scan request */
isScanRequest: PropTypes.bool,

/** Depending on expense report or personal IOU report, respective bank account route */
bankAccountRoute: PropTypes.string,

Expand Down Expand Up @@ -211,6 +214,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
isEditingSplitBill,
isPolicyExpenseChat,
isReadOnly,
isScanRequest,
listStyles,
mileageRate,
onConfirm,
Expand Down Expand Up @@ -281,6 +285,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
const [didConfirm, setDidConfirm] = useState(false);
const [didConfirmSplit, setDidConfirmSplit] = useState(false);

const [merchantError, setMerchantError] = useState(false);

const shouldDisplayFieldError = useMemo(() => {
if (!isEditingSplitBill) {
return false;
Expand All @@ -289,6 +295,21 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
return (hasSmartScanFailed && TransactionUtils.hasMissingSmartscanFields(transaction)) || (didConfirmSplit && TransactionUtils.areRequiredFieldsEmpty(transaction));
}, [isEditingSplitBill, hasSmartScanFailed, transaction, didConfirmSplit]);

const isMerchantEmpty = !iouMerchant || iouMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
const isMerchantRequired = isPolicyExpenseChat && !isScanRequest && shouldShowMerchant;

useEffect(() => {
if ((!isMerchantRequired && isMerchantEmpty) || !merchantError) {
return;
}
if (!isMerchantEmpty && merchantError) {
setMerchantError(false);
if (formError === 'iou.error.invalidMerchant') {
setFormError('');
}
}
}, [formError, isMerchantEmpty, merchantError, isMerchantRequired]);

useEffect(() => {
if (shouldDisplayFieldError && hasSmartScanFailed) {
setFormError('iou.receiptScanningFailed');
Expand All @@ -298,9 +319,13 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
setFormError('iou.error.genericSmartscanFailureMessage');
return;
}
if (merchantError) {
setFormError('iou.error.invalidMerchant');
return;
}
// reset the form error whenever the screen gains or loses focus
setFormError('');
}, [isFocused, transaction, shouldDisplayFieldError, hasSmartScanFailed, didConfirmSplit]);
}, [isFocused, transaction, shouldDisplayFieldError, hasSmartScanFailed, didConfirmSplit, isMerchantRequired, merchantError]);

useEffect(() => {
if (!shouldCalculateDistanceAmount) {
Expand Down Expand Up @@ -470,6 +495,10 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
if (_.isEmpty(selectedParticipants)) {
return;
}
if ((isMerchantRequired && isMerchantEmpty) || (shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction))) {
setMerchantError(true);
return;
}

if (iouType === CONST.IOU.TYPE.SEND) {
if (!paymentMethod) {
Expand Down Expand Up @@ -498,7 +527,21 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
onConfirm(selectedParticipants);
}
},
[selectedParticipants, onSendMoney, onConfirm, isEditingSplitBill, iouType, isDistanceRequest, isDistanceRequestWithoutRoute, iouCurrencyCode, iouAmount, transaction],
[
selectedParticipants,
isMerchantRequired,
isMerchantEmpty,
shouldDisplayFieldError,
transaction,
iouType,
onSendMoney,
iouCurrencyCode,
isDistanceRequest,
isDistanceRequestWithoutRoute,
iouAmount,
isEditingSplitBill,
onConfirm,
],
);

const footerContent = useMemo(() => {
Expand Down Expand Up @@ -551,7 +594,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
{button}
</>
);
}, [confirm, bankAccountRoute, iouCurrencyCode, iouType, isReadOnly, policyID, selectedParticipants, splitOrRequestOptions, translate, formError, styles.ph1, styles.mb2]);
}, [isReadOnly, iouType, selectedParticipants.length, confirm, bankAccountRoute, iouCurrencyCode, policyID, splitOrRequestOptions, formError, styles.ph1, styles.mb2, translate]);

const {image: receiptImage, thumbnail: receiptThumbnail} = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : {};
return (
Expand Down Expand Up @@ -629,6 +672,26 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
interactive={!isReadOnly}
numberOfLinesTitle={2}
/>
{isMerchantRequired && (
<MenuItemWithTopDescription
shouldShowRightIcon={!isReadOnly}
title={isMerchantEmpty ? '' : iouMerchant}
description={translate('common.merchant')}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() => {
if (isEditingSplitBill) {
Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID, CONST.EDIT_REQUEST_FIELD.MERCHANT));
return;
}
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()));
}}
disabled={didConfirm}
interactive={!isReadOnly}
brickRoadIndicator={merchantError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
error={merchantError ? translate('common.error.fieldRequired') : ''}
/>
)}
{!shouldShowAllFields && (
<View style={[styles.flexRow, styles.justifyContentBetween, styles.mh3, styles.alignItemsCenter, styles.mb2, styles.mt1]}>
<View style={[styles.shortTermsHorizontalRule, styles.flex1, styles.mr0]} />
Expand Down Expand Up @@ -680,10 +743,10 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
interactive={!isReadOnly}
/>
)}
{shouldShowMerchant && (
{!isMerchantRequired && shouldShowMerchant && (
<MenuItemWithTopDescription
shouldShowRightIcon={!isReadOnly}
title={iouMerchant}
title={isMerchantEmpty ? '' : iouMerchant}
description={translate('common.merchant')}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
Expand All @@ -696,8 +759,8 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
}}
disabled={didConfirm}
interactive={!isReadOnly}
brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
error={shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction) ? translate('common.error.enterMerchant') : ''}
brickRoadIndicator={merchantError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
error={merchantError ? translate('common.error.fieldRequired') : ''}
/>
)}
{shouldShowCategories && (
Expand Down
2 changes: 1 addition & 1 deletion src/components/ReportActionItem/MoneyRequestView.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate
<OfflineWithFeedback pendingAction={lodashGet(transaction, 'pendingFields.merchant') || lodashGet(transaction, 'pendingAction')}>
<MenuItemWithTopDescription
description={translate('common.merchant')}
title={transactionMerchant}
title={isEmptyMerchant ? '' : transactionMerchant}
interactive={canEdit}
shouldShowRightIcon={canEdit}
titleStyle={styles.flex1}
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@ export default {
genericSmartscanFailureMessage: 'Transaction is missing fields',
atLeastTwoDifferentWaypoints: 'Please enter at least two different addresses',
splitBillMultipleParticipantsErrorMessage: 'Split bill is only allowed between a single workspace or individual users. Please update your selection.',
invalidMerchant: 'Please enter a corrent merchant.',
},
waitingOnEnabledWallet: ({submitterDisplayName}: WaitingOnBankAccountParams) => `Started settling up, payment is held until ${submitterDisplayName} enables their Wallet`,
enableWallet: 'Enable Wallet',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ export default {
genericSmartscanFailureMessage: 'La transacción tiene campos vacíos',
atLeastTwoDifferentWaypoints: 'Por favor introduce al menos dos direcciones diferentes',
splitBillMultipleParticipantsErrorMessage: 'Solo puedes dividir una cuenta entre un único espacio de trabajo o con usuarios individuales. Por favor actualiza tu selección.',
invalidMerchant: 'Por favor ingrese un comerciante correcto.',
},
waitingOnEnabledWallet: ({submitterDisplayName}: WaitingOnBankAccountParams) => `Inició el pago, pero no se procesará hasta que ${submitterDisplayName} active su Billetera`,
enableWallet: 'Habilitar Billetera',
Expand Down
2 changes: 1 addition & 1 deletion src/libs/TransactionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ function buildOptimisticTransaction(
currency,
reportID,
comment: commentJSON,
merchant: merchant || CONST.TRANSACTION.DEFAULT_MERCHANT,
merchant: merchant || CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT,
created: created || DateUtils.getDBTime(),
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
receipt,
Expand Down
3 changes: 2 additions & 1 deletion src/libs/actions/IOU.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ function startMoneyRequest_temporaryForRefactor(reportID, isFromGlobalCreate, io
reportID,
transactionID: newTransactionID,
isFromGlobalCreate,
merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT,
});
}

Expand Down Expand Up @@ -278,7 +279,7 @@ function resetMoneyRequestInfo(id = '') {
currency: lodashGet(currentUserPersonalDetails, 'localCurrencyCode', CONST.CURRENCY.USD),
comment: '',
participants: [],
merchant: CONST.TRANSACTION.DEFAULT_MERCHANT,
merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT,
category: '',
tag: '',
created,
Expand Down
27 changes: 16 additions & 11 deletions src/pages/EditRequestMerchantPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,27 @@ const propTypes = {

/** Callback to fire when the Save button is pressed */
onSubmit: PropTypes.func.isRequired,

/** Boolean to enable validation */
isPolicyExpenseChat: PropTypes.bool.isRequired,
};

function EditRequestMerchantPage({defaultMerchant, onSubmit}) {
function EditRequestMerchantPage({defaultMerchant, onSubmit, isPolicyExpenseChat}) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const merchantInputRef = useRef(null);
const isEmptyMerchant = defaultMerchant === '' || defaultMerchant === CONST.TRANSACTION.UNKNOWN_MERCHANT || defaultMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;

const validate = useCallback((value) => {
const errors = {};

if (_.isEmpty(value.merchant)) {
errors.merchant = 'common.error.fieldRequired';
}

return errors;
}, []);
const validate = useCallback(
(value) => {
const errors = {};
if (_.isEmpty(value.merchant) && value.merchant.trim() === '' && isPolicyExpenseChat) {
errors.merchant = 'common.error.fieldRequired';
}
return errors;
},
[isPolicyExpenseChat],
);

return (
<ScreenWrapper
Expand All @@ -56,7 +61,7 @@ function EditRequestMerchantPage({defaultMerchant, onSubmit}) {
InputComponent={TextInput}
inputID="merchant"
name="merchant"
defaultValue={defaultMerchant}
defaultValue={isEmptyMerchant ? '' : defaultMerchant}
label={translate('common.merchant')}
accessibilityLabel={translate('common.merchant')}
role={CONST.ROLE.PRESENTATION}
Expand Down
6 changes: 6 additions & 0 deletions src/pages/EditRequestPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,18 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT
return (
<EditRequestMerchantPage
defaultMerchant={transactionMerchant}
isPolicyExpenseChat={isPolicyExpenseChat}
onSubmit={(transactionChanges) => {
// In case the merchant hasn't been changed, do not make the API request.
if (transactionChanges.merchant.trim() === transactionMerchant) {
Navigation.dismissModal();
return;
}
// This is possible only in case of IOU requests.
if (transactionChanges.merchant.trim() === '') {
editMoneyRequest({merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT});
return;
}
editMoneyRequest({merchant: transactionChanges.merchant.trim()});
}}
/>
Expand Down
3 changes: 2 additions & 1 deletion src/pages/iou/MoneyRequestMerchantPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ function MoneyRequestMerchantPage({iou, route}) {
const {inputCallbackRef} = useAutoFocusInput();
const iouType = lodashGet(route, 'params.iouType', '');
const reportID = lodashGet(route, 'params.reportID', '');
const isEmptyMerchant = iou.merchant === '' || iou.merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;

useEffect(() => {
const moneyRequestId = `${iouType}${reportID}`;
Expand Down Expand Up @@ -114,7 +115,7 @@ function MoneyRequestMerchantPage({iou, route}) {
InputComponent={TextInput}
inputID="moneyRequestMerchant"
name="moneyRequestMerchant"
defaultValue={iou.merchant}
defaultValue={isEmptyMerchant ? '' : iou.merchant}
maxLength={CONST.MERCHANT_NAME_MAX_LENGTH}
label={translate('common.merchant')}
accessibilityLabel={translate('common.merchant')}
Expand Down
3 changes: 2 additions & 1 deletion src/pages/iou/request/step/IOURequestStepMerchant.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ function IOURequestStepMerchant({
const styles = useThemeStyles();
const {translate} = useLocalize();
const {inputCallbackRef} = useAutoFocusInput();
const isEmptyMerchant = merchant === '' || merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;

const navigateBack = () => {
Navigation.goBack(backTo || ROUTES.HOME);
Expand Down Expand Up @@ -89,7 +90,7 @@ function IOURequestStepMerchant({
InputComponent={TextInput}
inputID="moneyRequestMerchant"
name="moneyRequestMerchant"
defaultValue={merchant}
defaultValue={isEmptyMerchant ? '' : merchant}
maxLength={CONST.MERCHANT_NAME_MAX_LENGTH}
label={translate('common.merchant')}
accessibilityLabel={translate('common.merchant')}
Expand Down
Loading

0 comments on commit d4e0db5

Please sign in to comment.