diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 5fe1941316e2..6c1afb6dd4a7 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -430,16 +430,31 @@ function isSelectedFeedExpired(directFeed: DirectCardFeedData | undefined): bool } /** Returns list of cards which can be assigned */ -function getFilteredCardList(list: WorkspaceCardsList | undefined, directFeed: DirectCardFeedData | undefined) { +function getFilteredCardList(list: WorkspaceCardsList | undefined, directFeed: DirectCardFeedData | undefined, workspaceCardFeeds: OnyxCollection = allWorkspaceCards) { const {cardList: customFeedCardsToAssign, ...cards} = list ?? {}; const assignedCards = Object.values(cards).map((card) => card.cardName); + // Get cards assigned across all workspaces + const allWorkspaceAssignedCards = new Set(); + Object.values(workspaceCardFeeds ?? {}).forEach((workspaceCards) => { + if (!workspaceCards) { + return; + } + const {cardList, ...workspaceCardItems} = workspaceCards; + Object.values(workspaceCardItems).forEach((card) => { + if (!card.cardName) { + return; + } + allWorkspaceAssignedCards.add(card.cardName); + }); + }); + if (directFeed) { - const unassignedDirectFeedCards = directFeed.accountList.filter((cardNumber) => !assignedCards.includes(cardNumber)); + const unassignedDirectFeedCards = directFeed.accountList.filter((cardNumber) => !assignedCards.includes(cardNumber) && !allWorkspaceAssignedCards.has(cardNumber)); return Object.fromEntries(unassignedDirectFeedCards.map((cardNumber) => [cardNumber, cardNumber])); } - return Object.fromEntries(Object.entries(customFeedCardsToAssign ?? {}).filter(([cardNumber]) => !assignedCards.includes(cardNumber))); + return Object.fromEntries(Object.entries(customFeedCardsToAssign ?? {}).filter(([cardNumber]) => !assignedCards.includes(cardNumber) && !allWorkspaceAssignedCards.has(cardNumber))); } function hasOnlyOneCardToAssign(list: FilteredCardList) { diff --git a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx index 835e29d1cb32..09cb7da9ba1a 100644 --- a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx @@ -42,11 +42,12 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { const [searchText, setSearchText] = useState(''); const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD); const [list] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${feed}`, {selector: filterInactiveCards}); + const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST); const [cardFeeds] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID}`); const isEditing = assignCard?.isEditing; const assigneeDisplayName = getPersonalDetailByEmail(assignCard?.data?.email ?? '')?.displayName ?? ''; - const filteredCardList = getFilteredCardList(list, cardFeeds?.settings?.oAuthAccountDetails?.[feed]); + const filteredCardList = getFilteredCardList(list, cardFeeds?.settings?.oAuthAccountDetails?.[feed], workspaceCardFeeds); const [cardSelected, setCardSelected] = useState(assignCard?.data?.encryptedCardNumber ?? ''); const [shouldShowError, setShouldShowError] = useState(false); diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 916b0757ef2f..d4849d210bf7 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -595,6 +595,111 @@ describe('CardUtils', () => { const cardsList = getFilteredCardList(undefined, undefined); expect(cardsList).toStrictEqual({}); }); + + it('Should handle the case when all cards are already assigned in other workspaces', () => { + const assignedCard1 = 'CREDIT CARD...5566'; + const assignedCard2 = 'CREDIT CARD...6677'; + + const mockAllWorkspaceCards = { + cards_888888_feed: { + '11111': { + accountID: 999999, + bank: CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE, + cardID: 11111, + cardName: assignedCard1, + domainName: 'other-workspace.exfy', + state: 3, + }, + '22222': { + accountID: 999999, + bank: CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE, + cardID: 22222, + cardName: assignedCard2, + domainName: 'other-workspace.exfy', + state: 3, + }, + }, + } as unknown as OnyxCollection; + + const customFeedWithAllAssignedCards = { + cardList: { + [assignedCard1]: 'ENCRYPTED_DATA', + [assignedCard2]: 'ENCRYPTED_DATA', + }, + } as unknown as WorkspaceCardsList; + const filteredCards = getFilteredCardList(customFeedWithAllAssignedCards, undefined, mockAllWorkspaceCards); + expect(filteredCards).toStrictEqual({}); + }); + + it('Should filter out cards that are already assigned in another workspace (custom feed)', () => { + const customFeedWorkspaceCardsList = { + '21310091': { + accountID: 18439984, + bank: CONST.COMPANY_CARD.FEED_BANK_NAME.VISA, + cardID: 21310091, + cardName: '480801XXXXXX2554', + domainName: 'expensify-policy41314f4dc5ce25af.exfy', + fraud: 'none', + lastFourPAN: '2554', + lastUpdated: '', + lastScrape: '2024-11-27 11:00:53', + scrapeMinDate: '2024-10-17', + state: 3, + }, + '21310092': { + accountID: 18439985, + bank: CONST.COMPANY_CARD.FEED_BANK_NAME.VISA, + cardID: 21310092, + cardName: '480801XXXXXX2666', + domainName: 'expensify-policy41314f4dc5ce25af.exfy', + fraud: 'none', + lastFourPAN: '2666', + lastUpdated: '', + lastScrape: '2024-11-27 11:00:53', + scrapeMinDate: '2024-10-17', + state: 3, + }, + cardList: { + '480801XXXXXX2554': 'ENCRYPTED_CARD_NUMBER', + '480801XXXXXX2666': 'ENCRYPTED_CARD_NUMBER', + }, + } as unknown as WorkspaceCardsList; + + const filteredCards = getFilteredCardList(customFeedWorkspaceCardsList, undefined); + expect(filteredCards).toStrictEqual({}); + }); + + it('Should filter out cards that are already assigned in another workspace (direct feed)', () => { + const assignedCard1 = 'CREDIT CARD...3344'; + const assignedCard2 = 'CREDIT CARD...3355'; + const unassignedCard = 'CREDIT CARD...6666'; + + const mockAllWorkspaceCards = { + cards_888888_feed: { + '67889': { + accountID: 999998, + bank: CONST.COMPANY_CARD.FEED_BANK_NAME.CAPITAL_ONE, + cardID: 67889, + cardName: assignedCard1, + domainName: 'other-workspace.exfy', + state: 3, + }, + '67890': { + accountID: 999999, + bank: CONST.COMPANY_CARD.FEED_BANK_NAME.CAPITAL_ONE, + cardID: 67890, + cardName: assignedCard2, + domainName: 'other-workspace.exfy', + state: 3, + }, + }, + } as unknown as OnyxCollection; + const directFeedWithAssignedCard = { + accountList: [assignedCard1, assignedCard2, unassignedCard], + } as unknown as (typeof oAuthAccountDetails)[typeof CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE]; + const filteredCards = getFilteredCardList(undefined, directFeedWithAssignedCard, mockAllWorkspaceCards); + expect(filteredCards).toStrictEqual({[`${unassignedCard}`]: unassignedCard}); + }); }); describe('getFeedType', () => {