From a840ffc4e254451baee5dc9de152dc9d00b4647f Mon Sep 17 00:00:00 2001 From: Kevin Brian Bader Date: Fri, 26 Dec 2025 17:03:03 -0800 Subject: [PATCH 1/2] feat: make transaction start date editable after assignment --- src/ROUTES.ts | 5 + src/SCREENS.ts | 1 + .../UpdateCardTransactionStartDateParams.ts | 7 + src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../ModalStackNavigators/index.tsx | 2 + .../RELATIONS/WORKSPACE_TO_RHP.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 + src/libs/Navigation/types.ts | 5 + src/libs/actions/CompanyCards.ts | 64 +++++++ .../WorkspaceCompanyCardDetailsPage.tsx | 19 +- ...ompanyCardEditTransactionStartDatePage.tsx | 162 ++++++++++++++++++ 12 files changed, 267 insertions(+), 5 deletions(-) create mode 100644 src/libs/API/parameters/UpdateCardTransactionStartDateParams.ts create mode 100644 src/pages/workspace/companyCards/WorkspaceCompanyCardEditTransactionStartDatePage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a1111f4f1419..da0d3747ce17 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -2239,6 +2239,11 @@ const ROUTES = { getRoute: (policyID: string, cardID: string, feed: CompanyCardFeedWithDomainID) => `workspaces/${policyID}/company-cards/${encodeURIComponent(feed)}/${encodeURIComponent(cardID)}/edit/name` as const, }, + WORKSPACE_COMPANY_CARD_EDIT_TRANSACTION_START_DATE: { + route: 'workspaces/:policyID/company-cards/:feed/:cardID/edit/transaction-start-date', + getRoute: (policyID: string, cardID: string, feed: CompanyCardFeedWithDomainID) => + `workspaces/${policyID}/company-cards/${encodeURIComponent(feed)}/${encodeURIComponent(cardID)}/edit/transaction-start-date` as const, + }, WORKSPACE_COMPANY_CARD_EXPORT: { route: 'workspaces/:policyID/company-cards/:feed/:cardID/edit/export', getRoute: (policyID: string, cardID: string, feed: CompanyCardFeedWithDomainID, backTo?: string) => diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 450a5beba884..7decb4c343c1 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -600,6 +600,7 @@ const SCREENS = { COMPANY_CARDS_SETTINGS_STATEMENT_CLOSE_DATE: 'Workspace_CompanyCards_Settings_Statement_Close_Date', COMPANY_CARD_DETAILS: 'Workspace_CompanyCard_Details', COMPANY_CARD_EDIT_CARD_NAME: 'Workspace_CompanyCard_Edit_Card_Name', + COMPANY_CARD_EDIT_TRANSACTION_START_DATE: 'Workspace_CompanyCard_Edit_Transaction_Start_Date', COMPANY_CARD_EXPORT: 'Workspace_CompanyCard_Export', EXPENSIFY_CARD: 'Workspace_ExpensifyCard', EXPENSIFY_CARD_DETAILS: 'Workspace_ExpensifyCard_Details', diff --git a/src/libs/API/parameters/UpdateCardTransactionStartDateParams.ts b/src/libs/API/parameters/UpdateCardTransactionStartDateParams.ts new file mode 100644 index 000000000000..282ba2507527 --- /dev/null +++ b/src/libs/API/parameters/UpdateCardTransactionStartDateParams.ts @@ -0,0 +1,7 @@ +type UpdateCardTransactionStartDateParams = { + authToken?: string | null; + cardID: number; + startDate: string; +}; + +export default UpdateCardTransactionStartDateParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 8d6b01cc6317..bece054a0237 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -379,6 +379,7 @@ export type {default as AssignCompanyCardParams} from './AssignCompanyCardParams export type {default as UnassignCompanyCard} from './UnassignCompanyCard'; export type {default as UpdateCompanyCard} from './UpdateCompanyCard'; export type {default as UpdateCompanyCardNameParams} from './UpdateCompanyCardNameParams'; +export type {default as UpdateCardTransactionStartDateParams} from './UpdateCardTransactionStartDateParams'; export type {default as SetCompanyCardExportAccountParams} from './SetCompanyCardExportAccountParams'; export type {default as SetPersonalDetailsAndShipExpensifyCardsParams} from './SetPersonalDetailsAndShipExpensifyCardsParams'; export type {default as SetPersonalDetailsAndRevealExpensifyCardParams} from './SetPersonalDetailsAndRevealExpensifyCardParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 55420bcc09dc..3eb92f0892e4 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -483,6 +483,7 @@ const WRITE_COMMANDS = { UNASSIGN_COMPANY_CARD: 'UnassignCard', UPDATE_COMPANY_CARD: 'SyncCard', UPDATE_COMPANY_CARD_NAME: 'SetCardName', + UPDATE_CARD_TRANSACTION_START_DATE: 'UpdateCardTransactionStartDate', SET_CARD_EXPORT_ACCOUNT: 'SetCardExportAccount', SET_PERSONAL_DETAILS_AND_SHIP_EXPENSIFY_CARDS: 'SetPersonalDetailsAndShipExpensifyCards', SET_INVOICING_TRANSFER_BANK_ACCOUNT: 'SetInvoicingTransferBankAccount', @@ -603,6 +604,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UNASSIGN_COMPANY_CARD]: Parameters.UnassignCompanyCard; [WRITE_COMMANDS.UPDATE_COMPANY_CARD]: Parameters.UpdateCompanyCard; [WRITE_COMMANDS.UPDATE_COMPANY_CARD_NAME]: Parameters.UpdateCompanyCardNameParams; + [WRITE_COMMANDS.UPDATE_CARD_TRANSACTION_START_DATE]: Parameters.UpdateCardTransactionStartDateParams; [WRITE_COMMANDS.SET_CARD_EXPORT_ACCOUNT]: Parameters.SetCompanyCardExportAccountParams; [WRITE_COMMANDS.SET_COMPANY_CARD_TRANSACTION_LIABILITY]: Parameters.SetCompanyCardTransactionLiability; [WRITE_COMMANDS.OPEN_POLICY_ADD_CARD_FEED_PAGE]: Parameters.OpenPolicyAddCardFeedPageParams; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 8ce2fd5a88d1..dc8c9a6a6b65 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -710,6 +710,8 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/companyCards/addNew/AddNewCardPage').default, [SCREENS.WORKSPACE.COMPANY_CARD_DETAILS]: () => require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage').default, [SCREENS.WORKSPACE.COMPANY_CARD_EDIT_CARD_NAME]: () => require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardEditCardNamePage').default, + [SCREENS.WORKSPACE.COMPANY_CARD_EDIT_TRANSACTION_START_DATE]: () => + require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardEditTransactionStartDatePage').default, [SCREENS.WORKSPACE.COMPANY_CARD_EXPORT]: () => require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardAccountSelectCardPage').default, [SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW]: () => require('../../../../pages/workspace/expensifyCard/issueNew/IssueNewCardPage').default, [SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW_CONFIRM_MAGIC_CODE]: () => diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts index 5dee0b252dd9..53f1f0cdac3a 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts @@ -251,6 +251,7 @@ const WORKSPACE_TO_RHP: Partial['config'] = { [SCREENS.WORKSPACE.COMPANY_CARD_EDIT_CARD_NAME]: { path: ROUTES.WORKSPACE_COMPANY_CARD_EDIT_CARD_NAME.route, }, + [SCREENS.WORKSPACE.COMPANY_CARD_EDIT_TRANSACTION_START_DATE]: { + path: ROUTES.WORKSPACE_COMPANY_CARD_EDIT_TRANSACTION_START_DATE.route, + }, [SCREENS.WORKSPACE.COMPANY_CARD_EXPORT]: { path: ROUTES.WORKSPACE_COMPANY_CARD_EXPORT.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index ffb65b171dab..88bba9f645dc 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1160,6 +1160,11 @@ type SettingsNavigatorParamList = { feed: CompanyCardFeedWithDomainID; cardID: string; }; + [SCREENS.WORKSPACE.COMPANY_CARD_EDIT_TRANSACTION_START_DATE]: { + policyID: string; + feed: string; + cardID: string; + }; [SCREENS.WORKSPACE.COMPANY_CARD_EXPORT]: { policyID: string; cardID: string; diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts index ea0f4ab5b510..42dbd0c1f9dc 100644 --- a/src/libs/actions/CompanyCards.ts +++ b/src/libs/actions/CompanyCards.ts @@ -11,6 +11,7 @@ import type { RequestFeedSetupParams, SetCompanyCardExportAccountParams, SetFeedStatementPeriodEndDayParams, + UpdateCardTransactionStartDateParams, UpdateCompanyCardNameParams, } from '@libs/API/parameters'; import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; @@ -675,6 +676,68 @@ function updateCompanyCardName(domainOrWorkspaceAccountID: number, cardID: strin API.write(WRITE_COMMANDS.UPDATE_COMPANY_CARD_NAME, parameters, {optimisticData, finallyData, failureData}); } +function updateCardTransactionStartDate(domainOrWorkspaceAccountID: number, cardID: string, newStartDate: string, bankName: CompanyCardFeed, oldStartDate?: string) { + const authToken = NetworkStore.getAuthToken(); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${domainOrWorkspaceAccountID}_${bankName}`, + value: { + [cardID]: { + scrapeMinDate: newStartDate, + pendingFields: { + scrapeMinDate: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + errorFields: { + scrapeMinDate: null, + }, + }, + }, + }, + ]; + + const finallyData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${domainOrWorkspaceAccountID}_${bankName}`, + value: { + [cardID]: { + pendingFields: { + scrapeMinDate: null, + }, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${domainOrWorkspaceAccountID}_${bankName}`, + value: { + [cardID]: { + scrapeMinDate: oldStartDate, + pendingFields: { + scrapeMinDate: null, + }, + errorFields: { + scrapeMinDate: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), + }, + }, + }, + }, + ]; + + const parameters: UpdateCardTransactionStartDateParams = { + authToken, + cardID: Number(cardID), + startDate: newStartDate, + }; + + API.write(WRITE_COMMANDS.UPDATE_CARD_TRANSACTION_START_DATE, parameters, {optimisticData, finallyData, failureData}); +} + function setCompanyCardExportAccount(policyID: string, domainOrWorkspaceAccountID: number, cardID: string, accountKey: string, newAccount: string, bank: CompanyCardFeed) { const authToken = NetworkStore.getAuthToken(); @@ -980,6 +1043,7 @@ export { resetFailedWorkspaceCompanyCardAssignment, updateWorkspaceCompanyCard, updateCompanyCardName, + updateCardTransactionStartDate, setCompanyCardExportAccount, clearCompanyCardErrorField, setAddNewCompanyCardStepAndData, diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx index 12f5c1a4e250..58fe341f94ec 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage.tsx @@ -203,11 +203,20 @@ function WorkspaceCompanyCardDetailsPage({route}: WorkspaceCompanyCardDetailsPag title={card?.isLoadingLastUpdated ? translate('workspace.moreFeatures.companyCards.updating') : lastScrape} interactive={false} /> - + clearCompanyCardErrorField(domainOrWorkspaceAccountID, cardID, bank, 'scrapeMinDate', true)} + > + Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARD_EDIT_TRANSACTION_START_DATE.getRoute(policyID, cardID, feedName))} + /> + ; +type WorkspaceCompanyCardEditTransactionStartDatePageProps = PlatformStackScreenProps; + +function WorkspaceCompanyCardEditTransactionStartDatePage({route}: WorkspaceCompanyCardEditTransactionStartDatePageProps) { + const {policyID, cardID} = route.params; + const feedName = decodeURIComponent(route.params.feed) as CompanyCardFeedWithDomainID; + const bank = getCompanyCardFeed(feedName); + + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const policy = usePolicy(policyID); + const workspaceAccountID = policy?.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID; + + const [cardFeeds] = useCardFeeds(policyID); + const companyFeeds = getCompanyFeeds(cardFeeds); + const domainOrWorkspaceAccountID = getDomainOrWorkspaceAccountID(workspaceAccountID, companyFeeds[feedName]); + + const [allBankCards] = useCardsList(feedName); + const card = allBankCards?.[cardID]; + const currentStartDate = card?.scrapeMinDate; + + const [dateOptionSelected, setDateOptionSelected] = useState(CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.CUSTOM); + + const [startDate, setStartDate] = useState(() => { + if (currentStartDate) { + return format(parseISO(currentStartDate), CONST.DATE.FNS_FORMAT_STRING); + } + return format(new Date(), CONST.DATE.FNS_FORMAT_STRING); + }); + + const [errorText, setErrorText] = useState(''); + + const handleSelectDateOption = (dateOption: DateOption) => { + setErrorText(''); + setDateOptionSelected(dateOption); + if (dateOption === CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.FROM_BEGINNING) { + return; + } + // Reset to current date when switching to custom + if (!currentStartDate) { + setStartDate(format(new Date(), CONST.DATE.FNS_FORMAT_STRING)); + } + }; + + const submit = () => { + if (dateOptionSelected === CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.CUSTOM && !isRequiredFulfilled(startDate)) { + setErrorText(translate('common.error.fieldRequired')); + return; + } + + const date90DaysBack = format(subDays(new Date(), 90), CONST.DATE.FNS_FORMAT_STRING); + const newStartDate = dateOptionSelected === CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.FROM_BEGINNING ? date90DaysBack : startDate; + + updateCardTransactionStartDate(domainOrWorkspaceAccountID, cardID, newStartDate, bank, currentStartDate); + Navigation.goBack(); + }; + + const dateOptions = [ + { + value: CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.FROM_BEGINNING, + text: translate('workspace.companyCards.fromTheBeginning'), + keyForList: CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.FROM_BEGINNING, + isSelected: dateOptionSelected === CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.FROM_BEGINNING, + }, + { + value: CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.CUSTOM, + text: translate('workspace.companyCards.customStartDate'), + keyForList: CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.CUSTOM, + isSelected: dateOptionSelected === CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.CUSTOM, + }, + ]; + + return ( + + + + {translate('workspace.companyCards.startDateDescription')} + + handleSelectDateOption(value)} + data={dateOptions} + shouldSingleExecuteRowSelect + initiallyFocusedItemKey={dateOptionSelected} + shouldUpdateFocusedIndex + addBottomSafeAreaPadding + shouldHighlightSelectedItem={false} + footerContent={ +