Skip to content

Commit 8dc318b

Browse files
authored
Merge pull request #87603 from Expensify/alberto-oneImport
[Payment due @bernhardoj] Properly determine the feedType to assign to new CSV feeds
2 parents 54011d8 + 0f4d154 commit 8dc318b

3 files changed

Lines changed: 65 additions & 6 deletions

File tree

src/libs/CardUtils.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,43 @@ function isSmartLimitEnabled(cardsList: CardList) {
10661066

10671067
const CUSTOM_FEEDS = [CONST.COMPANY_CARD.FEED_BANK_NAME.MASTER_CARD, CONST.COMPANY_CARD.FEED_BANK_NAME.VISA, CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX, CONST.COMPANY_CARD.FEED_BANK_NAME.CSV];
10681068

1069+
function collectUsedCSVFeedSlotNumbersFromCompanyCards(companyCards: CompanyFeeds | undefined, csvPrefix: string): number[] {
1070+
const numbers: number[] = [];
1071+
for (const key of Object.keys(companyCards ?? {})) {
1072+
if (!key.startsWith(csvPrefix)) {
1073+
continue;
1074+
}
1075+
const suffix = key.slice(csvPrefix.length);
1076+
if (!suffix) {
1077+
continue;
1078+
}
1079+
const n = Number.parseInt(suffix, 10);
1080+
if (!Number.isNaN(n) && n > 0) {
1081+
numbers.push(n);
1082+
}
1083+
}
1084+
return numbers;
1085+
}
1086+
1087+
/**
1088+
* Next available CSV file-import feed (`ccuploadN`) from `settings.companyCards` keys only,
1089+
* including feeds omitted from {@link getFeedType}'s `CombinedCardFeeds` input.
1090+
*/
1091+
function getCSVFeedType(companyCards: CompanyFeeds | undefined): CompanyCardFeedWithNumber {
1092+
const csvPrefix = CONST.COMPANY_CARD.FEED_BANK_NAME.CSV;
1093+
const feedNumbers = [...new Set(collectUsedCSVFeedSlotNumbersFromCompanyCards(companyCards, csvPrefix))].sort((a, b) => a - b);
1094+
1095+
let firstAvailableNumber = 1;
1096+
for (const num of feedNumbers) {
1097+
if (num && num !== firstAvailableNumber) {
1098+
return `${csvPrefix}${firstAvailableNumber}`;
1099+
}
1100+
firstAvailableNumber++;
1101+
}
1102+
1103+
return `${csvPrefix}${firstAvailableNumber}`;
1104+
}
1105+
10691106
function getFeedType(feedKey: CompanyCardFeed, cardFeeds: OnyxEntry<CombinedCardFeeds>): CompanyCardFeedWithNumber {
10701107
if (CUSTOM_FEEDS.some((feed) => feed === feedKey)) {
10711108
const filteredFeeds = Object.keys(cardFeeds ?? {})
@@ -1748,6 +1785,7 @@ export {
17481785
filterCardsByNonExpensify,
17491786
getAllCardsForWorkspace,
17501787
isCardHiddenFromSearch,
1788+
getCSVFeedType,
17511789
getFeedType,
17521790
flattenWorkspaceCardsList,
17531791
isCardConnectionBroken,

src/pages/workspace/companyCards/addNew/CompanyCardsImportedPage.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
import React, {useState} from 'react';
1+
import React, {useMemo, useState} from 'react';
22
import HeaderWithBackButton from '@components/HeaderWithBackButton';
33
import type {ColumnRole} from '@components/ImportColumn';
44
import ImportSpreadsheetColumns from '@components/ImportSpreadsheetColumns';
55
import ImportSpreadsheetConfirmModal from '@components/ImportSpreadsheetConfirmModal';
66
import ScreenWrapper from '@components/ScreenWrapper';
7-
import useCardFeeds from '@hooks/useCardFeeds';
87
import useCloseImportPage from '@hooks/useCloseImportPage';
98
import useLocalize from '@hooks/useLocalize';
109
import useOnyx from '@hooks/useOnyx';
1110
import usePolicy from '@hooks/usePolicy';
12-
import {getFeedType} from '@libs/CardUtils';
11+
import {getCSVFeedType} from '@libs/CardUtils';
1312
import {findDuplicate, generateColumnNames} from '@libs/importSpreadsheetUtils';
1413
import Navigation from '@libs/Navigation/Navigation';
1514
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
@@ -37,13 +36,15 @@ function CompanyCardsImportedPage({route}: CompanyCardsImportedPageProps) {
3736
const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID;
3837
const [lastSelectedFeed] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`);
3938
const [workspaceCardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`);
40-
const [cardFeeds] = useCardFeeds(policyID);
4139
const [isImportingTransactions, setIsImportingTransactions] = useState(false);
4240
const {setIsClosing} = useCloseImportPage();
4341
const shouldUseAdvancedFields = addNewCard?.data?.useAdvancedFields ?? false;
4442
const layoutName = addNewCard?.data?.companyCardLayoutName ?? '';
4543
const prefilledLayoutType = addNewCard?.data?.layoutType;
46-
const [generatedLayoutType] = useState(() => prefilledLayoutType ?? getFeedType(CONST.COMPANY_CARD.FEED_BANK_NAME.CSV, cardFeeds));
44+
const generatedLayoutType = useMemo(
45+
() => prefilledLayoutType ?? getCSVFeedType(workspaceCardFeeds?.settings?.companyCards),
46+
[prefilledLayoutType, workspaceCardFeeds?.settings?.companyCards],
47+
);
4748
const layoutType = prefilledLayoutType ?? generatedLayoutType;
4849
const [existingCardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${layoutType}`);
4950

tests/unit/CardUtilsTest.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
getCompanyCardDescription,
3232
getCompanyCardFeed,
3333
getCompanyFeeds,
34+
getCSVFeedType,
3435
getCustomFeedNameFromFeeds,
3536
getCustomOrFormattedFeedName,
3637
getDefaultExpensifyCardLimitType,
@@ -80,7 +81,7 @@ import type {
8081
Policy,
8182
WorkspaceCardsList,
8283
} from '@src/types/onyx';
83-
import type {CardFeedWithDomainID, CardFeedWithNumber, CompanyCardFeedWithNumber} from '@src/types/onyx/CardFeeds';
84+
import type {CardFeedWithDomainID, CardFeedWithNumber, CompanyCardFeedWithNumber, CompanyFeeds} from '@src/types/onyx/CardFeeds';
8485
import type IconAsset from '@src/types/utils/IconAsset';
8586
import {localeCompare, translateLocal} from '../utils/TestHelper';
8687
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
@@ -1884,6 +1885,25 @@ describe('CardUtils', () => {
18841885
});
18851886
});
18861887

1888+
describe('getCSVFeedType', () => {
1889+
it('returns the first gap when higher-numbered CSV feeds exist in companyCards only', () => {
1890+
expect(
1891+
getCSVFeedType({
1892+
ccupload3: {pending: false},
1893+
ccupload7: {pending: false},
1894+
} as CompanyFeeds),
1895+
).toBe('ccupload1');
1896+
});
1897+
1898+
it('returns ccupload2 when ccupload1 is already in companyCards', () => {
1899+
expect(getCSVFeedType({ccupload1: {pending: false}} as CompanyFeeds)).toBe('ccupload2');
1900+
});
1901+
1902+
it('returns ccupload1 when no CSV feeds exist', () => {
1903+
expect(getCSVFeedType(undefined)).toBe('ccupload1');
1904+
});
1905+
});
1906+
18871907
describe('flattenCompanyCards', () => {
18881908
it('should return the flattened list of non-Expensify cards related to the provided workspaceAccountID', () => {
18891909
const workspaceAccountID = 11111111;

0 commit comments

Comments
 (0)