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

feat: Receipt Audit Feature / Note type violations. #37813

Merged
merged 52 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
9467b3a
feat: Receipt Audit Feature / Note type violations.
Krishna2323 Mar 6, 2024
8c400c3
added translations.
Krishna2323 Mar 6, 2024
96744d5
extract noticeViolations from transactionViolations.
Krishna2323 Mar 6, 2024
952a4da
Update the dot separator sub-state for the request preview.
Krishna2323 Mar 7, 2024
8438383
Remove redundant code.
Krishna2323 Mar 7, 2024
7f97519
minor fix.
Krishna2323 Mar 7, 2024
cebc960
minor fix.
Krishna2323 Mar 7, 2024
86014ab
hide ReceiptAudit when scan is in progress.
Krishna2323 Mar 7, 2024
3db41da
minor updates.
Krishna2323 Mar 8, 2024
afb1afb
Merge branch 'main' into krishna2323/feat/36288
Krishna2323 Mar 11, 2024
b538331
Update MoneyRequestView.tsx
Krishna2323 Mar 11, 2024
48d1543
fix: translations.
Krishna2323 Mar 12, 2024
77165e7
minor fixes.
Krishna2323 Mar 17, 2024
699bb28
Merge branch 'Expensify:main' into krishna2323/feat/36288
Krishna2323 Mar 19, 2024
25cc0fa
receipt audit design changes.
Krishna2323 Mar 20, 2024
da3ae8c
Receipt audit design updates.
Krishna2323 Mar 22, 2024
f2a59fd
Merge branch 'Expensify:main' into krishna2323/feat/36288
Krishna2323 Mar 25, 2024
fd457cd
show notes violation for only admins and approvers in a paid policy.
Krishna2323 Mar 25, 2024
7a194c9
Merge branch 'krishna2323/feat/36288' of https://github.com/Krishna23…
Krishna2323 Mar 25, 2024
0de547a
Merge branch 'main' into krishna2323/feat/36288
Krishna2323 Mar 29, 2024
b785e1f
show notes violation to everyone.
Krishna2323 Mar 29, 2024
f4e118a
fix: violation messages styles.
Krishna2323 Mar 29, 2024
67d7dc4
Merge branch 'main' into krishna2323/feat/36288
Krishna2323 Mar 31, 2024
80b7eb5
Update MoneyRequestView.tsx
Krishna2323 Mar 31, 2024
bfec360
remove ReceiptAuditHeader condition.
Krishna2323 Mar 31, 2024
d9975d8
Merge branch 'Expensify:main' into krishna2323/feat/36288
Krishna2323 Apr 3, 2024
269163f
Merge branch 'Expensify:main' into krishna2323/feat/36288
Krishna2323 Apr 4, 2024
ae2324e
fix: margins issue.
Krishna2323 Apr 4, 2024
4bb11b5
Merge branch 'Expensify:main' into krishna2323/feat/36288
Krishna2323 Apr 7, 2024
899f12c
Merge branch 'Expensify:main' into krishna2323/feat/36288
Krishna2323 Apr 9, 2024
4225d83
show red dot when review is required.
Krishna2323 Apr 9, 2024
98ef4fb
Merge branch 'Expensify:main' into krishna2323/feat/36288
Krishna2323 Apr 13, 2024
78b79cc
allow admin & approver to update receipt when iou request is open.
Krishna2323 Apr 14, 2024
87270fb
revert: allow admin & approver to update receipt when iou request is …
Krishna2323 Apr 14, 2024
51cb29d
Merge branch 'Expensify:main' into krishna2323/feat/36288
Krishna2323 Apr 18, 2024
cd91773
show empty receipt for admin & approver if not present.
Krishna2323 Apr 18, 2024
ca0650b
revert margin changes.
Krishna2323 Apr 19, 2024
943df82
Merge branch 'main' into krishna2323/feat/36288
Krishna2323 Apr 20, 2024
cd52fee
Merge branch 'Expensify:main' into krishna2323/feat/36288
Krishna2323 Apr 23, 2024
1ae1c5b
show audit status only when receipt is scanned.
Krishna2323 Apr 23, 2024
31f34a8
Merge branch 'main' into krishna2323/feat/36288
Krishna2323 Apr 24, 2024
2c9ab1e
fix: disabled receipt cursor style.
Krishna2323 Apr 24, 2024
8f17210
Merge branch 'Expensify:main' into krishna2323/feat/36288
Krishna2323 Apr 25, 2024
148c2b9
fix: no space between audit message and notes violations.
Krishna2323 Apr 25, 2024
ace1043
fix lint warning.
Krishna2323 Apr 26, 2024
cfbddf1
minor updates according to suggestions.
Krishna2323 Apr 29, 2024
a0a2fc3
second commit: minor updates according to suggestions.
Krishna2323 Apr 29, 2024
812fd14
Merge branch 'Expensify:main' into krishna2323/feat/36288
Krishna2323 Apr 29, 2024
9fbf78e
third commit: minor updates according to suggestions.
Krishna2323 Apr 29, 2024
4081f85
Merge branch 'krishna2323/feat/36288' of https://github.com/Krishna23…
Krishna2323 Apr 29, 2024
e117ef5
fourth commit: minor updates according to suggestions.
Krishna2323 Apr 29, 2024
22e6fcb
5th commit: minor updates according to suggestions.
Krishna2323 Apr 29, 2024
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: 1 addition & 1 deletion src/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ function MoneyRequestHeader({
// Only the requestor can take delete the expense, admins can only edit it.
const isActionOwner = typeof parentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && parentReportAction.actorAccountID === session?.accountID;
const isPolicyAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;
const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && (session?.accountID ?? null) === moneyRequestReport?.managerID;
const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID;

const deleteTransaction = useCallback(() => {
if (parentReportAction) {
Expand Down
42 changes: 42 additions & 0 deletions src/components/ReceiptAudit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import Text from './Text';

function ReceiptAuditHeader({notes, shouldShowAuditMessage}: {notes: string[]; shouldShowAuditMessage: boolean}) {
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();

const auditText = notes.length > 0 ? translate('iou.receiptIssuesFound', notes.length) : translate('common.verified');
return (
<View style={[styles.ph5, styles.mbn1]}>
<View style={[styles.flexRow, styles.alignItemsCenter]}>
<Text style={[styles.textLabelSupporting]}>{translate('common.receipt')}</Text>
{shouldShowAuditMessage && (
<>
<Text style={[styles.textLabelSupporting]}>{` • ${auditText}`}</Text>
<Icon
width={12}
height={12}
src={notes.length ? Expensicons.DotIndicator : Expensicons.Checkmark}
fill={notes.length ? theme.danger : theme.success}
additionalStyles={styles.ml1}
/>
</>
)}
</View>
</View>
);
}

function ReceiptAuditMessages({notes = []}: {notes?: string[]}) {
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
const styles = useThemeStyles();
return <View style={[styles.mtn1, styles.mb2, styles.ph5, styles.gap1]}>{notes.length > 0 && notes.map((message) => <Text style={[styles.textLabelError]}>{message}</Text>)}</View>;
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
}

export {ReceiptAuditHeader, ReceiptAuditMessages};
6 changes: 5 additions & 1 deletion src/components/ReceiptEmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ type ReceiptEmptyStateProps = {

/** Callback to be called on onPress */
onPress?: () => void;

disabled?: boolean;
};

// Returns an SVG icon indicating that the user should attach a receipt
function ReceiptEmptyState({hasError = false, onPress = () => {}}: ReceiptEmptyStateProps) {
function ReceiptEmptyState({hasError = false, onPress = () => {}, disabled = false}: ReceiptEmptyStateProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

Expand All @@ -24,6 +26,8 @@ function ReceiptEmptyState({hasError = false, onPress = () => {}}: ReceiptEmptyS
accessibilityRole="imagebutton"
accessibilityLabel={translate('receipt.upload')}
onPress={onPress}
disabled={disabled}
disabledStyle={styles.cursorDefault}
style={[styles.alignItemsCenter, styles.justifyContentCenter, styles.moneyRequestViewImage, styles.moneyRequestAttachReceipt, hasError && styles.borderColorDanger]}
>
<Icon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,16 @@ function MoneyRequestPreviewContent({
const isSettlementOrApprovalPartial = Boolean(iouReport?.pendingFields?.partial);
const isPartialHold = isSettlementOrApprovalPartial && isOnHold;
const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '', transactionViolations);
const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '', transactionViolations);
const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction);
const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction);
const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction);
const isCardTransaction = TransactionUtils.isCardTransaction(transaction);
const isSettled = ReportUtils.isSettled(iouReport?.reportID);
const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
const shouldShowRBR =
hasViolations || hasFieldErrors || (!(isSettled && !isSettlementOrApprovalPartial) && !(ReportUtils.isReportApproved(iouReport) && !isSettlementOrApprovalPartial) && isOnHold);
const isFullySettled = isSettled && !isSettlementOrApprovalPartial;
const isFullyApproved = ReportUtils.isReportApproved(iouReport) && !isSettlementOrApprovalPartial;
const shouldShowRBR = hasNoticeTypeViolations || hasViolations || hasFieldErrors || (!isFullySettled && !isFullyApproved && isOnHold);

/*
Show the merchant for IOUs and expenses only if:
Expand Down Expand Up @@ -178,6 +180,8 @@ function MoneyRequestPreviewContent({
} else if (!(isSettled && !isSettlementOrApprovalPartial) && isOnHold) {
message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.hold')}`;
}
} else if (hasNoticeTypeViolations && transaction && !ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID)) {
message += ` • ${translate('violations.reviewRequired')}`;
} else if (ReportUtils.isPaidGroupPolicyExpenseReport(iouReport) && ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID) && !isPartialHold) {
message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.approved')}`;
} else if (iouReport?.isWaitingOnBankAccount) {
Expand Down
28 changes: 23 additions & 5 deletions src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import ConfirmedRoute from '@components/ConfirmedRoute';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import {useSession} from '@components/OnyxProvider';
import {ReceiptAuditHeader, ReceiptAuditMessages} from '@components/ReceiptAudit';
import ReceiptEmptyState from '@components/ReceiptEmptyState';
import SpacerView from '@components/SpacerView';
import Switch from '@components/Switch';
Expand Down Expand Up @@ -99,6 +101,7 @@ function MoneyRequestView({
}: MoneyRequestViewProps) {
const theme = useTheme();
const styles = useThemeStyles();
const session = useSession();
const StyleUtils = useStyleUtils();
const {isOffline} = useNetwork();
const {isSmallScreenWidth} = useWindowDimensions();
Expand All @@ -125,7 +128,7 @@ function MoneyRequestView({
const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction);
const formattedTransactionAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : '';
const hasPendingWaypoints = transaction?.pendingFields?.waypoints;
const hasPendingWaypoints = Boolean(transaction?.pendingFields?.waypoints);
const showMapAsImage = isDistanceRequest && hasPendingWaypoints;
const formattedOriginalAmount = transactionOriginalAmount && transactionOriginalCurrency && CurrencyUtils.convertToDisplayString(transactionOriginalAmount, transactionOriginalCurrency);
const isCardTransaction = TransactionUtils.isCardTransaction(transaction);
Expand All @@ -151,9 +154,14 @@ function MoneyRequestView({
const canEditMerchant = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.MERCHANT);
const canEditDate = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE);
const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT);
const hasReceipt = TransactionUtils.hasReceipt(transaction);
const isReceiptBeingScanned = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction);
const didRceiptScanSucceed = hasReceipt && TransactionUtils.didRceiptScanSucceed(transaction);
// TODO: remove the !isTrackExpense from this condition after this fix: https://github.com/Expensify/Expensify/issues/382786
const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE) && !isTrackExpense;

const isAdmin = policy?.role === 'admin';
const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID;
// A flag for verifying that the current report is a sub-report of a workspace chat
// if the policy of the report is either Collect or Control, then this report must be tied to workspace chat
const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report);
Expand Down Expand Up @@ -238,7 +246,6 @@ function MoneyRequestView({
}
}

const hasReceipt = TransactionUtils.hasReceipt(transaction);
let receiptURIs;
const hasErrors = canEdit && TransactionUtils.hasMissingSmartscanFields(transaction);
if (hasReceipt) {
Expand Down Expand Up @@ -324,12 +331,20 @@ function MoneyRequestView({
</OfflineWithFeedback>
);

const shouldShowMapOrReceipt = showMapAsImage || hasReceipt;
Copy link
Contributor

Choose a reason for hiding this comment

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

  • Let's move const showMapAsImage = isDistanceRequest && hasPendingWaypoints; above this line
  • Let's move const hasPendingWaypoints = transaction?.pendingFields?.waypoints; above that ^ line and make it const hasPendingWaypoints = Boolean(transaction?.pendingFields?.waypoints;)
    • That way hasPendingWaypoints is a bool
    • And we can remove the eslint rule suppression

Copy link
Contributor Author

Choose a reason for hiding this comment

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

can you pls check again.

const shouldShowReceiptEmptyState = !hasReceipt && (canEditReceipt || isAdmin || isApprover);
const noticeTypeViolations = transactionViolations?.filter((violation) => violation.type === 'notice').map((v) => ViolationsUtils.getViolationTranslation(v, translate)) ?? [];
const shouldShowNotesViolations = !isReceiptBeingScanned && canUseViolations && ReportUtils.isPaidGroupPolicy(report);

return (
<View style={[StyleUtils.getReportWelcomeContainerStyle(isSmallScreenWidth, true, shouldShowAnimatedBackground)]}>
{shouldShowAnimatedBackground && <AnimatedEmptyStateBackground />}
<View style={shouldShowAnimatedBackground && [StyleUtils.getReportWelcomeTopMarginStyle(isSmallScreenWidth, true)]}>
{/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */}
{(showMapAsImage || hasReceipt) && (
<ReceiptAuditHeader
notes={noticeTypeViolations}
shouldShowAuditMessage={Boolean(shouldShowNotesViolations && didRceiptScanSucceed)}
/>
{shouldShowMapOrReceipt && (
<OfflineWithFeedback
pendingAction={pendingAction}
errors={transaction?.errors}
Expand Down Expand Up @@ -359,9 +374,10 @@ function MoneyRequestView({
</View>
</OfflineWithFeedback>
)}
{!hasReceipt && canEditReceipt && (
{shouldShowReceiptEmptyState && (
<ReceiptEmptyState
hasError={hasErrors}
disabled={!canEditReceipt}
onPress={() =>
Navigation.navigate(
ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(
Expand All @@ -375,6 +391,8 @@ function MoneyRequestView({
}
/>
)}
{!shouldShowReceiptEmptyState && !shouldShowMapOrReceipt && <View style={{marginVertical: 6}} />}
{shouldShowNotesViolations && <ReceiptAuditMessages notes={noticeTypeViolations} />}
{canUseViolations && <ViolationMessages violations={getViolationsForField('receipt')} />}
<OfflineWithFeedback pendingAction={getPendingFieldAction('amount')}>
<MenuItemWithTopDescription
Expand Down
2 changes: 1 addition & 1 deletion src/components/ViolationMessages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function ViolationMessages({violations, isLast, containerStyle, t
const violationMessages = useMemo(() => violations.map((violation) => [violation.name, ViolationsUtils.getViolationTranslation(violation, translate)]), [translate, violations]);

return (
<View style={[styles.mtn2, isLast ? styles.mb2 : styles.mb1, containerStyle]}>
<View style={[styles.mtn1, isLast ? styles.mb2 : styles.mb1, containerStyle, styles.gap1]}>
{violationMessages.map(([name, message]) => (
<Text
key={`violationMessages.${name}`}
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ export default {
nonBillable: 'Non-billable',
tag: 'Tag',
receipt: 'Receipt',
verified: 'Verified',
replace: 'Replace',
distance: 'Distance',
mile: 'mile',
Expand Down Expand Up @@ -635,6 +636,7 @@ export default {
canceled: 'Canceled',
posted: 'Posted',
deleteReceipt: 'Delete receipt',
receiptIssuesFound: (count: number) => `${count === 1 ? 'Issue' : 'Issues'} found`,
fieldPending: 'Pending...',
defaultRate: 'Default rate',
receiptScanning: 'Scan in progress…',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ export default {
nonBillable: 'No facturable',
tag: 'Etiqueta',
receipt: 'Recibo',
verified: 'Verificado',
replace: 'Sustituir',
distance: 'Distancia',
mile: 'milla',
Expand Down Expand Up @@ -628,6 +629,7 @@ export default {
canceled: 'Canceló',
posted: 'Contabilizado',
deleteReceipt: 'Eliminar recibo',
receiptIssuesFound: (count: number) => `${count === 1 ? 'Problema encontrado' : 'Problemas encontrados'}`,
fieldPending: 'Pendiente...',
defaultRate: 'Tasa predeterminada',
receiptScanning: 'Escaneo en curso…',
Expand Down
13 changes: 13 additions & 0 deletions src/libs/TransactionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,10 @@ function isReceiptBeingScanned(transaction: OnyxEntry<Transaction>): boolean {
return [CONST.IOU.RECEIPT_STATE.SCANREADY, CONST.IOU.RECEIPT_STATE.SCANNING].some((value) => value === transaction?.receipt?.state);
}

function didRceiptScanSucceed(transaction: OnyxEntry<Transaction>): boolean {
return [CONST.IOU.RECEIPT_STATE.SCANCOMPLETE].some((value) => value === transaction?.receipt?.state);
}

/**
* Check if the transaction has a non-smartscanning receipt and is missing required fields
*/
Expand Down Expand Up @@ -606,6 +610,13 @@ function hasViolation(transactionID: string, transactionViolations: OnyxCollecti
);
}

/**
* Checks if any violations for the provided transaction are of type 'notice'
*/
function hasNoticeTypeViolation(transactionID: string, transactionViolations: OnyxCollection<TransactionViolation[]>): boolean {
return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'notice'));
}

function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection<TransactionViolation[]>): TransactionViolation[] | null {
return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null;
}
Expand Down Expand Up @@ -691,6 +702,7 @@ export {
hasEReceipt,
hasRoute,
isReceiptBeingScanned,
didRceiptScanSucceed,
getValidWaypoints,
isDistanceRequest,
isFetchingWaypointsFromServer,
Expand All @@ -711,6 +723,7 @@ export {
waypointHasValidAddress,
getRecentTransactions,
hasViolation,
hasNoticeTypeViolation,
isCustomUnitRateIDForP2P,
getRateID,
};
Expand Down
Loading