@zeniai/client-epic-state 5.0.71-betaVR3 → 5.0.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/coreEpics.js +1 -1
- package/lib/entity/account/accountSelector.d.ts +1 -1
- package/lib/entity/account/accountState.d.ts +1 -1
- package/lib/entity/account/subAccountSelector.d.ts +1 -1
- package/lib/entity/aiAccountantCustomer/aiAccountantCustomerPayload.js +3 -1
- package/lib/entity/aiAccountantCustomer/aiAccountantCustomerReducer.js +2 -1
- package/lib/entity/aiAccountantCustomer/aiAccountantCustomerSelector.d.ts +1 -1
- package/lib/entity/chargeCard/chargeCardReducer.d.ts +2 -19
- package/lib/entity/chargeCard/chargeCardReducer.js +2 -35
- package/lib/entity/chargeCardTransaction/chargeCardTransactionReducer.d.ts +1 -14
- package/lib/entity/chargeCardTransaction/chargeCardTransactionReducer.js +2 -36
- package/lib/entity/sectionProjectView/sectionProjectViewReducer.d.ts +1 -1
- package/lib/entity/sectionProjectView/sectionProjectViewSelector.js +1 -1
- package/lib/entity/tenant/SessionManager.js +3 -6
- package/lib/entity/tenant/clearAllEpic.d.ts +2 -2
- package/lib/entity/tenant/clearAllEpic.js +2 -2
- package/lib/entity/tenant/epic/saveAPIKeyConnectionEpic.js +2 -6
- package/lib/entity/tenant/epic/saveOAuthConnectionEpic.js +2 -6
- package/lib/entity/tenant/tenantReducer.js +1 -6
- package/lib/entity/transaction/payloadTypes/transactionLinePayload.js +1 -3
- package/lib/epic.d.ts +10 -12
- package/lib/epic.js +11 -13
- package/lib/esm/coreEpics.js +1 -1
- package/lib/esm/entity/aiAccountantCustomer/aiAccountantCustomerPayload.js +3 -1
- package/lib/esm/entity/aiAccountantCustomer/aiAccountantCustomerReducer.js +2 -1
- package/lib/esm/entity/chargeCard/chargeCardReducer.js +1 -34
- package/lib/esm/entity/chargeCardTransaction/chargeCardTransactionReducer.js +1 -35
- package/lib/esm/entity/sectionProjectView/sectionProjectViewSelector.js +1 -1
- package/lib/esm/entity/tenant/SessionManager.js +1 -4
- package/lib/esm/entity/tenant/clearAllEpic.js +2 -2
- package/lib/esm/entity/tenant/epic/saveAPIKeyConnectionEpic.js +2 -6
- package/lib/esm/entity/tenant/epic/saveOAuthConnectionEpic.js +2 -6
- package/lib/esm/entity/tenant/tenantReducer.js +1 -6
- package/lib/esm/entity/transaction/payloadTypes/transactionLinePayload.js +1 -3
- package/lib/esm/epic.js +11 -13
- package/lib/esm/index.js +15 -19
- package/lib/esm/reducer.js +2 -2
- package/lib/esm/view/aiAccountantView/epics/fetchAiAccountantCustomersEpic.js +5 -2
- package/lib/esm/view/aiAccountantView/epics/fetchAiAccountantJobsEpic.js +5 -2
- package/lib/esm/view/aiAccountantView/epics/triggerAiAccountantJobEpic.js +1 -4
- package/lib/esm/view/companyTaskManagerView/companyTaskManagerViewSelector.js +1 -1
- package/lib/esm/view/companyTaskManagerView/epics/fetchTaskManagerMetricsEpic.js +1 -1
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +2 -2
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +1 -2
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +5 -21
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/restoreBulkUploadAutomatchingOnMountEpic.js +6 -8
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +1 -7
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +9 -13
- package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +1 -11
- package/lib/esm/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +2 -81
- package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +1 -127
- package/lib/esm/view/expenseAutomationView/selectors/missingReceiptsSelector.js +0 -2
- package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +1 -20
- package/lib/esm/view/expenseAutomationView/types/missingReceiptsViewState.js +2 -0
- package/lib/esm/view/financeStatement/financeStatementSelector.js +1 -1
- package/lib/esm/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.js +2 -3
- package/lib/esm/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.js +2 -3
- package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/chargeCardDetailReducer.js +18 -55
- package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailEpic.js +2 -16
- package/lib/esm/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailPageEpic.js +4 -5
- package/lib/esm/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.js +9 -26
- package/lib/esm/view/spendManagement/chargeCards/chargeCardList/fetchChargeCardListPageEpic.js +6 -9
- package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.js +2 -3
- package/lib/esm/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentPageEpic.js +1 -1
- package/lib/esm/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.js +3 -7
- package/lib/esm/view/taskManager/cannedResponsesView/epics/fetchCannedResponsesEpic.js +1 -1
- package/lib/esm/view/taskManager/cannedResponsesView/epics/saveCannedResponseEpic.js +1 -1
- package/lib/esm/view/taskManager/taskDetailView/epics/unsnoozeTaskEpic.js +1 -1
- package/lib/esm/view/transactionDetail/transactionDetailSelector.js +1 -7
- package/lib/index.d.ts +16 -20
- package/lib/index.js +58 -75
- package/lib/reducer.d.ts +2 -2
- package/lib/reducer.js +2 -2
- package/lib/tsconfig.typecheck.tsbuildinfo +1 -0
- package/lib/view/aiAccountantView/aiAccountantViewPayload.d.ts +2 -2
- package/lib/view/aiAccountantView/aiAccountantViewReducer.d.ts +1 -1
- package/lib/view/aiAccountantView/epics/fetchAiAccountantCustomersEpic.js +5 -2
- package/lib/view/aiAccountantView/epics/fetchAiAccountantJobsEpic.js +5 -2
- package/lib/view/aiAccountantView/epics/triggerAiAccountantJobEpic.js +1 -4
- package/lib/view/companyTaskManagerView/companyTaskManagerViewSelector.d.ts +1 -1
- package/lib/view/companyTaskManagerView/companyTaskManagerViewSelector.js +1 -1
- package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js +2 -2
- package/lib/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js +1 -2
- package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +5 -21
- package/lib/view/expenseAutomationView/epics/missingReceipts/restoreBulkUploadAutomatchingOnMountEpic.js +6 -8
- package/lib/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +1 -7
- package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.d.ts +5 -11
- package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +8 -12
- package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.d.ts +2 -2
- package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +0 -10
- package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
- package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +0 -21
- package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.d.ts +2 -15
- package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +3 -83
- package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +3 -9
- package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +2 -128
- package/lib/view/expenseAutomationView/selectorTypes/missingReceiptsSelectorTypes.d.ts +1 -14
- package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +0 -14
- package/lib/view/expenseAutomationView/selectors/missingReceiptsSelector.js +0 -2
- package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +1 -20
- package/lib/view/expenseAutomationView/types/missingReceiptsViewState.d.ts +2 -20
- package/lib/view/expenseAutomationView/types/missingReceiptsViewState.js +4 -1
- package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +2 -41
- package/lib/view/financeStatement/financeStatementSelector.js +1 -1
- package/lib/view/profitAndLossProjectView/profitAndLossProjectViewSelectorTypes.d.ts +1 -1
- package/lib/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.d.ts +1 -3
- package/lib/view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer.js +2 -3
- package/lib/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.d.ts +1 -3
- package/lib/view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer.js +2 -3
- package/lib/view/spendManagement/chargeCards/chargeCardDetail/chargeCardDetailReducer.js +18 -55
- package/lib/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailEpic.js +2 -16
- package/lib/view/spendManagement/chargeCards/chargeCardDetail/fetchChargeCardDetailPageEpic.js +4 -5
- package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.d.ts +3 -18
- package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardListReducer.js +10 -27
- package/lib/view/spendManagement/chargeCards/chargeCardList/fetchChargeCardListPageEpic.js +6 -9
- package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.d.ts +1 -3
- package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/chargeCardPaymentHistoryReducer.js +2 -3
- package/lib/view/spendManagement/chargeCards/chargeCardPaymentHistory/fetchChargeCardPaymentPageEpic.js +1 -1
- package/lib/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.d.ts +1 -3
- package/lib/view/spendManagement/chargeCards/chargeCardRepaymentDetail/chargeCardRepaymentDetailReducer.js +3 -7
- package/lib/view/transactionDetail/transactionDetailSelector.js +1 -7
- package/lib/view/zeniOAuthView/zeniOAuthSelector.d.ts +1 -1
- package/package.json +4 -9
- package/lib/commonStateTypes/cacheOverrideFetchReducer.d.ts +0 -17
- package/lib/commonStateTypes/cacheOverrideFetchReducer.js +0 -20
- package/lib/commonStateTypes/pusherActions.d.ts +0 -11
- package/lib/commonStateTypes/pusherActions.js +0 -6
- package/lib/esm/commonStateTypes/cacheOverrideFetchReducer.js +0 -16
- package/lib/esm/commonStateTypes/pusherActions.js +0 -3
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/refreshBatchDetailsForBatchIdEpic.js +0 -52
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/syncTabsAfterAutomatchEpic.js +0 -53
- package/lib/esm/view/expenseAutomationView/types/completedSubTab.js +0 -17
- package/lib/view/expenseAutomationView/epics/missingReceipts/refreshBatchDetailsForBatchIdEpic.d.ts +0 -18
- package/lib/view/expenseAutomationView/epics/missingReceipts/refreshBatchDetailsForBatchIdEpic.js +0 -56
- package/lib/view/expenseAutomationView/epics/missingReceipts/syncTabsAfterAutomatchEpic.d.ts +0 -28
- package/lib/view/expenseAutomationView/epics/missingReceipts/syncTabsAfterAutomatchEpic.js +0 -57
- package/lib/view/expenseAutomationView/types/completedSubTab.d.ts +0 -10
- package/lib/view/expenseAutomationView/types/completedSubTab.js +0 -21
|
@@ -4,7 +4,7 @@ import { getTagsByIds } from '../../entity/tag/tagSelector';
|
|
|
4
4
|
import { getTaskById } from '../../entity/task/taskSelector';
|
|
5
5
|
import { getUserByUserId, getUsersByUserIds, } from '../../entity/user/userSelector';
|
|
6
6
|
export const getCompanyTaskManagerView = createSelector((state) => state.companyTaskManagerViewState, (state) => state.companyState, (state) => state.userState, (state) => state.taskState, (state) => state.tagState, (companyTaskManagerViewState, companyState, userState, taskState, tagState) => {
|
|
7
|
-
const { taskIds, uiState, fetchState, error, totalTaskCount, metrics, metricsFetchState
|
|
7
|
+
const { taskIds, uiState, fetchState, error, totalTaskCount, metrics, metricsFetchState } = companyTaskManagerViewState;
|
|
8
8
|
const tasksList = [];
|
|
9
9
|
taskIds.forEach((taskId) => {
|
|
10
10
|
const task = getTaskById(taskState, taskId);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { of } from 'rxjs';
|
|
2
2
|
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
3
|
-
import { isSuccessResponse } from '../../../responsePayload';
|
|
3
|
+
import { isSuccessResponse, } from '../../../responsePayload';
|
|
4
4
|
import { fetchTaskManagerMetrics, updateTaskManagerMetrics, updateTaskManagerMetricsOnFailure, } from '../companyTaskManagerViewReducer';
|
|
5
5
|
export const fetchTaskManagerMetricsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchTaskManagerMetrics.match), switchMap(() => {
|
|
6
6
|
const query = JSON.stringify({ is_filter_by_logged_in_user: true });
|
package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchesEpic.js
CHANGED
|
@@ -3,10 +3,10 @@ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
|
3
3
|
import { toMonthYearPeriodId } from '../../../../commonStateTypes/timePeriod';
|
|
4
4
|
import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
|
|
5
5
|
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
|
|
6
|
-
import { BULK_UPLOAD_BATCH_LIST_DATE_RANGE_DAYS } from '../../helpers/bulkUploadTiming';
|
|
7
|
-
import { rollingCalendarDateRangeInclusive } from '../../helpers/rollingCalendarDateRangeInclusive';
|
|
8
6
|
import { toBatchListItem, } from '../../payload/missingReceiptsPayload';
|
|
9
7
|
import { fetchBulkUploadBatches, fetchBulkUploadBatchesFailure, fetchBulkUploadBatchesSuccess, } from '../../reducers/missingReceiptsViewReducer';
|
|
8
|
+
import { BULK_UPLOAD_BATCH_LIST_DATE_RANGE_DAYS } from '../../helpers/bulkUploadTiming';
|
|
9
|
+
import { rollingCalendarDateRangeInclusive } from '../../helpers/rollingCalendarDateRangeInclusive';
|
|
10
10
|
export const fetchBulkUploadBatchesEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchBulkUploadBatches.match), switchMap((action) => {
|
|
11
11
|
const { cacheOverride } = action.payload;
|
|
12
12
|
const state = state$.value;
|
package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchCompletedTransactionsEpic.js
CHANGED
|
@@ -5,7 +5,6 @@ import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
|
|
|
5
5
|
import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
|
|
6
6
|
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
|
|
7
7
|
import { fetchCompletedTransactions, fetchCompletedTransactionsFailure, fetchCompletedTransactionsSuccess, getCompletedTransactionsCacheKey, } from '../../reducers/missingReceiptsViewReducer';
|
|
8
|
-
import { DEFAULT_COMPLETED_SUB_TAB } from '../../types/completedSubTab';
|
|
9
8
|
export const fetchCompletedTransactionsEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchCompletedTransactions.match), switchMap((action) => {
|
|
10
9
|
const state = state$.value;
|
|
11
10
|
const { expenseAutomationViewState: { selectedPeriodByTenantId }, expenseAutomationMissingReceiptsViewState: { bulkUpload }, } = state;
|
|
@@ -40,7 +39,7 @@ export const fetchCompletedTransactionsEpic = (actions$, state$, zeniAPI) => act
|
|
|
40
39
|
if (sortOrder != null) {
|
|
41
40
|
queryPayload.sort_order = sortOrder === 'ascending' ? 'asc' : 'desc';
|
|
42
41
|
}
|
|
43
|
-
if (completedSubTab !==
|
|
42
|
+
if (completedSubTab !== 'all') {
|
|
44
43
|
queryPayload.match_type = completedSubTab;
|
|
45
44
|
}
|
|
46
45
|
const encodedQuery = encodeURIComponent(JSON.stringify(queryPayload));
|
package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js
CHANGED
|
@@ -31,19 +31,10 @@ export function fetchBatchDetailsByIds(batchIds, zeniAPI) {
|
|
|
31
31
|
}
|
|
32
32
|
export const fetchMultipleBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchBulkUploadBatchesSuccess.match), mergeMap((action) => {
|
|
33
33
|
const batchList = action.payload.batchList;
|
|
34
|
-
const
|
|
35
|
-
const { batchDetailsById, batchDetailsRevalidating } = bulkUpload;
|
|
34
|
+
const { batchDetailsById } = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload;
|
|
36
35
|
/** Only completed list rows may trigger batch-details API; preserve platform order. */
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
* Stale-while-revalidate: when a manual refresh has set `batchDetailsRevalidating`, refresh
|
|
40
|
-
* every completed list row (replacing entries via `storeBatchDetails` as they arrive). On a
|
|
41
|
-
* normal first fetch we still only request rows whose details are missing.
|
|
42
|
-
*/
|
|
43
|
-
const candidateRows = batchDetailsRevalidating === true
|
|
44
|
-
? completedRows
|
|
45
|
-
: completedRows.filter((batch) => batchDetailsById[batch.batchId] == null);
|
|
46
|
-
const completedBatchIds = candidateRows.map((batch) => batch.batchId);
|
|
36
|
+
const completedBatchesMissingDetails = filterBatchListItemsForBatchDetailsFetch(batchList).filter((batch) => batchDetailsById[batch.batchId] == null);
|
|
37
|
+
const completedBatchIds = completedBatchesMissingDetails.map((batch) => batch.batchId);
|
|
47
38
|
const batchIdsToFetch = completedBatchIds.slice(0, INITIAL_BATCH_PAGE_SIZE);
|
|
48
39
|
if (batchIdsToFetch.length === 0) {
|
|
49
40
|
/**
|
|
@@ -51,6 +42,7 @@ export const fetchMultipleBatchDetailsEpic = (actions$, state$, zeniAPI) => acti
|
|
|
51
42
|
* `currentBatchId` may not appear in the filtered list but still needs `GET /batches/:id`.
|
|
52
43
|
* Fall back to the single-batch-details epic so `phase` and details can resolve.
|
|
53
44
|
*/
|
|
45
|
+
const bulkUpload = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload;
|
|
54
46
|
const currentId = bulkUpload.currentBatchId;
|
|
55
47
|
if (currentId != null &&
|
|
56
48
|
bulkUpload.phase === 'matching' &&
|
|
@@ -58,15 +50,7 @@ export const fetchMultipleBatchDetailsEpic = (actions$, state$, zeniAPI) => acti
|
|
|
58
50
|
shouldFetchBatchDetailsForBatchId(bulkUpload.batchStatusById, bulkUpload.batchListByPeriod, currentId)) {
|
|
59
51
|
return of(fetchBulkUploadBatchDetails());
|
|
60
52
|
}
|
|
61
|
-
|
|
62
|
-
* Close the pagination loop so the Unmatched-tab loading heuristic can settle.
|
|
63
|
-
* `fetchMoreBatchDetailsComplete` also clears `batchDetailsRevalidating` and flips
|
|
64
|
-
* `initialBatchDetailsLoaded` to true. Without this, `batchDetailsPaginationState` stays
|
|
65
|
-
* at `Not-Started` forever after a list refresh that finds zero completed rows (or all
|
|
66
|
-
* already cached) — the direct-open-Unmatched-without-upload path used to render an
|
|
67
|
-
* infinite skeleton.
|
|
68
|
-
*/
|
|
69
|
-
return of(fetchMoreBatchDetailsComplete());
|
|
53
|
+
return EMPTY;
|
|
70
54
|
}
|
|
71
55
|
return concat(of(setInitialBatchDetailsLoading()), fetchBatchDetailsByIds(batchIdsToFetch, zeniAPI), of(fetchMoreBatchDetailsComplete()));
|
|
72
56
|
}));
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { EMPTY, concat, from, of } from 'rxjs';
|
|
2
2
|
import { catchError, concatMap, filter, mergeMap, take } from 'rxjs/operators';
|
|
3
|
-
import { toMonthYearPeriodId } from '../../../../commonStateTypes/timePeriod';
|
|
4
|
-
import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
|
|
5
3
|
import { isSuccessResponse } from '../../../../responsePayload';
|
|
6
|
-
import { BULK_UPLOAD_BATCH_LIST_DATE_RANGE_DAYS } from '../../helpers/bulkUploadTiming';
|
|
7
|
-
import { rollingCalendarDateRangeInclusive } from '../../helpers/rollingCalendarDateRangeInclusive';
|
|
8
4
|
import { isBatchDetailsStatusPendingOrProcessing, toBatchListItem, } from '../../payload/missingReceiptsPayload';
|
|
9
5
|
import { fetchBulkUploadBatchesSuccess, restoreBulkUploadAutomatchingOnMount, restoreBulkUploadMatchingState, } from '../../reducers/missingReceiptsViewReducer';
|
|
6
|
+
import { BULK_UPLOAD_BATCH_LIST_DATE_RANGE_DAYS } from '../../helpers/bulkUploadTiming';
|
|
7
|
+
import { rollingCalendarDateRangeInclusive } from '../../helpers/rollingCalendarDateRangeInclusive';
|
|
8
|
+
import { toMonthYearPeriodId } from '../../../../commonStateTypes/timePeriod';
|
|
9
|
+
import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
|
|
10
10
|
/**
|
|
11
11
|
* On missing-receipts entry / period switch, hydrate the automatching banner for the logged-in
|
|
12
12
|
* user without relying on UI-driven list fetches (the Missing tab does not fetch the batch list
|
|
@@ -49,12 +49,10 @@ export const restoreBulkUploadAutomatchingOnMountEpic = (actions$, state$, zeniA
|
|
|
49
49
|
bulkUpload.batchListFetchState.fetchState === 'Completed';
|
|
50
50
|
const listSource = alreadyCached
|
|
51
51
|
? of(null)
|
|
52
|
-
: zeniAPI
|
|
53
|
-
.getJSON(listUrl)
|
|
54
|
-
.pipe(catchError(() => of(null)));
|
|
52
|
+
: zeniAPI.getJSON(listUrl).pipe(catchError(() => of(null)));
|
|
55
53
|
return listSource.pipe(mergeMap((listResponse) => {
|
|
56
54
|
const batchList = alreadyCached
|
|
57
|
-
?
|
|
55
|
+
? bulkUpload.batchListByPeriod[periodId] ?? []
|
|
58
56
|
: listResponse != null &&
|
|
59
57
|
isSuccessResponse(listResponse) &&
|
|
60
58
|
listResponse.data != null
|
|
@@ -2,11 +2,10 @@ import { EMPTY, from, merge, of } from 'rxjs';
|
|
|
2
2
|
import { catchError, debounceTime, filter, mergeMap, switchMap, } from 'rxjs/operators';
|
|
3
3
|
import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
|
|
4
4
|
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
|
|
5
|
-
import { rollingCalendarDateRangeInclusive } from '../../helpers/rollingCalendarDateRangeInclusive';
|
|
6
5
|
import { supportedTransactionPayloadToManualSearchResult } from '../../payload/missingReceiptsPayload';
|
|
7
6
|
import { searchTransactionsForManualMatch, searchTransactionsForManualMatchFailure, searchTransactionsForManualMatchSuccess, } from '../../reducers/missingReceiptsViewReducer';
|
|
8
|
-
import { DEFAULT_COMPLETED_SUB_TAB } from '../../types/completedSubTab';
|
|
9
7
|
import { toTransactionsSortKey } from '../../types/transactionsViewState';
|
|
8
|
+
import { rollingCalendarDateRangeInclusive } from '../../helpers/rollingCalendarDateRangeInclusive';
|
|
10
9
|
/**
|
|
11
10
|
* `auto_categorized` for manual transaction search (Unmatched).
|
|
12
11
|
* Backend contract TBD — align with `fetchTransactionCategorizationEpic` (`selectedTab === 'autoCategorized'`)
|
|
@@ -47,11 +46,6 @@ export const searchTransactionsForManualMatchEpic = (actions$, state$, zeniAPI)
|
|
|
47
46
|
page_token: pageToken ?? null,
|
|
48
47
|
page_size: manualSearch.pageSize,
|
|
49
48
|
search_text: query,
|
|
50
|
-
// Manual search ignores the Completed sub-tab axis — always span the
|
|
51
|
-
// default sentinel so the receipt-matching UX returns results
|
|
52
|
-
// regardless of where the transaction would otherwise be bucketed on
|
|
53
|
-
// the Completed tab.
|
|
54
|
-
sub_tab: DEFAULT_COMPLETED_SUB_TAB,
|
|
55
49
|
};
|
|
56
50
|
return zeniAPI
|
|
57
51
|
.getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expense-automation/transactions?query=${encodeURIComponent(JSON.stringify(queryParam))}`)
|
package/lib/esm/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js
CHANGED
|
@@ -5,27 +5,23 @@ import { updateTransactions } from '../../../../entity/transaction/transactionRe
|
|
|
5
5
|
import { isSuccessResponse } from '../../../../responsePayload';
|
|
6
6
|
import { BULK_UPLOAD_AUTOMATCHING_TIMEOUT_MS } from '../../helpers/bulkUploadTiming';
|
|
7
7
|
import { extractTransactionPayloadsFromBatchFiles, isBatchDetailsApiStatusCompleted, toBatchDetails, } from '../../payload/missingReceiptsPayload';
|
|
8
|
-
import { bulkUploadAutomatchingTimedOut, bulkUploadReceiptsSuccess, clearBulkUpload, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatches, pusherBatchStatusUpdate,
|
|
8
|
+
import { bulkUploadAutomatchingTimedOut, bulkUploadReceiptsSuccess, clearBulkUpload, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatches, pusherBatchStatusUpdate, restoreBulkUploadMatchingState, } from '../../reducers/missingReceiptsViewReducer';
|
|
9
9
|
/** First poll after upload; then interval while batch not resolved via Pusher or API. */
|
|
10
10
|
const FALLBACK_FIRST_DELAY_MS = 30000;
|
|
11
11
|
/** Shorter first poll for restored sessions — batch may already be near completion. */
|
|
12
12
|
const RESTORE_FIRST_DELAY_MS = 5000;
|
|
13
13
|
const FALLBACK_POLL_INTERVAL_MS = 10000;
|
|
14
14
|
/**
|
|
15
|
-
* On Pusher batch completion: refresh
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* on screen and the just-completed row replaces in place via `storeBatchDetails`. The post-match
|
|
19
|
-
* tab decision (Unmatched vs Completed) is made client-side in `ExpenseAutomationPage` once fresh
|
|
15
|
+
* On Pusher batch completion: refresh batch list (then fetchMultipleBatchDetailsEpic runs).
|
|
16
|
+
* Debounced to reduce duplicate refreshes. The post-match tab decision
|
|
17
|
+
* (Unmatched vs Completed) is made client-side in `ExpenseAutomationPage` once fresh
|
|
20
18
|
* batch details land -- do not dispatch `requestMissingReceiptsTabNavigation` here.
|
|
21
|
-
*
|
|
22
|
-
* Pusher resend storms (same batch completing twice within the dedupe window) are throttled in
|
|
23
|
-
* `refreshBatchDetailsForBatchIdEpic` via `lastBatchDetailRefreshAtById`, so this epic stays
|
|
24
|
-
* stateless and just forwards the event.
|
|
25
19
|
*/
|
|
26
|
-
export const pusherBatchStatusCompletionEpic = (actions$) => actions$.pipe(filter(pusherBatchStatusUpdate.match), filter((action) => action.payload.status === 'completed'), debounceTime(300), mergeMap((
|
|
27
|
-
fetchBulkUploadBatches({
|
|
28
|
-
|
|
20
|
+
export const pusherBatchStatusCompletionEpic = (actions$) => actions$.pipe(filter(pusherBatchStatusUpdate.match), filter((action) => action.payload.status === 'completed'), debounceTime(300), mergeMap(() => from([
|
|
21
|
+
fetchBulkUploadBatches({
|
|
22
|
+
cacheOverride: true,
|
|
23
|
+
invalidateBatchDetailsCache: true,
|
|
24
|
+
}),
|
|
29
25
|
])));
|
|
30
26
|
/**
|
|
31
27
|
* If Pusher and batch-details are delayed beyond {@link BULK_UPLOAD_AUTOMATCHING_TIMEOUT_MS},
|
|
@@ -5,8 +5,7 @@ import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
|
|
|
5
5
|
import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
|
|
6
6
|
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
|
|
7
7
|
import { getLineItemsByTransactionsIdsFromResponse } from '../../helpers/transactionCategorizationLocalDataHelper';
|
|
8
|
-
import { fetchTransactionCategorization, fetchTransactionCategorizationFailure, fetchTransactionCategorizationSuccess, initializeTransactionCategorizationViewLocalData, resetOtherTabsFetchState,
|
|
9
|
-
import { DEFAULT_COMPLETED_SUB_TAB } from '../../types/completedSubTab';
|
|
8
|
+
import { fetchTransactionCategorization, fetchTransactionCategorizationFailure, fetchTransactionCategorizationSuccess, initializeTransactionCategorizationViewLocalData, resetOtherTabsFetchState, updateTotalCountForTransactionCategorization, updateTransactionCategorizationUIState, } from '../../reducers/transactionsViewReducer';
|
|
10
9
|
import { TRANSACTIONS_TABS, toTransactionsSortKey, } from '../../types/transactionsViewState';
|
|
11
10
|
export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchTransactionCategorization.match), switchMap((action) => {
|
|
12
11
|
const { expenseAutomationViewState: { selectedPeriodByTenantId }, expenseAutomationTransactionsViewState, } = state$.value;
|
|
@@ -24,9 +23,6 @@ export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) =>
|
|
|
24
23
|
page_token: pageToken,
|
|
25
24
|
page_size: 25,
|
|
26
25
|
search_text: uiState.searchString,
|
|
27
|
-
sub_tab: selectedTab === 'autoCategorized'
|
|
28
|
-
? expenseAutomationTransactionsViewState.selectedTransactionCategorizationCompletedSubTab
|
|
29
|
-
: DEFAULT_COMPLETED_SUB_TAB,
|
|
30
26
|
};
|
|
31
27
|
return zeniAPI
|
|
32
28
|
.getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expense-automation/transactions?query=${encodeURIComponent(JSON.stringify(queryParam))}`)
|
|
@@ -49,12 +45,6 @@ export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) =>
|
|
|
49
45
|
selectedTab,
|
|
50
46
|
totalCount: response.data.total_count,
|
|
51
47
|
}));
|
|
52
|
-
updateActions.push(updateParentTotalCountForTab({
|
|
53
|
-
selectedPeriod,
|
|
54
|
-
selectedTab,
|
|
55
|
-
parentTotalCount: response.data.parent_tab_total_count ??
|
|
56
|
-
response.data.total_count,
|
|
57
|
-
}));
|
|
58
48
|
updateActions.push(resetOtherTabsFetchState({
|
|
59
49
|
refreshViewInBackground,
|
|
60
50
|
tabs: TRANSACTIONS_TABS.filter((tab) => selectedTab !== tab),
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createAction, createSlice } from '@reduxjs/toolkit';
|
|
2
2
|
import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
|
|
3
|
-
import { DEFAULT_COMPLETED_SUB_TAB, } from '../types/completedSubTab';
|
|
4
3
|
import { MIN_MANUAL_TRANSACTION_SEARCH_LENGTH, } from '../types/missingReceiptsViewState';
|
|
5
4
|
export const getCompletedTransactionsCacheKey = (periodId, sortKey, sortOrder, subTab) => {
|
|
6
5
|
const sortKeyPart = sortKey ?? 'none';
|
|
@@ -10,18 +9,15 @@ export const getCompletedTransactionsCacheKey = (periodId, sortKey, sortOrder, s
|
|
|
10
9
|
export const initialBulkUploadState = {
|
|
11
10
|
batchDetailsById: {},
|
|
12
11
|
batchDetailsPaginationState: { fetchState: 'Not-Started', error: undefined },
|
|
13
|
-
batchDetailsRevalidating: false,
|
|
14
12
|
batchListByPeriod: {},
|
|
15
13
|
batchListFetchState: { fetchState: 'Not-Started', error: undefined },
|
|
16
14
|
batchStatusById: {},
|
|
17
|
-
completedSubTab:
|
|
15
|
+
completedSubTab: 'all',
|
|
18
16
|
completedTransactionsByPeriod: {},
|
|
19
17
|
confirmMatchStatus: { fetchState: 'Not-Started', error: undefined },
|
|
20
18
|
currentBatchId: undefined,
|
|
21
19
|
currentResultsTab: 'unmatched',
|
|
22
20
|
failedBatchDetailIds: [],
|
|
23
|
-
initialBatchDetailsLoaded: false,
|
|
24
|
-
lastBatchDetailRefreshAtById: {},
|
|
25
21
|
manualSearchUiResetKey: 0,
|
|
26
22
|
manualSearch: {
|
|
27
23
|
fetchState: { fetchState: 'Not-Started', error: undefined },
|
|
@@ -68,13 +64,6 @@ export const fetchBulkUploadBatchDetails = createAction('expenseAutomationMissin
|
|
|
68
64
|
* after a hard refresh. The epic owns the list + details API calls end-to-end.
|
|
69
65
|
*/
|
|
70
66
|
export const restoreBulkUploadAutomatchingOnMount = createAction('expenseAutomationMissingReceiptsView/restoreBulkUploadAutomatchingOnMount');
|
|
71
|
-
/**
|
|
72
|
-
* Epic trigger only. Targeted stale-while-revalidate refresh for a single batch's details — used
|
|
73
|
-
* by `pusherBatchStatusCompletionEpic` so a Pusher 'completed' event re-fetches only that one
|
|
74
|
-
* batch instead of wiping the entire `batchDetailsById` cache. The epic dedupes via
|
|
75
|
-
* `lastBatchDetailRefreshAtById` so resend storms don't trigger waves of API calls.
|
|
76
|
-
*/
|
|
77
|
-
export const refreshBatchDetailsForBatchId = createAction('expenseAutomationMissingReceiptsView/refreshBatchDetailsForBatchId');
|
|
78
67
|
const expenseAutomationMissingReceiptsView = createSlice({
|
|
79
68
|
name: 'expenseAutomationMissingReceiptsView',
|
|
80
69
|
initialState,
|
|
@@ -261,16 +250,6 @@ const expenseAutomationMissingReceiptsView = createSlice({
|
|
|
261
250
|
const details = action.payload;
|
|
262
251
|
draft.bulkUpload.batchDetailsById[details.batchId] = details;
|
|
263
252
|
draft.bulkUpload.phase = 'completed';
|
|
264
|
-
draft.bulkUpload.initialBatchDetailsLoaded = true;
|
|
265
|
-
/**
|
|
266
|
-
* A successful detail load supersedes any previous failure for this batch — without this,
|
|
267
|
-
* `getPrimaryBatchIdFromBatchListOrder` (and pagination) would keep skipping the batch
|
|
268
|
-
* across the recovery, hiding now-valid Unmatched-tab work.
|
|
269
|
-
*/
|
|
270
|
-
const failedIndex = draft.bulkUpload.failedBatchDetailIds.indexOf(details.batchId);
|
|
271
|
-
if (failedIndex >= 0) {
|
|
272
|
-
draft.bulkUpload.failedBatchDetailIds.splice(failedIndex, 1);
|
|
273
|
-
}
|
|
274
253
|
},
|
|
275
254
|
fetchBulkUploadBatchDetailsFailure: {
|
|
276
255
|
reducer(draft) {
|
|
@@ -292,19 +271,6 @@ const expenseAutomationMissingReceiptsView = createSlice({
|
|
|
292
271
|
draft.bulkUpload.currentBatchId === details.batchId) {
|
|
293
272
|
draft.bulkUpload.phase = 'completed';
|
|
294
273
|
}
|
|
295
|
-
/** Stale-while-revalidate: any successful detail load means we have rendered data. */
|
|
296
|
-
draft.bulkUpload.initialBatchDetailsLoaded = true;
|
|
297
|
-
/**
|
|
298
|
-
* Stale-while-revalidate recovery: if this batch had previously failed, a successful
|
|
299
|
-
* targeted refresh (Pusher `completed` → `refreshBatchDetailsForBatchIdEpic`) or
|
|
300
|
-
* multi-batch revalidation (manual refresh) supersedes the failure. Selectors that gate
|
|
301
|
-
* on `failedBatchDetailIds` (primary-batch resolution, `hasMoreBatchDetails`) must see the
|
|
302
|
-
* batch as recovered so it can show fresh Unmatched work.
|
|
303
|
-
*/
|
|
304
|
-
const failedIndex = draft.bulkUpload.failedBatchDetailIds.indexOf(details.batchId);
|
|
305
|
-
if (failedIndex >= 0) {
|
|
306
|
-
draft.bulkUpload.failedBatchDetailIds.splice(failedIndex, 1);
|
|
307
|
-
}
|
|
308
274
|
},
|
|
309
275
|
batchDetailFetchFailed(draft, action) {
|
|
310
276
|
const { batchId } = action.payload;
|
|
@@ -333,14 +299,6 @@ const expenseAutomationMissingReceiptsView = createSlice({
|
|
|
333
299
|
fetchState: 'Completed',
|
|
334
300
|
error: undefined,
|
|
335
301
|
};
|
|
336
|
-
/**
|
|
337
|
-
* Stale-while-revalidate: a completed pagination round means the initial page (or a
|
|
338
|
-
* subsequent revalidation) has fully resolved. Once `initialBatchDetailsLoaded` flips to
|
|
339
|
-
* `true` it stays `true` for the session — only `clearBulkUpload` /
|
|
340
|
-
* `clearBulkUploadBatchDetailsForScopeChange` can reset it.
|
|
341
|
-
*/
|
|
342
|
-
draft.bulkUpload.initialBatchDetailsLoaded = true;
|
|
343
|
-
draft.bulkUpload.batchDetailsRevalidating = false;
|
|
344
302
|
const currentId = draft.bulkUpload.currentBatchId;
|
|
345
303
|
const currentDetails = currentId != null
|
|
346
304
|
? draft.bulkUpload.batchDetailsById[currentId]
|
|
@@ -356,24 +314,12 @@ const expenseAutomationMissingReceiptsView = createSlice({
|
|
|
356
314
|
fetchState: 'Error',
|
|
357
315
|
error: action.payload.error,
|
|
358
316
|
};
|
|
359
|
-
draft.bulkUpload.batchDetailsRevalidating = false;
|
|
360
317
|
},
|
|
361
318
|
fetchBulkUploadBatches: {
|
|
362
319
|
reducer(draft, action) {
|
|
363
|
-
/**
|
|
364
|
-
* Manual refresh button (and any other caller passing `invalidateBatchDetailsCache: true`):
|
|
365
|
-
* wipe the details cache, failed-id list, and pagination state so the Unmatched tab drops
|
|
366
|
-
* its existing rows and re-renders the initial-load skeleton while the next list +
|
|
367
|
-
* details fetches resolve. The post-success epic (`fetchMultipleBatchDetailsEpic`) then
|
|
368
|
-
* re-runs the details chain from a clean slate.
|
|
369
|
-
*/
|
|
370
320
|
if (action.payload.invalidateBatchDetailsCache === true) {
|
|
371
321
|
draft.bulkUpload.batchDetailsById = {};
|
|
372
322
|
draft.bulkUpload.failedBatchDetailIds = [];
|
|
373
|
-
draft.bulkUpload.batchDetailsPaginationState = {
|
|
374
|
-
fetchState: 'Not-Started',
|
|
375
|
-
error: undefined,
|
|
376
|
-
};
|
|
377
323
|
}
|
|
378
324
|
draft.bulkUpload.batchListFetchState = {
|
|
379
325
|
fetchState: 'In-Progress',
|
|
@@ -390,31 +336,6 @@ const expenseAutomationMissingReceiptsView = createSlice({
|
|
|
390
336
|
};
|
|
391
337
|
},
|
|
392
338
|
},
|
|
393
|
-
/**
|
|
394
|
-
* Tenant/period scope change — the new scope's data is genuinely unknown so the existing
|
|
395
|
-
* details cache and `initialBatchDetailsLoaded` flag must be reset (the Unmatched skeleton
|
|
396
|
-
* is acceptable on scope change). Distinct from a stale-while-revalidate refresh.
|
|
397
|
-
*/
|
|
398
|
-
clearBulkUploadBatchDetailsForScopeChange(draft) {
|
|
399
|
-
draft.bulkUpload.batchDetailsById = {};
|
|
400
|
-
draft.bulkUpload.failedBatchDetailIds = [];
|
|
401
|
-
draft.bulkUpload.batchDetailsPaginationState = {
|
|
402
|
-
fetchState: 'Not-Started',
|
|
403
|
-
error: undefined,
|
|
404
|
-
};
|
|
405
|
-
draft.bulkUpload.batchDetailsRevalidating = false;
|
|
406
|
-
draft.bulkUpload.initialBatchDetailsLoaded = false;
|
|
407
|
-
draft.bulkUpload.lastBatchDetailRefreshAtById = {};
|
|
408
|
-
},
|
|
409
|
-
/**
|
|
410
|
-
* Records the last time we issued a targeted refresh for `batchId`. Set by
|
|
411
|
-
* `refreshBatchDetailsForBatchIdEpic` before each API call so subsequent Pusher resends in the
|
|
412
|
-
* dedupe window are skipped.
|
|
413
|
-
*/
|
|
414
|
-
markBatchDetailRefreshAttempted(draft, action) {
|
|
415
|
-
draft.bulkUpload.lastBatchDetailRefreshAtById[action.payload.batchId] =
|
|
416
|
-
action.payload.timestamp;
|
|
417
|
-
},
|
|
418
339
|
fetchBulkUploadBatchesSuccess(draft, action) {
|
|
419
340
|
const periodId = toMonthYearPeriodId(action.payload.selectedPeriod);
|
|
420
341
|
draft.bulkUpload.batchListByPeriod[periodId] = action.payload.batchList;
|
|
@@ -657,7 +578,7 @@ const expenseAutomationMissingReceiptsView = createSlice({
|
|
|
657
578
|
});
|
|
658
579
|
export const { fetchMissingReceipts, fetchMissingReceiptsSuccess, fetchMissingReceiptsFailure, uploadMissingReceiptSuccess, updateMissingReceiptUploadState, updateMissingReceiptsUIState, markMissingReceiptAsDone, clearExpenseAutomationMissingReceiptsView,
|
|
659
580
|
// Bulk Upload
|
|
660
|
-
bulkUploadReceipts, updateBulkUploadProgress, setBulkUploadUploadedFileCount, bulkUploadReceiptsSuccess, bulkUploadReceiptsFailure, restoreBulkUploadMatchingState, bulkUploadAutomatchingTimedOut, pusherBatchStatusUpdate, requestMissingReceiptsTabNavigation, clearMissingReceiptsTabNavigation, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatchDetailsFailure, storeBatchDetails, batchDetailFetchFailed, setInitialBatchDetailsLoading, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, fetchBulkUploadBatches, fetchBulkUploadBatchesSuccess, fetchBulkUploadBatchesFailure,
|
|
581
|
+
bulkUploadReceipts, updateBulkUploadProgress, setBulkUploadUploadedFileCount, bulkUploadReceiptsSuccess, bulkUploadReceiptsFailure, restoreBulkUploadMatchingState, bulkUploadAutomatchingTimedOut, pusherBatchStatusUpdate, requestMissingReceiptsTabNavigation, clearMissingReceiptsTabNavigation, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatchDetailsFailure, storeBatchDetails, batchDetailFetchFailed, setInitialBatchDetailsLoading, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, fetchBulkUploadBatches, fetchBulkUploadBatchesSuccess, fetchBulkUploadBatchesFailure, confirmBulkUploadMatch, confirmBulkUploadMatchSuccess, confirmBulkUploadMatchFailure, setBulkUploadResultsTab, setBulkUploadCompletedSubTab, setBulkUploadSortConfig, clearBulkUpload, searchTransactionsForManualMatch, searchTransactionsForManualMatchSuccess, searchTransactionsForManualMatchFailure, clearManualSearchResults, acknowledgeBulkUploadConfirmMatchComplete,
|
|
661
582
|
// Completed Transactions
|
|
662
583
|
fetchCompletedTransactions, fetchCompletedTransactionsSuccess, fetchCompletedTransactionsFailure, } = expenseAutomationMissingReceiptsView.actions;
|
|
663
584
|
export default expenseAutomationMissingReceiptsView.reducer;
|
|
@@ -4,7 +4,6 @@ import uniq from 'lodash/uniq';
|
|
|
4
4
|
import { toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
|
|
5
5
|
import { isVendorTransaction } from '../../../entity/transaction/stateTypes/vendorTransaction';
|
|
6
6
|
import { MAX_SELECTION_LIMIT, checkIfAllLineItemsAreCategoryClassFilled, filterAutoTabLineItems, mergeTabSpecificLineItems, removeTransactionFromTabView, setEntityRecommendationForLineIdInLocalData, toSetAllLineItemsToCategoryClass, toTransactionDetailLocalData, } from '../helpers/transactionCategorizationLocalDataHelper';
|
|
7
|
-
import { DEFAULT_COMPLETED_SUB_TAB, } from '../types/completedSubTab';
|
|
8
7
|
export const initialTransactionTabViewState = {
|
|
9
8
|
saveStatus: {
|
|
10
9
|
fetchState: 'Not-Started',
|
|
@@ -42,74 +41,12 @@ export const initialTransactionTabViewState = {
|
|
|
42
41
|
selectedTransactionLineId: undefined,
|
|
43
42
|
};
|
|
44
43
|
export const initialState = {
|
|
45
|
-
autoCategorizedSubTabCache: {},
|
|
46
|
-
parentTotalCountByTab: {
|
|
47
|
-
review: {},
|
|
48
|
-
autoCategorized: {},
|
|
49
|
-
},
|
|
50
44
|
selectedTransactionCategorizationTab: 'review',
|
|
51
|
-
selectedTransactionCategorizationCompletedSubTab: DEFAULT_COMPLETED_SUB_TAB,
|
|
52
45
|
transactionCategorizationView: {
|
|
53
46
|
review: { ...initialTransactionTabViewState },
|
|
54
47
|
autoCategorized: { ...initialTransactionTabViewState },
|
|
55
48
|
},
|
|
56
49
|
};
|
|
57
|
-
/**
|
|
58
|
-
* Capture the autoCategorized tab's current paginated dataset for a sub-tab.
|
|
59
|
-
* The mid-flight `'In-Progress'` state is normalised to `'Completed'` because
|
|
60
|
-
* the in-flight request is cancelled by `switchMap` in the fetch epic when the
|
|
61
|
-
* sub-tab changes — restoring `'In-Progress'` later would leave the UI stuck
|
|
62
|
-
* showing a phantom loading state with no fetch actually running. Length-zero
|
|
63
|
-
* datasets degrade to `'Not-Started'` so the next visit triggers a fresh
|
|
64
|
-
* fetch from page 1 instead of short-circuiting on stale empty caches.
|
|
65
|
-
*
|
|
66
|
-
* Snapshot scope (deliberately narrow):
|
|
67
|
-
* - Captured: `pageToken`, `totalCount`, `transactionIdsBySelectedPeriod`,
|
|
68
|
-
* `fetchState`, `error`, `scrollPosition`. The first five are the keys the
|
|
69
|
-
* fetch epic's cache check reads to decide whether to short-circuit a
|
|
70
|
-
* sub-tab revisit; `scrollPosition` is captured per sub-tab so revisiting
|
|
71
|
-
* restores the user's last offset and switching to a never-visited sub-tab
|
|
72
|
-
* starts at the top instead of inheriting the outgoing sub-tab's offset
|
|
73
|
-
* (which would land the user at a meaningless row index against a
|
|
74
|
-
* completely different list).
|
|
75
|
-
* - Intentionally NOT captured (left in place on `autoCat` and inherited by
|
|
76
|
-
* the incoming sub-tab):
|
|
77
|
-
* - `uiState.searchString`, `uiState.sortKey`, `uiState.sortOrder`,
|
|
78
|
-
* `uiState.nodeCollapseState`, `uiState.limit` — search and sort are
|
|
79
|
-
* user-applied preferences that persist across sub-tabs by design (a
|
|
80
|
-
* search query stays active when the user toggles between Manual / AI
|
|
81
|
-
* Accountant).
|
|
82
|
-
* - `selectedCheckBoxTransactionIds` — selection state is intentionally
|
|
83
|
-
* cleared at the action source (the fetch epic / save flow), not
|
|
84
|
-
* by the snapshot path. Carrying selection across sub-tabs is safe
|
|
85
|
-
* because every sub-tab change goes through a fetch round-trip that
|
|
86
|
-
* reconciles selection against the new row IDs.
|
|
87
|
-
* - `transactionReviewLocalDataById`, `transactionIdsWithUnsavedData`,
|
|
88
|
-
* `uploadReceiptStatusById`, `saveStatus`, `refreshStatus`,
|
|
89
|
-
* `markAsNotMiscategorizedStatus` — these are per-row / per-flight
|
|
90
|
-
* caches that are write-through global state, not sub-tab-scoped.
|
|
91
|
-
*
|
|
92
|
-
* If the contract changes (e.g. selection should reset on every sub-tab
|
|
93
|
-
* switch), update both this comment and the snapshot return shape together.
|
|
94
|
-
*/
|
|
95
|
-
function snapshotAutoCategorizedTab(autoCat) {
|
|
96
|
-
const transactionIdsBySelectedPeriod = {
|
|
97
|
-
...autoCat.transactionIdsBySelectedPeriod,
|
|
98
|
-
};
|
|
99
|
-
const hasAnyLoadedRows = Object.values(transactionIdsBySelectedPeriod).some((ids) => ids.length > 0);
|
|
100
|
-
let normalisedFetchState = autoCat.fetchState;
|
|
101
|
-
if (normalisedFetchState === 'In-Progress') {
|
|
102
|
-
normalisedFetchState = hasAnyLoadedRows ? 'Completed' : 'Not-Started';
|
|
103
|
-
}
|
|
104
|
-
return {
|
|
105
|
-
error: autoCat.error,
|
|
106
|
-
fetchState: normalisedFetchState,
|
|
107
|
-
pageToken: autoCat.uiState.pageToken,
|
|
108
|
-
scrollPosition: autoCat.uiState.scrollPosition,
|
|
109
|
-
totalCount: { ...autoCat.uiState.totalCount },
|
|
110
|
-
transactionIdsBySelectedPeriod,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
50
|
const expenseAutomationTransactionsView = createSlice({
|
|
114
51
|
name: 'expenseAutomationTransactionsView',
|
|
115
52
|
initialState,
|
|
@@ -323,55 +260,6 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
323
260
|
return { payload: { newSelectedTab } };
|
|
324
261
|
},
|
|
325
262
|
},
|
|
326
|
-
updateTransactionCategorizationCompletedSubTab: {
|
|
327
|
-
reducer(draft, action) {
|
|
328
|
-
const { newSubTab } = action.payload;
|
|
329
|
-
const previousSubTab = draft.selectedTransactionCategorizationCompletedSubTab;
|
|
330
|
-
if (previousSubTab === newSubTab) {
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
const autoCat = draft.transactionCategorizationView.autoCategorized;
|
|
334
|
-
// Snapshot the outgoing sub-tab so a future revisit can short-circuit
|
|
335
|
-
// the fetch epic (mirrors the parent-tab "don't refetch what's
|
|
336
|
-
// already cached" behaviour). Period-scoped data is preserved as-is
|
|
337
|
-
// because the snapshot itself is keyed only by sub-tab; the active
|
|
338
|
-
// tab's per-period maps remain the source of truth while live.
|
|
339
|
-
draft.autoCategorizedSubTabCache[previousSubTab] =
|
|
340
|
-
snapshotAutoCategorizedTab(autoCat);
|
|
341
|
-
const cached = draft.autoCategorizedSubTabCache[newSubTab];
|
|
342
|
-
if (cached != null) {
|
|
343
|
-
autoCat.uiState.pageToken = cached.pageToken;
|
|
344
|
-
autoCat.uiState.totalCount = { ...cached.totalCount };
|
|
345
|
-
autoCat.uiState.scrollPosition = cached.scrollPosition;
|
|
346
|
-
autoCat.transactionIdsBySelectedPeriod = {
|
|
347
|
-
...cached.transactionIdsBySelectedPeriod,
|
|
348
|
-
};
|
|
349
|
-
autoCat.fetchState = cached.fetchState;
|
|
350
|
-
autoCat.error = cached.error;
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
// Sub-tab has never been visited — clear the active slot so the
|
|
354
|
-
// view-epic's `fetchState === 'Not-Started' || transactionIds.length
|
|
355
|
-
// === 0` cache check falls through and triggers a fresh page-1
|
|
356
|
-
// fetch for the new dataset. `scrollPosition` is reset to the top
|
|
357
|
-
// so the incoming list isn't anchored to the outgoing sub-tab's
|
|
358
|
-
// offset (different rows → previous offset is meaningless).
|
|
359
|
-
autoCat.uiState.pageToken = null;
|
|
360
|
-
autoCat.uiState.totalCount = {};
|
|
361
|
-
autoCat.uiState.scrollPosition = {
|
|
362
|
-
scrollTop: 0,
|
|
363
|
-
scrollLeft: undefined,
|
|
364
|
-
};
|
|
365
|
-
autoCat.transactionIdsBySelectedPeriod = {};
|
|
366
|
-
autoCat.fetchState = 'Not-Started';
|
|
367
|
-
autoCat.error = undefined;
|
|
368
|
-
}
|
|
369
|
-
draft.selectedTransactionCategorizationCompletedSubTab = newSubTab;
|
|
370
|
-
},
|
|
371
|
-
prepare(newSubTab) {
|
|
372
|
-
return { payload: { newSubTab } };
|
|
373
|
-
},
|
|
374
|
-
},
|
|
375
263
|
updateTransactionCategorization: {
|
|
376
264
|
reducer(draft, action) {
|
|
377
265
|
const selectedTab = action.payload.selectedTab;
|
|
@@ -809,10 +697,6 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
809
697
|
const { selectedTab, selectedPeriod, totalCount } = action.payload;
|
|
810
698
|
draft.transactionCategorizationView[selectedTab].uiState.totalCount[toMonthYearPeriodId(selectedPeriod)] = totalCount;
|
|
811
699
|
},
|
|
812
|
-
updateParentTotalCountForTab(draft, action) {
|
|
813
|
-
const { selectedTab, selectedPeriod, parentTotalCount } = action.payload;
|
|
814
|
-
draft.parentTotalCountByTab[selectedTab][toMonthYearPeriodId(selectedPeriod)] = parentTotalCount;
|
|
815
|
-
},
|
|
816
700
|
updateSelectedCheckboxTransactionIds(draft, action) {
|
|
817
701
|
const selectedTab = action.payload.selectedTab;
|
|
818
702
|
const transactionIds = action.payload.transactionIds;
|
|
@@ -823,16 +707,6 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
823
707
|
draft.transactionCategorizationView[selectedTab] = {
|
|
824
708
|
...initialTransactionTabViewState,
|
|
825
709
|
};
|
|
826
|
-
draft.parentTotalCountByTab[selectedTab] = {};
|
|
827
|
-
// The per-sub-tab snapshot cache lives at the slice root and is
|
|
828
|
-
// exclusively populated by the autoCategorized tab's sub-tab switching
|
|
829
|
-
// (see updateTransactionCategorizationCompletedSubTab). When that tab
|
|
830
|
-
// is reset, leaving stale snapshots behind would short-circuit the
|
|
831
|
-
// next sub-tab visit with phantom row IDs / page tokens that no longer
|
|
832
|
-
// correspond to anything in state — drop them in lockstep.
|
|
833
|
-
if (selectedTab === 'autoCategorized') {
|
|
834
|
-
draft.autoCategorizedSubTabCache = {};
|
|
835
|
-
}
|
|
836
710
|
},
|
|
837
711
|
clearExpenseAutomationTransactionsView(draft) {
|
|
838
712
|
Object.assign(draft, initialState);
|
|
@@ -929,5 +803,5 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
929
803
|
},
|
|
930
804
|
},
|
|
931
805
|
});
|
|
932
|
-
export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab,
|
|
806
|
+
export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, } = expenseAutomationTransactionsView.actions;
|
|
933
807
|
export default expenseAutomationTransactionsView.reducer;
|
|
@@ -224,8 +224,6 @@ export function getExpenseAutomationMissingReceiptsView(state) {
|
|
|
224
224
|
manualSearchUiResetKey: bulkUpload.manualSearchUiResetKey,
|
|
225
225
|
hasMoreBatchDetails,
|
|
226
226
|
batchDetailsPaginationState: bulkUpload.batchDetailsPaginationState,
|
|
227
|
-
batchDetailsRevalidating: bulkUpload.batchDetailsRevalidating,
|
|
228
|
-
initialBatchDetailsLoaded: bulkUpload.initialBatchDetailsLoaded,
|
|
229
227
|
manualSearch: {
|
|
230
228
|
...bulkUpload.manualSearch,
|
|
231
229
|
results: manualSearchResults,
|
|
@@ -10,7 +10,7 @@ import { getProjectList } from '../../projectList/projectListSelector';
|
|
|
10
10
|
import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
|
|
11
11
|
export function getExpenseAutomationTransactionView(state) {
|
|
12
12
|
const { accountState, classState, classListState, accountListState, expenseAutomationTransactionsViewState, expenseAutomationViewState, projectState, projectListState, transactionState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
|
|
13
|
-
const {
|
|
13
|
+
const { selectedTransactionCategorizationTab } = expenseAutomationTransactionsViewState;
|
|
14
14
|
const { uiState, refreshStatus, saveStatus, markAsNotMiscategorizedStatus, transactionIdsBySelectedPeriod, transactionReviewLocalDataById, selectedCheckBoxTransactionIds, transactionIdsWithUnsavedData, uploadReceiptStatusById, selectedTransactionId, selectedTransactionLineId, } = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTransactionCategorizationTab];
|
|
15
15
|
const uncategorizedIncomeExpense = getUncategorizedAccounts(accountState, accountListState, 'accountList');
|
|
16
16
|
const accountList = getAccountList(accountState, accountListState, 'accountList');
|
|
@@ -63,27 +63,10 @@ export function getExpenseAutomationTransactionView(state) {
|
|
|
63
63
|
.uiState.totalCount;
|
|
64
64
|
const totalCountByAutoCat = expenseAutomationTransactionsViewState.transactionCategorizationView
|
|
65
65
|
.autoCategorized.uiState.totalCount;
|
|
66
|
-
// Per-(sub-)tab counts powering pagination, in-list "showing X of Y", and
|
|
67
|
-
// any save/refresh round-trips. Sourced from `uiState.totalCount` which
|
|
68
|
-
// mirrors the response's `total_count` for whatever tab/sub-tab the user is
|
|
69
|
-
// viewing (e.g. only the Manual sub-tab rows for Completed → Manual).
|
|
70
66
|
const totalCountByTab = {
|
|
71
67
|
review: monthYearPeriodId != null ? totalCountByReview[monthYearPeriodId] : 0,
|
|
72
68
|
autoCategorized: monthYearPeriodId != null ? totalCountByAutoCat[monthYearPeriodId] : 0,
|
|
73
69
|
};
|
|
74
|
-
// Parent-tab badge counts. Sourced from `parent_tab_total_count` in the
|
|
75
|
-
// listing response and used exclusively by the navbar / tab strip badges.
|
|
76
|
-
// Independent of which Completed sub-tab is active so the badge stays
|
|
77
|
-
// stable across sub-tab switches.
|
|
78
|
-
const parentTotalCountByTab = expenseAutomationTransactionsViewState.parentTotalCountByTab;
|
|
79
|
-
const parentTabTotalCountByTab = {
|
|
80
|
-
review: monthYearPeriodId != null
|
|
81
|
-
? (parentTotalCountByTab.review[monthYearPeriodId] ?? 0)
|
|
82
|
-
: 0,
|
|
83
|
-
autoCategorized: monthYearPeriodId != null
|
|
84
|
-
? (parentTotalCountByTab.autoCategorized[monthYearPeriodId] ?? 0)
|
|
85
|
-
: 0,
|
|
86
|
-
};
|
|
87
70
|
const fetchStateByTab = {
|
|
88
71
|
review: {
|
|
89
72
|
fetchState: expenseAutomationTransactionsViewState.transactionCategorizationView
|
|
@@ -125,11 +108,9 @@ export function getExpenseAutomationTransactionView(state) {
|
|
|
125
108
|
markAsNotMiscategorizedStatus,
|
|
126
109
|
completionStatus,
|
|
127
110
|
totalCountByTab,
|
|
128
|
-
parentTabTotalCountByTab,
|
|
129
111
|
fetchStateByTransactionTabs: fetchStateByTab,
|
|
130
112
|
uploadReceiptStatusById,
|
|
131
113
|
selectedTransactionId,
|
|
132
114
|
selectedTransactionLineId,
|
|
133
|
-
selectedTransactionCategorizationCompletedSubTab,
|
|
134
115
|
};
|
|
135
116
|
}
|