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

Polish: Dupe detection #45890

Merged
Merged
4 changes: 2 additions & 2 deletions src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ function MoneyRequestView({
// transactionTag can be an empty string
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists));
const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true));
const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true) || !!updatedTransaction?.billable);

const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest);
const tripID = ReportUtils.getTripIDFromTransactionParentReport(parentReport);
Expand Down Expand Up @@ -639,7 +639,7 @@ function MoneyRequestView({
</View>
<Switch
accessibilityLabel={translate('common.billable')}
isOn={!!transactionBillable}
isOn={updatedTransaction?.billable ?? !!transactionBillable}
onToggle={saveBillable}
disabled={!canEdit || readonly}
/>
Expand Down
81 changes: 52 additions & 29 deletions src/libs/TransactionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -866,20 +866,47 @@ function compareDuplicateTransactionFields(transactionID: string): {keep: Partia
reimbursable: ['reimbursable'],
};

const getDifferentValues = (items: Array<OnyxEntry<Transaction>>, keys: Array<keyof Transaction>) => [
...new Set(
items
.map((item) => {
// Prioritize modifiedMerchant over merchant
if (keys.includes('modifiedMerchant' as keyof Transaction) && keys.includes('merchant' as keyof Transaction)) {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
return item?.modifiedMerchant || item?.merchant;
}
return keys.map((key) => item?.[key]);
})
.flat(),
),
];
// Helper function thats create an array of different values for a given key in the transactions
function getDifferentValues(items: Array<OnyxEntry<Transaction>>, keys: Array<keyof Transaction>) {
kubabutkiewicz marked this conversation as resolved.
Show resolved Hide resolved
return [
...new Set(
items
.map((item) => {
// Prioritize modifiedMerchant over merchant
if (keys.includes('modifiedMerchant' as keyof Transaction) && keys.includes('merchant' as keyof Transaction)) {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
return getMerchant(item);
}
return keys.map((key) => item?.[key]);
})
.flat(),
),
];
}

// Helper function to check if all comments are equal
kubabutkiewicz marked this conversation as resolved.
Show resolved Hide resolved
function areAllCommentsEqual(items: Array<OnyxEntry<Transaction>>, firstTransaction: OnyxEntry<Transaction>) {
return items.every((item) => lodashIsEqual(item?.comment, firstTransaction?.comment));
}

// Helper function to check if all comments exist
function doAllCommentsExist(items: Array<OnyxEntry<Transaction>>, firstTransaction: OnyxEntry<Transaction>) {
return items.every((item) => !!item?.comment.comment === !!firstTransaction?.comment.comment);
}

// Helper function to check if all fields are equal for a given key
function areAllFieldsEqual(items: Array<OnyxEntry<Transaction>>, keyExtractor: (item: OnyxEntry<Transaction>) => string) {
const firstTransaction = transactions[0];
return items.every((item) => keyExtractor(item) === keyExtractor(firstTransaction));
}

// Helper function to process changes
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not going to block on this, but you could have been a bit more specific, like process changes for what purpose? Explain why we need this since the comment just say the same as the function name

function processChanges(fieldName: string, items: Array<OnyxEntry<Transaction>>, keys: Array<keyof Transaction>) {
const differentValues = getDifferentValues(items, keys);
if (differentValues.length > 0) {
change[fieldName] = differentValues;
}
}

for (const fieldName in fieldsToCompare) {
if (Object.prototype.hasOwnProperty.call(fieldsToCompare, fieldName)) {
Expand All @@ -888,29 +915,25 @@ function compareDuplicateTransactionFields(transactionID: string): {keep: Partia
const isFirstTransactionCommentEmptyObject = typeof firstTransaction?.comment === 'object' && firstTransaction?.comment.comment === '';

if (fieldName === 'description') {
const allCommentsAreEqual = transactions.every((item) => lodashIsEqual(item?.comment, firstTransaction?.comment));
const allCommentsExist = transactions.every((item) => !!item?.comment.comment === !!firstTransaction?.comment.comment);
const allCommentsAreEqual = areAllCommentsEqual(transactions, firstTransaction);
const allCommentsExist = doAllCommentsExist(transactions, firstTransaction);
const allCommentsAreEmpty = isFirstTransactionCommentEmptyObject && transactions.every((item) => item?.comment === undefined);

if (allCommentsAreEqual || allCommentsExist || allCommentsAreEmpty) {
keep[fieldName] = firstTransaction?.comment.comment ?? firstTransaction?.comment;
} else {
const differentValues = getDifferentValues(transactions, keys);
if (differentValues.length > 0) {
change[fieldName] = differentValues;
}
processChanges(fieldName, transactions, keys);
}
} else {
const allFieldsAreEqual = transactions.every((item) => keys.every((key) => item?.[key] === firstTransaction?.[key]));

if (allFieldsAreEqual) {
keep[fieldName] = firstTransaction?.[keys[0]];
} else if (fieldName === 'merchant') {
if (areAllFieldsEqual(transactions, getMerchant)) {
keep[fieldName] = getMerchant(firstTransaction);
} else {
const differentValues = getDifferentValues(transactions, keys);
if (differentValues.length > 0) {
change[fieldName] = differentValues;
}
processChanges(fieldName, transactions, keys);
}
} else if (areAllFieldsEqual(transactions, (item) => keys.map((key) => item?.[key]).join('|'))) {
keep[fieldName] = firstTransaction?.[keys[0]] ?? firstTransaction?.[keys[1]];
} else {
processChanges(fieldName, transactions, keys);
}
}
}
Expand Down
65 changes: 36 additions & 29 deletions src/pages/TransactionDuplicate/Confirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import Button from '@components/Button';
import FixedFooter from '@components/FixedFooter';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import MoneyRequestView from '@components/ReportActionItem/MoneyRequestView';
import ScreenWrapper from '@components/ScreenWrapper';
Expand Down Expand Up @@ -60,36 +61,42 @@ function Confirmation() {
testID={Confirmation.displayName}
shouldShowOfflineIndicator
>
<FullPageNotFoundView shouldShow={!reviewDuplicates}>
<HeaderWithBackButton title={translate('iou.reviewDuplicates')} />
<ScrollView style={styles.mb3}>
<View style={[styles.ph5, styles.pb8]}>
<Text
family="EXP_NEW_KANSAS_MEDIUM"
fontSize={variables.fontSizeLarge}
style={styles.pb5}
>
{translate('violations.confirmDetails')}
</Text>
<Text>{translate('violations.confirmDuplicatesInfo')}</Text>
{({safeAreaPaddingBottomStyle}) => (
<FullPageNotFoundView shouldShow={!reviewDuplicates}>
<View style={[styles.flex1, safeAreaPaddingBottomStyle]}>
<HeaderWithBackButton title={translate('iou.reviewDuplicates')} />
<ScrollView>
<View style={[styles.ph5, styles.pb8]}>
<Text
family="EXP_NEW_KANSAS_MEDIUM"
fontSize={variables.fontSizeLarge}
style={styles.pb5}
>
{translate('violations.confirmDetails')}
</Text>
<Text>{translate('violations.confirmDuplicatesInfo')}</Text>
</View>
{/* We need that provider here becuase MoneyRequestView component requires that */}
<ShowContextMenuContext.Provider value={contextValue}>
<MoneyRequestView
report={report}
shouldShowAnimatedBackground={false}
readonly
updatedTransaction={transaction as OnyxEntry<Transaction>}
/>
</ShowContextMenuContext.Provider>
</ScrollView>
<FixedFooter style={styles.mtAuto}>
<Button
text={translate('common.confirm')}
success
onPress={mergeDuplicates}
large
/>
</FixedFooter>
</View>
{/* We need that provider here becuase MoneyRequestView component require that */}
<ShowContextMenuContext.Provider value={contextValue}>
<MoneyRequestView
report={report}
shouldShowAnimatedBackground={false}
readonly
updatedTransaction={transaction as OnyxEntry<Transaction>}
/>
</ShowContextMenuContext.Provider>
<Button
text={translate('common.confirm')}
success
style={[styles.ph5, styles.mt2]}
onPress={mergeDuplicates}
/>
</ScrollView>
</FullPageNotFoundView>
</FullPageNotFoundView>
)}
</ScreenWrapper>
);
}
Expand Down
Loading