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 44 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
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 = [], showAuditMessage = false}: {notes?: string[]; showAuditMessage?: boolean}) {
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();

const issuesFoundText = notes.length > 0 ? translate('iou.receiptIssuesFound', notes.length) : translate('common.verified');
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
return (
<View style={[styles.ph5, styles.mbn1]}>
<View style={[styles.flexRow, styles.alignItemsCenter]}>
<Text style={[styles.textLabelSupporting]}>{translate('common.receipt')}</Text>
{showAuditMessage && (
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
<>
<Text style={[styles.textLabelSupporting]}>{` • ${issuesFoundText}`}</Text>
Copy link
Contributor

Choose a reason for hiding this comment

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

Something is off here, so let's test the Spanish version too. In English this says "Issue(s) Found", and in Spanish this will say "Problema(s)", but it should say "Problema(s) encontrado(s)" to match English and to make sense

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to:

receiptIssuesFound: (count: number) => `${count === 1 ? 'Problema' : 'Problemas'} encontrado`,

Pls let me know if it is correct or not.

Copy link
Contributor

Choose a reason for hiding this comment

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

Needs to be

receiptIssuesFound: (count: number) => `${count === 1 ? 'Problema encontrado' : 'Problemas encontrados'} `,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the confirmation, updated now.

<Icon
width={12}
height={12}
src={notes.length > 0 ? Expensicons.DotIndicator : Expensicons.Checkmark}
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
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,18 @@ function MoneyRequestPreviewContent({
const isSettlementOrApprovalPartial = Boolean(iouReport?.pendingFields?.partial);
const isPartialHold = isSettlementOrApprovalPartial && isOnHold;
const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '', transactionViolations);
const hasNoteTypeViolations = TransactionUtils.hasNoteTypeViolation(transaction?.transactionID ?? '', transactionViolations);
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
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);
hasNoteTypeViolations ||
hasViolations ||
hasFieldErrors ||
(!(isSettled && !isSettlementOrApprovalPartial) && !(ReportUtils.isReportApproved(iouReport) && !isSettlementOrApprovalPartial) && isOnHold);
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved

/*
Show the merchant for IOUs and expenses only if:
Expand Down Expand Up @@ -178,6 +182,8 @@ function MoneyRequestPreviewContent({
} else if (!(isSettled && !isSettlementOrApprovalPartial) && isOnHold) {
message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.hold')}`;
}
} else if (hasNoteTypeViolations && transaction && !ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID)) {
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
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
22 changes: 19 additions & 3 deletions src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
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 @@
}: MoneyRequestViewProps) {
const theme = useTheme();
const styles = useThemeStyles();
const session = useSession();
const StyleUtils = useStyleUtils();
const {isOffline} = useNetwork();
const {isSmallScreenWidth} = useWindowDimensions();
Expand Down Expand Up @@ -151,9 +154,14 @@
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 isReceiptScanCompleted = hasReceipt && TransactionUtils.isReceiptScanCompleted(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) && (session?.accountID ?? null) === moneyRequestReport?.managerID;
Copy link
Contributor

@cead22 cead22 Apr 29, 2024

Choose a reason for hiding this comment

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

The second condition looks wrong, or at least it doesn't seem like we need the ?? null fallback. But also, don't we need to make sure moneyRequestReport?.managerID; isn't null? I think this should be

Suggested change
const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && (session?.accountID ?? null) === moneyRequestReport?.managerID;
const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I took that condition from MoneyRequestHeader, but the suggested changes looks more accurate to me so I updated both instances.

const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && (session?.accountID ?? null) === 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 All @@ -179,6 +187,8 @@
(field: ViolationField, data?: OnyxTypes.TransactionViolation['data']): boolean => !!canUseViolations && getViolationsForField(field, data).length > 0,
[canUseViolations, getViolationsForField],
);
const noteTypeViolations = transactionViolations?.filter((violation) => violation.type === 'notice').map((v) => ViolationsUtils.getViolationTranslation(v, translate));
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
const shouldShowNotesViolations = !isReceiptBeingScanned && canUseViolations && ReportUtils.isPaidGroupPolicy(report);

let amountDescription = `${translate('iou.amount')}`;

Expand Down Expand Up @@ -233,7 +243,6 @@
}
}

const hasReceipt = TransactionUtils.hasReceipt(transaction);
let receiptURIs;
const hasErrors = canEdit && TransactionUtils.hasMissingSmartscanFields(transaction);
if (hasReceipt) {
Expand Down Expand Up @@ -323,6 +332,10 @@
<View style={[StyleUtils.getReportWelcomeContainerStyle(isSmallScreenWidth, true, shouldShowAnimatedBackground)]}>
{shouldShowAnimatedBackground && <AnimatedEmptyStateBackground />}
<View style={shouldShowAnimatedBackground && [StyleUtils.getReportWelcomeTopMarginStyle(isSmallScreenWidth, true)]}>
<ReceiptAuditHeader
notes={noteTypeViolations}
showAuditMessage={shouldShowNotesViolations && isReceiptScanCompleted}
/>
{/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */}
{(showMapAsImage || hasReceipt) && (
<OfflineWithFeedback
Expand Down Expand Up @@ -354,9 +367,10 @@
</View>
</OfflineWithFeedback>
)}
{!hasReceipt && canEditReceipt && (
{!hasReceipt && (canEditReceipt || isAdmin || isApprover) && (
<ReceiptEmptyState
hasError={hasErrors}
disabled={!canEditReceipt}
onPress={() =>
Navigation.navigate(
ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(
Expand All @@ -370,7 +384,9 @@
}
/>
)}
{canUseViolations && <ViolationMessages violations={getViolationsForField('receipt')} />}
{!(!hasReceipt && (canEditReceipt || isAdmin || isApprover)) && !(showMapAsImage || hasReceipt) && <View style={{marginVertical: 6}} />}

Check failure on line 387 in src/components/ReportActionItem/MoneyRequestView.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint

Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator
Copy link
Contributor

Choose a reason for hiding this comment

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

lint issue

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed!

Copy link
Contributor

Choose a reason for hiding this comment

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

This is impossible to read, please intermediate variables with self-explanatory names to simplify this

{shouldShowNotesViolations && <ReceiptAuditMessages notes={noteTypeViolations} />}
<ViolationMessages violations={getViolationsForField('receipt')} />
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
<OfflineWithFeedback pendingAction={getPendingFieldAction('amount')}>
<MenuItemWithTopDescription
title={formattedTransactionAmount ? formattedTransactionAmount.toString() : ''}
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
3 changes: 3 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`,
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
replace: 'Replace',
distance: 'Distance',
mile: 'mile',
Expand Down Expand Up @@ -633,6 +634,8 @@ export default {
canceled: 'Canceled',
posted: 'Posted',
deleteReceipt: 'Delete receipt',
receiptIssuesFound: (count: number) => `${count === 1 ? 'Issue' : 'Issues'} found`,
routePending: 'Pending...',
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
fieldPending: 'Pending...',
defaultRate: 'Default rate',
receiptScanning: 'Scan in progress…',
Expand Down
3 changes: 3 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`,
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
replace: 'Sustituir',
distance: 'Distancia',
mile: 'milla',
Expand Down Expand Up @@ -626,6 +627,8 @@ export default {
canceled: 'Canceló',
posted: 'Contabilizado',
deleteReceipt: 'Eliminar recibo',
receiptIssuesFound: (count: number) => `${count === 1 ? 'Problema' : 'Problemas'}`,
routePending: 'Pendiente...',
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 isReceiptScanCompleted(transaction: OnyxEntry<Transaction>): boolean {
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -595,6 +599,13 @@ function hasViolation(transactionID: string, transactionViolations: OnyxCollecti
);
}

/**
* Checks if any violations for the provided transaction are of type 'note'
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
*/
function hasNoteTypeViolation(transactionID: string, transactionViolations: OnyxCollection<TransactionViolation[]>): boolean {
Krishna2323 marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -680,6 +691,7 @@ export {
hasEReceipt,
hasRoute,
isReceiptBeingScanned,
isReceiptScanCompleted,
getValidWaypoints,
isDistanceRequest,
isFetchingWaypointsFromServer,
Expand All @@ -699,6 +711,7 @@ export {
waypointHasValidAddress,
getRecentTransactions,
hasViolation,
hasNoteTypeViolation,
isCustomUnitRateIDForP2P,
getRateID,
};
Expand Down
Loading