Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b5a31a4
Implemented BE part of Per Diem Request
shubham1206agra Jan 2, 2025
28f18ee
Fix default values
shubham1206agra Jan 2, 2025
ab084f2
Fix optimistic bug
shubham1206agra Jan 2, 2025
7bd8de9
Fix navigation bug
shubham1206agra Jan 2, 2025
868c4d2
Fix parameters
shubham1206agra Jan 3, 2025
35c0bd7
Fix weird tab navigator behavior
shubham1206agra Jan 5, 2025
57f1e3a
Renaming component
shubham1206agra Jan 7, 2025
e3cde6d
Merge branch 'Expensify:main' into per-diem-6
shubham1206agra Jan 7, 2025
c3688b4
Fixed subrate page
shubham1206agra Jan 7, 2025
ed00cdc
Fixed empty time case
shubham1206agra Jan 7, 2025
6c34373
Fixed weird destination options when options are less than the threshold
shubham1206agra Jan 7, 2025
394a07e
Fixed offline behavior in destination page
shubham1206agra Jan 7, 2025
b89b6b5
Fixed subrate form
shubham1206agra Jan 7, 2025
6aa760e
Fix qty translation
shubham1206agra Jan 8, 2025
afb3c15
Fix time badge translation
shubham1206agra Jan 8, 2025
eb98559
Merge branch 'Expensify:main' into per-diem-6
shubham1206agra Jan 9, 2025
6b69c5c
Fix eReceipts
shubham1206agra Jan 10, 2025
e723cf7
Merge branch 'Expensify:main' into per-diem-6
shubham1206agra Jan 10, 2025
06a6ea1
Fix eReceipts part 2
shubham1206agra Jan 10, 2025
a51ff73
Fix translations
shubham1206agra Jan 10, 2025
393ba99
Merge branch 'Expensify:main' into per-diem-6
shubham1206agra Jan 11, 2025
1c0d561
Fix variable names
shubham1206agra Jan 12, 2025
3e366f4
Minor fix
shubham1206agra Jan 12, 2025
312b9ff
Merge branch 'Expensify:main' into per-diem-6
shubham1206agra Jan 12, 2025
348c201
Fixed default category not setting up properly
shubham1206agra Jan 12, 2025
c07a2bf
Disable blur validation in subrate step
shubham1206agra Jan 12, 2025
35e70a3
Fix lint
shubham1206agra Jan 13, 2025
4bae644
Fix conflicts
shubham1206agra Jan 14, 2025
9616b52
Fix lint part 1
shubham1206agra Jan 14, 2025
784da8e
Fix lint part 2
shubham1206agra Jan 14, 2025
850c339
Fix lint part 3
shubham1206agra Jan 14, 2025
2ae74b1
Merge main
shubham1206agra Jan 16, 2025
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
2 changes: 2 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2324,6 +2324,8 @@ const CONST = {

IOU: {
MAX_RECENT_REPORTS_TO_SHOW: 5,
// This will guranatee that the quantity input will not exceed 9,007,199,254,740,991 (Number.MAX_SAFE_INTEGER).
QUANTITY_MAX_LENGTH: 12,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hi @shubham1206agra, coming from this issue, just wanna confirm if for PerDiem expenses, we allow quantity up to 12 max-length?

// This is the transactionID used when going through the create expense flow so that it mimics a real transaction (like the edit flow)
OPTIMISTIC_TRANSACTION_ID: '1',
// Note: These payment types are used when building IOU reportAction message values in the server and should
Expand Down
5 changes: 5 additions & 0 deletions src/components/Attachments/AttachmentView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import DistanceEReceipt from '@components/DistanceEReceipt';
import EReceipt from '@components/EReceipt';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import PerDiemEReceipt from '@components/PerDiemEReceipt';
import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
Expand Down Expand Up @@ -160,6 +161,10 @@ function AttachmentView({
);
}

if (TransactionUtils.isPerDiemRequest(transaction) && transaction && !TransactionUtils.hasReceiptSource(transaction)) {
return <PerDiemEReceipt transactionID={transaction.transactionID} />;
}

if (transaction && !TransactionUtils.hasReceiptSource(transaction) && TransactionUtils.hasEReceipt(transaction)) {
return (
<View style={[styles.flex1, styles.alignItemsCenter]}>
Expand Down
39 changes: 20 additions & 19 deletions src/components/EReceiptThumbnail.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
import React, {useMemo} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import * as ReportUtils from '@libs/ReportUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import * as TripReservationUtils from '@libs/TripReservationUtils';
import colors from '@styles/theme/colors';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Transaction} from '@src/types/onyx';
import Icon from './Icon';
import * as eReceiptBGs from './Icon/EReceiptBGs';
import * as Expensicons from './Icon/Expensicons';
import * as MCCIcons from './Icon/MCCIcons';
import Image from './Image';
import Text from './Text';

type EReceiptThumbnailOnyxProps = {
transaction: OnyxEntry<Transaction>;
};

type IconSize = 'x-small' | 'small' | 'medium' | 'large';

type EReceiptThumbnailProps = EReceiptThumbnailOnyxProps & {
/** TransactionID of the transaction this EReceipt corresponds to. It's used by withOnyx HOC */
// eslint-disable-next-line react/no-unused-prop-types
type EReceiptThumbnailProps = {
/** TransactionID of the transaction this EReceipt corresponds to. */
transactionID: string;

/** Border radius to be applied on the parent view. */
Expand Down Expand Up @@ -54,9 +48,10 @@ const backgroundImages = {
[CONST.ERECEIPT_COLORS.PINK]: eReceiptBGs.EReceiptBG_Pink,
};

function EReceiptThumbnail({transaction, borderRadius, fileExtension, isReceiptThumbnail = false, centerIconV = true, iconSize = 'large'}: EReceiptThumbnailProps) {
function EReceiptThumbnail({transactionID, borderRadius, fileExtension, isReceiptThumbnail = false, centerIconV = true, iconSize = 'large'}: EReceiptThumbnailProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
const colorCode = isReceiptThumbnail ? StyleUtils.getFileExtensionColorCode(fileExtension) : StyleUtils.getEReceiptColorCode(transaction);

const backgroundImage = useMemo(() => backgroundImages[colorCode], [colorCode]);
Expand All @@ -68,6 +63,7 @@ function EReceiptThumbnail({transaction, borderRadius, fileExtension, isReceiptT
const transactionMCCGroup = transactionDetails?.mccGroup;
const MCCIcon = transactionMCCGroup ? MCCIcons[`${transactionMCCGroup}`] : undefined;
const tripIcon = TripReservationUtils.getTripEReceiptIcon(transaction);
const isPerDiemRequest = TransactionUtils.isPerDiemRequest(transaction);

let receiptIconWidth: number = variables.eReceiptIconWidth;
let receiptIconHeight: number = variables.eReceiptIconHeight;
Expand Down Expand Up @@ -135,15 +131,23 @@ function EReceiptThumbnail({transaction, borderRadius, fileExtension, isReceiptT
{fileExtension.toUpperCase()}
</Text>
)}
{MCCIcon && !isReceiptThumbnail ? (
{isPerDiemRequest ? (
<Icon
src={Expensicons.CalendarSolid}
height={receiptMCCSize}
width={receiptMCCSize}
fill={primaryColor}
/>
) : null}
{!isPerDiemRequest && MCCIcon && !isReceiptThumbnail ? (
<Icon
src={MCCIcon}
height={receiptMCCSize}
width={receiptMCCSize}
fill={primaryColor}
/>
) : null}
{!MCCIcon && tripIcon ? (
{!isPerDiemRequest && !MCCIcon && tripIcon ? (
<Icon
src={tripIcon}
height={receiptMCCSize}
Expand All @@ -158,9 +162,6 @@ function EReceiptThumbnail({transaction, borderRadius, fileExtension, isReceiptT
}

EReceiptThumbnail.displayName = 'EReceiptThumbnail';
export default withOnyx<EReceiptThumbnailProps, EReceiptThumbnailOnyxProps>({
transaction: {
key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
},
})(EReceiptThumbnail);
export type {IconSize, EReceiptThumbnailProps, EReceiptThumbnailOnyxProps};
export default EReceiptThumbnail;

export type {IconSize, EReceiptThumbnailProps};
8 changes: 4 additions & 4 deletions src/components/MoneyRequestConfirmationListFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ function MoneyRequestConfirmationListFooter({
<MenuItemWithTopDescription
key={`${translate('common.subrate')}${field?.key ?? index}`}
shouldShowRightIcon={!isReadOnly}
title={PerDiemRequestUtils.getSubratesForDisplay(field)}
title={PerDiemRequestUtils.getSubratesForDisplay(field, translate('iou.qty'))}
description={translate('common.subrate')}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
Expand All @@ -638,7 +638,7 @@ function MoneyRequestConfirmationListFooter({
<Badge
key="firstDay"
icon={Expensicons.Stopwatch}
text={translate('iou.firstDayText', {hours: firstDay})}
text={translate('iou.firstDayText', {count: firstDay})}
/>,
);
}
Expand All @@ -647,7 +647,7 @@ function MoneyRequestConfirmationListFooter({
<Badge
key="tripDays"
icon={Expensicons.CalendarSolid}
text={translate('iou.tripLengthText', {days: tripDays})}
text={translate('iou.tripLengthText', {count: tripDays})}
/>,
);
}
Expand All @@ -656,7 +656,7 @@ function MoneyRequestConfirmationListFooter({
<Badge
key="lastDay"
icon={Expensicons.Stopwatch}
text={translate('iou.lastDayText', {hours: lastDay})}
text={translate('iou.lastDayText', {count: lastDay})}
/>,
);
}
Expand Down
120 changes: 120 additions & 0 deletions src/components/PerDiemEReceipt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {TransactionCustomUnit} from '@src/types/onyx/Transaction';
import EReceiptThumbnail from './EReceiptThumbnail';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import Text from './Text';

type PerDiemEReceiptProps = {
/* TransactionID of the transaction this EReceipt corresponds to */
transactionID: string;
};

function computeDefaultPerDiemExpenseRates(customUnit: TransactionCustomUnit, currency: string) {
const subRates = customUnit.subRates ?? [];
const subRateComments = subRates.map((subRate) => {
const rate = subRate.rate ?? 0;
const rateComment = subRate.name ?? '';
const quantity = subRate.quantity ?? 0;
return `${quantity}x ${rateComment} @ ${CurrencyUtils.convertAmountToDisplayString(rate, currency)}`;
});
return subRateComments.join(', ');
}

function getPerDiemDestination(merchant: string) {
const merchantParts = merchant.split(', ');
if (merchantParts.length < 3) {
return '';
}
return merchantParts.slice(0, merchantParts.length - 3).join(', ');
}

function getPerDiemDates(merchant: string) {
const merchantParts = merchant.split(', ');
if (merchantParts.length < 3) {
return merchant;
}
return merchantParts.slice(-3).join(', ');
}

function PerDiemEReceipt({transactionID}: PerDiemEReceiptProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);

// Get receipt colorway, or default to Yellow.
const {backgroundColor: primaryColor, color: secondaryColor} = StyleUtils.getEReceiptColorStyles(StyleUtils.getEReceiptColorCode(transaction)) ?? {};

const {amount: transactionAmount, currency: transactionCurrency, merchant: transactionMerchant} = ReportUtils.getTransactionDetails(transaction, CONST.DATE.MONTH_DAY_YEAR_FORMAT) ?? {};
const ratesDescription = computeDefaultPerDiemExpenseRates(transaction?.comment?.customUnit ?? {}, transactionCurrency ?? '');
const datesDescription = getPerDiemDates(transactionMerchant ?? '');
const destination = getPerDiemDestination(transactionMerchant ?? '');
const formattedAmount = CurrencyUtils.convertToDisplayStringWithoutCurrency(transactionAmount ?? 0, transactionCurrency);
const currency = CurrencyUtils.getCurrencySymbol(transactionCurrency ?? '');

const secondaryTextColorStyle = secondaryColor ? StyleUtils.getColorStyle(secondaryColor) : undefined;

return (
<View style={[styles.eReceiptContainer, primaryColor ? StyleUtils.getBackgroundColorStyle(primaryColor) : undefined]}>
<View style={styles.fullScreen}>
<EReceiptThumbnail
transactionID={transactionID}
centerIconV={false}
/>
</View>
<View style={[styles.alignItemsCenter, styles.ph8, styles.pb14, styles.pt8]}>
<View style={[StyleUtils.getWidthAndHeightStyle(variables.eReceiptIconWidth, variables.eReceiptIconHeight)]} />
</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, StyleUtils.getWidthStyle(variables.eReceiptTextContainerWidth)]}>
<View style={[styles.flexColumn, styles.pt1]}>
<Text style={[styles.eReceiptCurrency, secondaryTextColorStyle]}>{currency}</Text>
</View>
<Text
adjustsFontSizeToFit
style={[styles.eReceiptAmountLarge, secondaryTextColorStyle]}
>
{formattedAmount}
</Text>
</View>
<Text style={[styles.eReceiptMerchant, styles.breakWord, styles.textAlignCenter]}>{`${destination} ${translate('common.perDiem').toLowerCase()}`}</Text>
</View>
<View style={[styles.alignSelfStretch, styles.flexColumn, styles.mb8, styles.gap4]}>
<View style={[styles.flexColumn, styles.gap1]}>
<Text style={[styles.eReceiptWaypointTitle, secondaryTextColorStyle]}>{translate('iou.dates')}</Text>
<Text style={[styles.eReceiptWaypointAddress]}>{datesDescription}</Text>
</View>
<View style={[styles.flexColumn, styles.gap1]}>
<Text style={[styles.eReceiptWaypointTitle, secondaryTextColorStyle]}>{translate('iou.rates')}</Text>
<Text style={[styles.eReceiptWaypointAddress]}>{ratesDescription}</Text>
</View>
</View>
<View style={[styles.justifyContentBetween, styles.alignItemsCenter, styles.alignSelfStretch, styles.flexRow, styles.mb8]}>
<Icon
width={variables.eReceiptWordmarkWidth}
height={variables.eReceiptWordmarkHeight}
fill={secondaryColor}
src={Expensicons.ExpensifyWordmark}
/>
<Text style={styles.eReceiptGuaranteed}>{translate('eReceipt.guaranteed')}</Text>
</View>
</View>
</View>
);
}

PerDiemEReceipt.displayName = 'PerDiemEReceipt';

export default PerDiemEReceipt;
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ function MoneyRequestPreviewContent({
const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID, transactionViolations, true);
const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction);
const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction);
const isPerDiemRequest = TransactionUtils.isPerDiemRequest(transaction);
const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction);
const isCardTransaction = TransactionUtils.isCardTransaction(transaction);
const isSettled = ReportUtils.isSettled(iouReport?.reportID);
Expand Down Expand Up @@ -197,6 +198,8 @@ function MoneyRequestPreviewContent({

if (isDistanceRequest) {
message = translate('common.distance');
} else if (isPerDiemRequest) {
message = translate('common.perDiem');
} else if (isScanning) {
message = translate('common.receipt');
} else if (isBillSplit) {
Expand Down
Loading