@zeniai/client-epic-state 5.0.69-betaML1 → 5.0.69-betaML11
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/commonStateTypes/viewAndReport/viewAndReport.d.ts +2 -2
- package/lib/commonStateTypes/viewAndReport/viewAndReport.js +2 -0
- package/lib/entity/account/accountSelector.d.ts +9 -0
- package/lib/entity/account/accountSelector.js +14 -1
- package/lib/entity/account/accountState.d.ts +1 -1
- package/lib/entity/accountGroup/accountGroupState.d.ts +1 -1
- package/lib/entity/class/classReducer.d.ts +4 -4
- package/lib/entity/class/classState.d.ts +1 -1
- package/lib/entity/forecast/forecastState.d.ts +1 -1
- package/lib/entity/sectionAccountsView/sectionAccountsView.d.ts +1 -1
- package/lib/entity/sectionClassesViewV2/sectionClassesView.d.ts +1 -1
- package/lib/entity/sectionClassesViewV2/sectionClassesViewReducer.d.ts +2 -2
- package/lib/entity/sectionProjectView/sectionProjectView.d.ts +1 -1
- package/lib/entity/sectionProjectView/sectionProjectViewReducer.d.ts +2 -2
- package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
- package/lib/entity/snackbar/snackbarTypes.js +1 -0
- package/lib/entity/transaction/payloadTypes/transactionPayload.d.ts +4 -0
- package/lib/entity/transaction/payloadTypes/transactionPayload.js +20 -58
- package/lib/entity/transaction/stateTypes/transaction.d.ts +3 -0
- package/lib/epic.d.ts +3 -2
- package/lib/epic.js +3 -2
- package/lib/esm/commonStateTypes/viewAndReport/viewAndReport.js +2 -0
- package/lib/esm/entity/account/accountSelector.js +11 -0
- package/lib/esm/entity/snackbar/snackbarTypes.js +1 -0
- package/lib/esm/entity/transaction/payloadTypes/transactionPayload.js +20 -58
- package/lib/esm/epic.js +3 -2
- package/lib/esm/index.js +9 -8
- package/lib/esm/reducer.js +3 -0
- package/lib/esm/view/createTransferEntry/createTransferEntryReducer.js +56 -0
- package/lib/esm/view/createTransferEntry/createTransferEntrySelector.js +64 -0
- package/lib/esm/view/createTransferEntry/createTransferEntryState.js +17 -0
- package/lib/esm/view/createTransferEntry/epics/createTransferEntryEpic.js +112 -0
- package/lib/esm/view/createTransferEntry/epics/fetchTransferAccountsEpic.js +36 -0
- package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic.js +8 -0
- package/lib/esm/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.js +1 -21
- package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +26 -117
- package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +9 -29
- package/lib/esm/view/recommendation/recommendationHelper.js +0 -29
- package/lib/esm/view/recommendation/recommendationReducer.js +1 -36
- package/lib/esm/view/transactionDetail/epics/transactionDetailEpic.js +14 -2
- package/lib/esm/view/transactionDetail/transactionDetailReducer.js +3 -110
- package/lib/esm/view/transactionDetail/transactionDetailSelector.js +6 -23
- package/lib/index.d.ts +9 -9
- package/lib/index.js +43 -36
- package/lib/reducer.d.ts +3 -0
- package/lib/reducer.js +3 -0
- package/lib/view/auditReportView/auditReportViewState.d.ts +1 -1
- package/lib/view/billPayCard/billPayCardSelector.d.ts +1 -1
- package/lib/view/cardBalance/cardBalanceSelector.d.ts +1 -1
- package/lib/view/cashBalance/cashBalanceSelector.d.ts +1 -1
- package/lib/view/cashInCashOut/cashInCashOutSelector.d.ts +1 -1
- package/lib/view/cashPosition/cashPositionSelector.d.ts +1 -1
- package/lib/view/createTransferEntry/createTransferEntryReducer.d.ts +32 -0
- package/lib/view/createTransferEntry/createTransferEntryReducer.js +60 -0
- package/lib/view/createTransferEntry/createTransferEntrySelector.d.ts +39 -0
- package/lib/view/createTransferEntry/createTransferEntrySelector.js +69 -0
- package/lib/view/createTransferEntry/createTransferEntryState.d.ts +30 -0
- package/lib/view/createTransferEntry/createTransferEntryState.js +20 -0
- package/lib/view/createTransferEntry/epics/createTransferEntryEpic.d.ts +11 -0
- package/lib/view/createTransferEntry/epics/createTransferEntryEpic.js +116 -0
- package/lib/view/createTransferEntry/epics/fetchTransferAccountsEpic.d.ts +14 -0
- package/lib/view/createTransferEntry/epics/fetchTransferAccountsEpic.js +40 -0
- package/lib/view/dashboard/dashboardReducer.d.ts +2 -2
- package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic.d.ts +2 -1
- package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic.js +8 -0
- package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -2
- package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.js +2 -23
- package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +4 -15
- package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +26 -117
- package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +4 -3
- package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.d.ts +1 -3
- package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +11 -30
- package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +0 -1
- package/lib/view/financeStatement/financeStatementReducer.d.ts +1 -1
- package/lib/view/globalMerchantView/globalMerchantViewReducer.d.ts +2 -2
- package/lib/view/recommendation/recommendationHelper.d.ts +0 -9
- package/lib/view/recommendation/recommendationHelper.js +1 -31
- package/lib/view/recommendation/recommendationReducer.d.ts +1 -18
- package/lib/view/recommendation/recommendationReducer.js +2 -37
- package/lib/view/reimbursementCard/reimbursementCardSelector.d.ts +1 -1
- package/lib/view/reportsResync/reportsResyncReducer.d.ts +2 -2
- package/lib/view/tasksCard/tasksCardSelector.d.ts +1 -1
- package/lib/view/topEx/topExSelector.d.ts +1 -1
- package/lib/view/transactionDetail/epics/transactionDetailEpic.d.ts +2 -1
- package/lib/view/transactionDetail/epics/transactionDetailEpic.js +14 -2
- package/lib/view/transactionDetail/transactionDetailPayload.d.ts +1 -1
- package/lib/view/transactionDetail/transactionDetailReducer.d.ts +3 -12
- package/lib/view/transactionDetail/transactionDetailReducer.js +4 -111
- package/lib/view/transactionDetail/transactionDetailSelector.d.ts +6 -3
- package/lib/view/transactionDetail/transactionDetailSelector.js +7 -25
- package/lib/view/transactionDetail/transactionDetailTypes.d.ts +0 -1
- package/package.json +1 -1
- package/lib/esm/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.js +0 -87
- package/lib/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.d.ts +0 -9
- package/lib/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.js +0 -91
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createSlice } from '@reduxjs/toolkit';
|
|
2
|
+
import { initialCreateTransferEntryState, } from './createTransferEntryState';
|
|
3
|
+
export const initialState = initialCreateTransferEntryState;
|
|
4
|
+
const createTransferEntrySlice = createSlice({
|
|
5
|
+
name: 'createTransferEntry',
|
|
6
|
+
initialState,
|
|
7
|
+
reducers: {
|
|
8
|
+
fetchAccountsForTransferFlow(draft, action) {
|
|
9
|
+
draft.transferAccountsByType[action.payload.kind].fetchState =
|
|
10
|
+
'In-Progress';
|
|
11
|
+
},
|
|
12
|
+
updateAccountsForTransferFlow(draft, action) {
|
|
13
|
+
const slice = draft.transferAccountsByType[action.payload.kind];
|
|
14
|
+
slice.fetchState = 'Completed';
|
|
15
|
+
slice.accountIds = action.payload.accountIds;
|
|
16
|
+
},
|
|
17
|
+
updateAccountsForTransferFlowFailure(draft, action) {
|
|
18
|
+
draft.transferAccountsByType[action.payload.kind].fetchState = 'Error';
|
|
19
|
+
},
|
|
20
|
+
createTransferEntry: {
|
|
21
|
+
prepare(payload) {
|
|
22
|
+
return { payload };
|
|
23
|
+
},
|
|
24
|
+
reducer(draft) {
|
|
25
|
+
draft.createTransferEntryStatus = 'In-Progress';
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
createTransferEntrySuccess(draft) {
|
|
29
|
+
draft.createTransferEntryStatus = 'Completed';
|
|
30
|
+
},
|
|
31
|
+
createTransferEntryFailure(draft) {
|
|
32
|
+
draft.createTransferEntryStatus = 'Error';
|
|
33
|
+
},
|
|
34
|
+
resetCreateTransferEntryStatus(draft) {
|
|
35
|
+
draft.createTransferEntryStatus = 'Not-Started';
|
|
36
|
+
},
|
|
37
|
+
createTransferEntryReplacedTransaction: {
|
|
38
|
+
prepare(previousTransactionId, newTransactionId) {
|
|
39
|
+
return { payload: { newTransactionId, previousTransactionId } };
|
|
40
|
+
},
|
|
41
|
+
reducer(draft, action) {
|
|
42
|
+
draft.lastTransferEntryReplacement = action.payload;
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
/**
|
|
46
|
+
* One-shot acknowledgement: the host has consumed the route-replacement
|
|
47
|
+
* signal and navigated to the new transaction url, so we can drop the
|
|
48
|
+
* record. Re-dispatching is idempotent.
|
|
49
|
+
*/
|
|
50
|
+
clearTransferEntryRouteReplacement(draft) {
|
|
51
|
+
draft.lastTransferEntryReplacement = undefined;
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
export const { fetchAccountsForTransferFlow, updateAccountsForTransferFlow, updateAccountsForTransferFlowFailure, createTransferEntry, createTransferEntrySuccess, createTransferEntryFailure, resetCreateTransferEntryStatus, createTransferEntryReplacedTransaction, clearTransferEntryRouteReplacement, } = createTransferEntrySlice.actions;
|
|
56
|
+
export default createTransferEntrySlice.reducer;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createSelector } from '@reduxjs/toolkit';
|
|
2
|
+
import { getAccountBase, getAccountBaseForCreditCardTransferAccounts, getAccountBaseForTransferAccounts, } from '../../entity/account/accountSelector';
|
|
3
|
+
import { getAccountList } from '../accountList/accountListSelector';
|
|
4
|
+
export function getCreateTransferEntryStatus(state) {
|
|
5
|
+
return state.createTransferEntryState.createTransferEntryStatus;
|
|
6
|
+
}
|
|
7
|
+
export function getLastTransferEntryReplacement(state) {
|
|
8
|
+
return state.createTransferEntryState.lastTransferEntryReplacement;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Shared resolver for the credit/debit dropdowns in the transfer drawer.
|
|
12
|
+
*
|
|
13
|
+
* Resolution policy (intentional):
|
|
14
|
+
* 1. Prefer the dedicated API fetch results (`transferEntryEligible` for the
|
|
15
|
+
* debit pool, `creditCardOnlyEligible` for the credit-card-payment credit
|
|
16
|
+
* pool).
|
|
17
|
+
* 2. If the dedicated fetch returns an empty list while the COA is loaded,
|
|
18
|
+
* fall back to the COA: the full account list for `transferEntryEligible`,
|
|
19
|
+
* and COA accounts filtered to `credit_card` for `creditCardOnlyEligible`.
|
|
20
|
+
* This keeps the dropdown populated for older tenants where the dedicated
|
|
21
|
+
* endpoints haven't been backfilled yet.
|
|
22
|
+
*
|
|
23
|
+
* Intent — what this selector deliberately does NOT do:
|
|
24
|
+
* - Does NOT distinguish "fetch in-flight" / "fetch errored" / "fetch returned
|
|
25
|
+
* empty" — all three collapse to the COA fallback. The product preference
|
|
26
|
+
* is "always show something selectable" over "show a spinner that may never
|
|
27
|
+
* resolve". Loading/error UI belongs in the view layer; if you need a
|
|
28
|
+
* spinner, observe the `fetchState` on `transferAccountsByType[kind]`
|
|
29
|
+
* separately rather than changing the resolution policy here.
|
|
30
|
+
* - Does NOT enforce "API result is authoritative even if empty" — a tenant
|
|
31
|
+
* whose dedicated endpoint is correctly returning `[]` (e.g. no eligible
|
|
32
|
+
* transfer destinations) will still see the COA. That trade-off is
|
|
33
|
+
* accepted because the alternative blocks the user from picking anything;
|
|
34
|
+
* the backend distinguishes "no result" from "endpoint not implemented" by
|
|
35
|
+
* surfacing an error response, which the view layer handles separately.
|
|
36
|
+
*
|
|
37
|
+
* Memoized with reselect so consuming view-models don't recompute on every
|
|
38
|
+
* unrelated state change.
|
|
39
|
+
*/
|
|
40
|
+
export const getResolvedTransferAccounts = createSelector([
|
|
41
|
+
(state) => state.accountState,
|
|
42
|
+
(state) => state.accountListState,
|
|
43
|
+
(state) => state.createTransferEntryState.transferAccountsByType
|
|
44
|
+
.transferEntryEligible.accountIds,
|
|
45
|
+
(state) => state.createTransferEntryState.transferAccountsByType
|
|
46
|
+
.creditCardOnlyEligible.accountIds,
|
|
47
|
+
], (accountState, accountListState, transferEligibleIds, creditCardOnlyIds) => {
|
|
48
|
+
const accountList = getAccountList(accountState, accountListState, 'accountList');
|
|
49
|
+
const transferEligibleFromApi = transferEligibleIds
|
|
50
|
+
.map((id) => getAccountBaseForTransferAccounts(accountState, id))
|
|
51
|
+
.filter((account) => account != null);
|
|
52
|
+
const coaFallbackAllAccounts = accountList.accounts
|
|
53
|
+
.map((a) => getAccountBase(accountState, a.accountId, 'account_list'))
|
|
54
|
+
.filter((account) => account != null);
|
|
55
|
+
const transferAccountsList = transferEligibleFromApi.length > 0
|
|
56
|
+
? transferEligibleFromApi
|
|
57
|
+
: coaFallbackAllAccounts;
|
|
58
|
+
const creditCardFromApi = creditCardOnlyIds
|
|
59
|
+
.map((id) => getAccountBaseForCreditCardTransferAccounts(accountState, id))
|
|
60
|
+
.filter((account) => account != null);
|
|
61
|
+
const coaFallbackCreditCards = coaFallbackAllAccounts.filter((a) => a.accountType === 'credit_card');
|
|
62
|
+
const creditCardTransferAccountsList = creditCardFromApi.length > 0 ? creditCardFromApi : coaFallbackCreditCards;
|
|
63
|
+
return { creditCardTransferAccountsList, transferAccountsList };
|
|
64
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Which pool `fetchAccountsForTransferFlow` / transfer-flow epic should load. */
|
|
2
|
+
export const TRANSFER_ACCOUNTS_FETCH_TYPE = [
|
|
3
|
+
'transferEntryEligible',
|
|
4
|
+
'creditCardOnlyEligible',
|
|
5
|
+
];
|
|
6
|
+
const initialTransferAccountsState = {
|
|
7
|
+
accountIds: [],
|
|
8
|
+
fetchState: 'Not-Started',
|
|
9
|
+
};
|
|
10
|
+
export const initialCreateTransferEntryState = {
|
|
11
|
+
createTransferEntryStatus: 'Not-Started',
|
|
12
|
+
lastTransferEntryReplacement: undefined,
|
|
13
|
+
transferAccountsByType: {
|
|
14
|
+
transferEntryEligible: initialTransferAccountsState,
|
|
15
|
+
creditCardOnlyEligible: initialTransferAccountsState,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { concat, from, timer } from 'rxjs';
|
|
2
|
+
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
|
+
import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
4
|
+
import { toTransactionID } from '../../../entity/transaction/payloadTypes/transactionIDPayload';
|
|
5
|
+
import { removeTransaction, updateTransaction, } from '../../../entity/transaction/transactionReducer';
|
|
6
|
+
import { isSuccessResponse } from '../../../responsePayload';
|
|
7
|
+
import { removeTransactionFromAllTabs } from '../../expenseAutomationView/reducers/transactionsViewReducer';
|
|
8
|
+
import { initializeTransactionDetailLocalData, removeTransactionDetail, } from '../../transactionDetail/transactionDetailReducer';
|
|
9
|
+
import { getTransactionDetailKey } from '../../transactionDetail/transactionDetailState';
|
|
10
|
+
import { clearTransferEntryRouteReplacement, createTransferEntry, createTransferEntryFailure, createTransferEntryReplacedTransaction, createTransferEntrySuccess, resetCreateTransferEntryStatus, } from '../createTransferEntryReducer';
|
|
11
|
+
// ─── Pure helpers ────────────────────────────────────────────────────────────
|
|
12
|
+
function transactionIdsAreEqual(a, b) {
|
|
13
|
+
return a.id === b.id && a.type === b.type;
|
|
14
|
+
}
|
|
15
|
+
const successSnackbar = () => openSnackbar({
|
|
16
|
+
messageSection: 'create_transfer_entry',
|
|
17
|
+
messageText: 'success',
|
|
18
|
+
type: 'success',
|
|
19
|
+
});
|
|
20
|
+
const failureSnackbar = () => openSnackbar({
|
|
21
|
+
messageSection: 'create_transfer_entry',
|
|
22
|
+
messageText: 'failed',
|
|
23
|
+
type: 'error',
|
|
24
|
+
});
|
|
25
|
+
function buildFailureActions() {
|
|
26
|
+
return [createTransferEntryFailure(), failureSnackbar()];
|
|
27
|
+
}
|
|
28
|
+
function buildSuccessSideEffects(sourceTransactionId) {
|
|
29
|
+
return [
|
|
30
|
+
createTransferEntrySuccess(),
|
|
31
|
+
removeTransactionFromAllTabs(sourceTransactionId),
|
|
32
|
+
successSnackbar(),
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
function buildPreviousTransactionCleanupActions(previousTransactionId) {
|
|
36
|
+
return [
|
|
37
|
+
removeTransaction(getTransactionDetailKey(previousTransactionId)),
|
|
38
|
+
removeTransactionDetail(previousTransactionId),
|
|
39
|
+
clearTransferEntryRouteReplacement(),
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Emits `resetCreateTransferEntryStatus()` (and, on the replacement path, the
|
|
44
|
+
* follow-up cleanup actions) one scheduler tick after the success actions.
|
|
45
|
+
*
|
|
46
|
+
* Why the `timer(0)` (and not synchronous emission):
|
|
47
|
+
* On the replacement path the host (web-app-ui) watches
|
|
48
|
+
* `lastTransferEntryReplacement`, navigates to the new transaction url, and
|
|
49
|
+
* then sees `clearTransferEntryRouteReplacement` clear the signal. If we
|
|
50
|
+
* dispatched the cleanup actions in the same tick as
|
|
51
|
+
* `createTransferEntryReplacedTransaction`, redux subscribers would observe
|
|
52
|
+
* `lastTransferEntryReplacement` flicker on then off in a single render, and
|
|
53
|
+
* the old transaction's state (which the still-mounted detail view may be
|
|
54
|
+
* reading) would be torn down before the host has had a chance to start
|
|
55
|
+
* navigating. Deferring by one microtask gives the host exactly one render
|
|
56
|
+
* pass to react before we tear down the previous transaction.
|
|
57
|
+
*/
|
|
58
|
+
function delayedResetAndPreviousCleanup(previousTransactionId) {
|
|
59
|
+
return timer(0).pipe(mergeMap(() => {
|
|
60
|
+
const actions = [resetCreateTransferEntryStatus()];
|
|
61
|
+
if (previousTransactionId != null) {
|
|
62
|
+
actions.push(...buildPreviousTransactionCleanupActions(previousTransactionId));
|
|
63
|
+
}
|
|
64
|
+
return from(actions);
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
// ─── Success-path branches ───────────────────────────────────────────────────
|
|
68
|
+
function handleSuccessWithoutTransactionBody(transactionId) {
|
|
69
|
+
return concat(from(buildSuccessSideEffects(transactionId)), delayedResetAndPreviousCleanup(undefined));
|
|
70
|
+
}
|
|
71
|
+
function handleSuccessWithTransactionBody(transactionPayload, vendorPayload, previousTransactionId, sourceTransactionId) {
|
|
72
|
+
const newTransactionId = toTransactionID(transactionPayload);
|
|
73
|
+
const replaced = !transactionIdsAreEqual(previousTransactionId, newTransactionId);
|
|
74
|
+
const immediate = [
|
|
75
|
+
updateTransaction(getTransactionDetailKey(newTransactionId), transactionPayload),
|
|
76
|
+
initializeTransactionDetailLocalData({
|
|
77
|
+
transactionId: newTransactionId,
|
|
78
|
+
vendorId: vendorPayload?.[0]?.vendor_id,
|
|
79
|
+
}),
|
|
80
|
+
...buildSuccessSideEffects(sourceTransactionId),
|
|
81
|
+
];
|
|
82
|
+
if (replaced) {
|
|
83
|
+
immediate.push(createTransferEntryReplacedTransaction(previousTransactionId, newTransactionId));
|
|
84
|
+
}
|
|
85
|
+
return concat(from(immediate), delayedResetAndPreviousCleanup(replaced ? previousTransactionId : undefined));
|
|
86
|
+
}
|
|
87
|
+
// ─── Top-level epic ──────────────────────────────────────────────────────────
|
|
88
|
+
const CREATE_TRANSFER_ENTRY_URL = `https://dev.api.zeni.ai/version/accounting/komal/1.0/transactions/transaction-type-update`;
|
|
89
|
+
export const createTransferEntryEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(createTransferEntry.match), mergeMap((action) => {
|
|
90
|
+
const { transactionId, creditAccountIntegrationId, debitAccountIntegrationId, memo, transactionType, sourceTransactionType, } = action.payload;
|
|
91
|
+
const previousTransactionId = {
|
|
92
|
+
id: transactionId,
|
|
93
|
+
type: sourceTransactionType,
|
|
94
|
+
};
|
|
95
|
+
return zeniAPI
|
|
96
|
+
.postAndGetJSON(CREATE_TRANSFER_ENTRY_URL, {
|
|
97
|
+
transaction_type: transactionType,
|
|
98
|
+
transaction_id: transactionId,
|
|
99
|
+
to_account_integration_id: creditAccountIntegrationId,
|
|
100
|
+
from_account_integration_id: debitAccountIntegrationId,
|
|
101
|
+
memo,
|
|
102
|
+
})
|
|
103
|
+
.pipe(mergeMap((response) => {
|
|
104
|
+
if (!isSuccessResponse(response)) {
|
|
105
|
+
return from(buildFailureActions());
|
|
106
|
+
}
|
|
107
|
+
if (response.data?.transaction != null) {
|
|
108
|
+
return handleSuccessWithTransactionBody(response.data.transaction, response.data.vendors, previousTransactionId, transactionId);
|
|
109
|
+
}
|
|
110
|
+
return handleSuccessWithoutTransactionBody(transactionId);
|
|
111
|
+
}), catchError(() => from(buildFailureActions())));
|
|
112
|
+
}));
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { of } from 'rxjs';
|
|
2
|
+
import { catchError, filter, groupBy, mergeMap, switchMap } from 'rxjs/operators';
|
|
3
|
+
import { updateAccounts } from '../../../entity/account/accountReducer';
|
|
4
|
+
import { isSuccessResponse } from '../../../responsePayload';
|
|
5
|
+
import { fetchAccountsForTransferFlow, updateAccountsForTransferFlow, updateAccountsForTransferFlowFailure, } from '../createTransferEntryReducer';
|
|
6
|
+
const REPORT_ID_BY_TYPE = {
|
|
7
|
+
transferEntryEligible: 'transfer_accounts',
|
|
8
|
+
creditCardOnlyEligible: 'credit_card_transfer_accounts',
|
|
9
|
+
};
|
|
10
|
+
const QUERY_BY_TYPE = {
|
|
11
|
+
transferEntryEligible: { is_only_include_transfer_accounts: true },
|
|
12
|
+
creditCardOnlyEligible: { is_include_only_credit_card_accounts: true },
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Pools fetched concurrently: `groupBy(kind)` keeps `transferEntryEligible`
|
|
16
|
+
* and `creditCardOnlyEligible` in their own streams so a back-to-back dispatch
|
|
17
|
+
* of both does not cancel the first. Within a single type, a fresh dispatch
|
|
18
|
+
* supersedes any in-flight request (per-group `switchMap`).
|
|
19
|
+
*/
|
|
20
|
+
export const fetchTransferAccountsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchAccountsForTransferFlow.match), groupBy((action) => action.payload.kind), mergeMap((group$) => group$.pipe(switchMap((action) => {
|
|
21
|
+
const { kind } = action.payload;
|
|
22
|
+
const queryValue = QUERY_BY_TYPE[kind];
|
|
23
|
+
const reportId = REPORT_ID_BY_TYPE[kind];
|
|
24
|
+
const url = `https://dev.api.zeni.ai/version/accounting/komal/1.0/accounts?query=${encodeURIComponent(JSON.stringify(queryValue))}`;
|
|
25
|
+
return zeniAPI.getJSON(url).pipe(mergeMap((response) => {
|
|
26
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
27
|
+
const accountIds = response.data.accounts.map((account) => account.account_id);
|
|
28
|
+
const actions = [
|
|
29
|
+
updateAccounts(response.data.accounts, reportId),
|
|
30
|
+
updateAccountsForTransferFlow({ accountIds, kind }),
|
|
31
|
+
];
|
|
32
|
+
return of(...actions);
|
|
33
|
+
}
|
|
34
|
+
return of(updateAccountsForTransferFlowFailure({ kind }));
|
|
35
|
+
}), catchError(() => of(updateAccountsForTransferFlowFailure({ kind }))));
|
|
36
|
+
}))));
|
|
@@ -4,6 +4,7 @@ import { toMonthYearPeriodId, } from '../../../../commonStateTypes/timePeriod';
|
|
|
4
4
|
import { getIsAccountingProjectsEnabled } from '../../../../entity/tenant/tenantSelector';
|
|
5
5
|
import { fetchAccountList } from '../../../accountList/accountListReducer';
|
|
6
6
|
import { fetchClassList } from '../../../classList/classListReducer';
|
|
7
|
+
import { fetchAccountsForTransferFlow } from '../../../createTransferEntry/createTransferEntryReducer';
|
|
7
8
|
import { fetchOwnerList } from '../../../ownerList/ownerListReducer';
|
|
8
9
|
import { fetchProjectList } from '../../../projectList/projectListReducer';
|
|
9
10
|
import { fetchTransactionCategorization, fetchTransactionCategorizationView, } from '../../reducers/transactionsViewReducer';
|
|
@@ -23,6 +24,13 @@ export const fetchTransactionCategorizationViewEpic = (actions$, state$) => acti
|
|
|
23
24
|
accountList.fetchState === 'Not-Started') {
|
|
24
25
|
updateActions.push(fetchAccountList('accountList', true));
|
|
25
26
|
}
|
|
27
|
+
const { transferEntryEligible, creditCardOnlyEligible } = state$.value.createTransferEntryState.transferAccountsByType;
|
|
28
|
+
if (transferEntryEligible.fetchState === 'Not-Started') {
|
|
29
|
+
updateActions.push(fetchAccountsForTransferFlow({ kind: 'transferEntryEligible' }));
|
|
30
|
+
}
|
|
31
|
+
if (creditCardOnlyEligible.fetchState === 'Not-Started') {
|
|
32
|
+
updateActions.push(fetchAccountsForTransferFlow({ kind: 'creditCardOnlyEligible' }));
|
|
33
|
+
}
|
|
26
34
|
const classList = state$.value.classListState;
|
|
27
35
|
if (classList.hasValidState() === false &&
|
|
28
36
|
classList.fetchState === 'Not-Started') {
|
package/lib/esm/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.js
CHANGED
|
@@ -59,22 +59,6 @@ export const filterAutoTabLineItems = (tabView, transactionId, updatedTransactio
|
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
61
|
export const MAX_SELECTION_LIMIT = 5;
|
|
62
|
-
// Returns true when the transaction should be auto-added to the
|
|
63
|
-
// selectedCheckBoxTransactionIds list after a categorization-related
|
|
64
|
-
// state mutation: all line items are filled, the selection limit has
|
|
65
|
-
// not been hit, and the id isn't already in the list. Consolidates the
|
|
66
|
-
// triplet that was inlined at four sites (updateSelectedCustomer/Vendor,
|
|
67
|
-
// setAllItemsToCategoryClassInLocalDataForCategorization, and
|
|
68
|
-
// setEntityRecommendationForLineIdInLocalData below).
|
|
69
|
-
export const shouldAutoSelectAndAdd = (transaction, localData, selectedCheckBoxTransactionIds, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled) => {
|
|
70
|
-
if (selectedCheckBoxTransactionIds.includes(transaction.id)) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
if (selectedCheckBoxTransactionIds.length >= MAX_SELECTION_LIMIT) {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
return checkIfAllLineItemsAreCategoryClassFilled(transaction, localData, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled);
|
|
77
|
-
};
|
|
78
62
|
export const toTransactionDetailLocalData = (transaction, selectedTab, lineItemsByTransactionIds, uncategorizedIncomeExpense, isUncategorizedExpenseCategoryEnabled) => {
|
|
79
63
|
const transactionLocal = {
|
|
80
64
|
...initialSupportedTransactionCategorization.transactionReviewLocalData,
|
|
@@ -560,14 +544,10 @@ export const setEntityRecommendationForLineIdInLocalData = (draft, transaction,
|
|
|
560
544
|
selectedEntity: transactionDetailLocalData.selectedEntity,
|
|
561
545
|
tabSpecificLineItems: transactionDetailLocalData.tabSpecificLineItems,
|
|
562
546
|
};
|
|
563
|
-
// Auto-uncheck if not all line items are filled (the row's
|
|
564
|
-
// category/class became stale after the entity update). Otherwise
|
|
565
|
-
// route through shouldAutoSelectAndAdd so the MAX_SELECTION_LIMIT
|
|
566
|
-
// gate is consistently applied across all auto-add sites.
|
|
567
547
|
if (!checkIfAllLineItemsAreCategoryClassFilled(transaction, newLocalData, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled)) {
|
|
568
548
|
selectedCheckBoxTransactionIds = selectedCheckBoxTransactionIds.filter((id) => id !== transaction.id);
|
|
569
549
|
}
|
|
570
|
-
else if (
|
|
550
|
+
else if (selectedCheckBoxTransactionIds.includes(transaction.id) === false) {
|
|
571
551
|
selectedCheckBoxTransactionIds.push(transaction.id);
|
|
572
552
|
}
|
|
573
553
|
draft.selectedCheckBoxTransactionIds = selectedCheckBoxTransactionIds;
|
|
@@ -3,8 +3,9 @@ import recordGet from 'lodash/get';
|
|
|
3
3
|
import uniq from 'lodash/uniq';
|
|
4
4
|
import { toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
|
|
5
5
|
import { isVendorTransaction } from '../../../entity/transaction/stateTypes/vendorTransaction';
|
|
6
|
-
import { filterAutoTabLineItems, mergeTabSpecificLineItems, removeTransactionFromTabView, setEntityRecommendationForLineIdInLocalData,
|
|
6
|
+
import { MAX_SELECTION_LIMIT, checkIfAllLineItemsAreCategoryClassFilled, filterAutoTabLineItems, mergeTabSpecificLineItems, removeTransactionFromTabView, setEntityRecommendationForLineIdInLocalData, toSetAllLineItemsToCategoryClass, toTransactionDetailLocalData, } from '../helpers/transactionCategorizationLocalDataHelper';
|
|
7
7
|
import { DEFAULT_COMPLETED_SUB_TAB, } from '../types/completedSubTab';
|
|
8
|
+
import { TRANSACTIONS_TABS, } from '../types/transactionsViewState';
|
|
8
9
|
export const initialTransactionTabViewState = {
|
|
9
10
|
saveStatus: {
|
|
10
11
|
fetchState: 'Not-Started',
|
|
@@ -420,9 +421,12 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
420
421
|
const selectedCheckBoxTransactionIds = draft.transactionCategorizationView[selectedTab]
|
|
421
422
|
.selectedCheckBoxTransactionIds;
|
|
422
423
|
if (selectedTransaction != null &&
|
|
423
|
-
|
|
424
|
+
checkIfAllLineItemsAreCategoryClassFilled(selectedTransaction, newTransactionLocalData, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled) === true && // check if all line items are filled
|
|
425
|
+
selectedCheckBoxTransactionIds.length < MAX_SELECTION_LIMIT &&
|
|
426
|
+
selectedCheckBoxTransactionIds.includes(transactionId) === false) {
|
|
424
427
|
draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds = [
|
|
425
|
-
...
|
|
428
|
+
...draft.transactionCategorizationView[selectedTab]
|
|
429
|
+
.selectedCheckBoxTransactionIds,
|
|
426
430
|
transactionId,
|
|
427
431
|
];
|
|
428
432
|
}
|
|
@@ -505,26 +509,9 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
505
509
|
const transactionLocalData = recordGet(draft.transactionCategorizationView[selectedTab]
|
|
506
510
|
.transactionReviewLocalDataById, transactionId.id, undefined);
|
|
507
511
|
if (transactionLocalData != null) {
|
|
508
|
-
const newLocalData = setEntityRecommendationForLineIdInLocalData(draft.transactionCategorizationView[selectedTab], transaction, transactionLocalData.transactionReviewLocalData, entity, lineIds, uncategorizedAccounts, action.payload.recommendationWithCOTByLineId, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled);
|
|
509
|
-
// Release the per-line fetch flag for every line this fetch
|
|
510
|
-
// covered. Done in-place on the freshly returned local data
|
|
511
|
-
// (the helper produces a new mutable object), before we write
|
|
512
|
-
// it back to the draft.
|
|
513
|
-
lineIds.forEach((lineId) => {
|
|
514
|
-
const lineItem = newLocalData.lineItemById[lineId];
|
|
515
|
-
if (lineItem != null) {
|
|
516
|
-
newLocalData.lineItemById[lineId] = {
|
|
517
|
-
...lineItem,
|
|
518
|
-
categoryClassRecommendationsFetchState: {
|
|
519
|
-
fetchState: 'Completed',
|
|
520
|
-
error: undefined,
|
|
521
|
-
},
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
});
|
|
525
512
|
draft.transactionCategorizationView[selectedTab].transactionReviewLocalDataById[transaction.id] = {
|
|
526
513
|
transactionId: transaction.id,
|
|
527
|
-
transactionReviewLocalData:
|
|
514
|
+
transactionReviewLocalData: setEntityRecommendationForLineIdInLocalData(draft.transactionCategorizationView[selectedTab], transaction, transactionLocalData.transactionReviewLocalData, entity, lineIds, uncategorizedAccounts, action.payload.recommendationWithCOTByLineId, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled),
|
|
528
515
|
};
|
|
529
516
|
// Mark transaction as dirty (autofill happened)
|
|
530
517
|
if (!draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData.includes(transaction.id)) {
|
|
@@ -537,99 +524,6 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
537
524
|
}
|
|
538
525
|
},
|
|
539
526
|
},
|
|
540
|
-
// Start arm of the line-update recommendations fetch. Flips covered
|
|
541
|
-
// lines to In-Progress AND removes the transaction from
|
|
542
|
-
// selectedCheckBoxTransactionIds — the row is unchecked + disabled
|
|
543
|
-
// until either the success arm (recheck if limit allows) or the
|
|
544
|
-
// failure arm releases the per-line flag.
|
|
545
|
-
markCategoryClassRecommendationsInProgressForCategorization: {
|
|
546
|
-
reducer(draft, action) {
|
|
547
|
-
const { selectedTab, transactionId, lineIds } = action.payload;
|
|
548
|
-
const tab = draft.transactionCategorizationView[selectedTab];
|
|
549
|
-
const transactionLocalData = recordGet(tab.transactionReviewLocalDataById, transactionId.id, undefined);
|
|
550
|
-
if (transactionLocalData != null) {
|
|
551
|
-
lineIds.forEach((lineId) => {
|
|
552
|
-
const lineItem = transactionLocalData.transactionReviewLocalData.lineItemById[lineId];
|
|
553
|
-
if (lineItem != null) {
|
|
554
|
-
transactionLocalData.transactionReviewLocalData.lineItemById[lineId] = {
|
|
555
|
-
...lineItem,
|
|
556
|
-
categoryClassRecommendationsFetchState: {
|
|
557
|
-
fetchState: 'In-Progress',
|
|
558
|
-
error: undefined,
|
|
559
|
-
},
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
// Uncheck on fetch start: the row's existing category/class are
|
|
565
|
-
// stale after a payee change, so we drop the auto-selection until
|
|
566
|
-
// fresh recommendations land.
|
|
567
|
-
if (tab.selectedCheckBoxTransactionIds.includes(transactionId.id)) {
|
|
568
|
-
tab.selectedCheckBoxTransactionIds =
|
|
569
|
-
tab.selectedCheckBoxTransactionIds.filter((id) => id !== transactionId.id);
|
|
570
|
-
}
|
|
571
|
-
},
|
|
572
|
-
prepare(selectedTab, transactionId, lineIds) {
|
|
573
|
-
return { payload: { selectedTab, transactionId, lineIds } };
|
|
574
|
-
},
|
|
575
|
-
},
|
|
576
|
-
// Completed arm. Used by the epic when the HTTP call succeeds but the
|
|
577
|
-
// server returned no per-line recommendations — we still need to
|
|
578
|
-
// release the In-Progress flag so the skeleton resolves. Successful
|
|
579
|
-
// responses with recommendations go through
|
|
580
|
-
// setEntityRecommendationForLineIdsForCategorization instead (which
|
|
581
|
-
// also flips the lines to Completed).
|
|
582
|
-
markCategoryClassRecommendationsCompletedForCategorization: {
|
|
583
|
-
reducer(draft, action) {
|
|
584
|
-
const { selectedTab, transactionId, lineIds } = action.payload;
|
|
585
|
-
const transactionLocalData = recordGet(draft.transactionCategorizationView[selectedTab]
|
|
586
|
-
.transactionReviewLocalDataById, transactionId.id, undefined);
|
|
587
|
-
if (transactionLocalData != null) {
|
|
588
|
-
lineIds.forEach((lineId) => {
|
|
589
|
-
const lineItem = transactionLocalData.transactionReviewLocalData.lineItemById[lineId];
|
|
590
|
-
if (lineItem != null) {
|
|
591
|
-
transactionLocalData.transactionReviewLocalData.lineItemById[lineId] = {
|
|
592
|
-
...lineItem,
|
|
593
|
-
categoryClassRecommendationsFetchState: {
|
|
594
|
-
fetchState: 'Completed',
|
|
595
|
-
error: undefined,
|
|
596
|
-
},
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
});
|
|
600
|
-
}
|
|
601
|
-
},
|
|
602
|
-
prepare(selectedTab, transactionId, lineIds) {
|
|
603
|
-
return { payload: { selectedTab, transactionId, lineIds } };
|
|
604
|
-
},
|
|
605
|
-
},
|
|
606
|
-
// Failure arm. Flips covered lines to Error so the skeleton resolves
|
|
607
|
-
// and downstream UI can react to the failure. The transaction stays
|
|
608
|
-
// unchecked from the start-arm action.
|
|
609
|
-
markCategoryClassRecommendationsFailureForCategorization: {
|
|
610
|
-
reducer(draft, action) {
|
|
611
|
-
const { selectedTab, transactionId, lineIds, error } = action.payload;
|
|
612
|
-
const transactionLocalData = recordGet(draft.transactionCategorizationView[selectedTab]
|
|
613
|
-
.transactionReviewLocalDataById, transactionId.id, undefined);
|
|
614
|
-
if (transactionLocalData != null) {
|
|
615
|
-
lineIds.forEach((lineId) => {
|
|
616
|
-
const lineItem = transactionLocalData.transactionReviewLocalData.lineItemById[lineId];
|
|
617
|
-
if (lineItem != null) {
|
|
618
|
-
transactionLocalData.transactionReviewLocalData.lineItemById[lineId] = {
|
|
619
|
-
...lineItem,
|
|
620
|
-
categoryClassRecommendationsFetchState: {
|
|
621
|
-
fetchState: 'Error',
|
|
622
|
-
error,
|
|
623
|
-
},
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
});
|
|
627
|
-
}
|
|
628
|
-
},
|
|
629
|
-
prepare(selectedTab, transactionId, lineIds, error) {
|
|
630
|
-
return { payload: { selectedTab, transactionId, lineIds, error } };
|
|
631
|
-
},
|
|
632
|
-
},
|
|
633
527
|
fetchTransactionCategorizationSuccess(draft, action) {
|
|
634
528
|
const { selectedTab, refreshViewInBackground } = action.payload;
|
|
635
529
|
if (draft.transactionCategorizationView[selectedTab].fetchState ===
|
|
@@ -725,7 +619,9 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
725
619
|
.selectedCheckBoxTransactionIds;
|
|
726
620
|
if (newTransactionLocalData.customer != null &&
|
|
727
621
|
newTransactionLocalData.customer.id != null &&
|
|
728
|
-
|
|
622
|
+
checkIfAllLineItemsAreCategoryClassFilled(selectedTransaction, newTransactionLocalData, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled) === true && // check if all line items are filled
|
|
623
|
+
selectedCheckBoxTransactionIds.length < MAX_SELECTION_LIMIT &&
|
|
624
|
+
selectedCheckBoxTransactionIds.includes(transactionId) === false) {
|
|
729
625
|
draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds = [
|
|
730
626
|
...selectedCheckBoxTransactionIds,
|
|
731
627
|
transactionId,
|
|
@@ -799,7 +695,9 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
799
695
|
.selectedCheckBoxTransactionIds;
|
|
800
696
|
if (newTransactionLocalData.vendor != null &&
|
|
801
697
|
newTransactionLocalData.vendor.id != null &&
|
|
802
|
-
|
|
698
|
+
checkIfAllLineItemsAreCategoryClassFilled(selectedTransaction, newTransactionLocalData, uncategorizedAccounts, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled) === true && // check if all line items are filled
|
|
699
|
+
selectedCheckBoxTransactionIds.length < MAX_SELECTION_LIMIT &&
|
|
700
|
+
selectedCheckBoxTransactionIds.includes(transactionId) === false) {
|
|
803
701
|
draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds = [
|
|
804
702
|
...selectedCheckBoxTransactionIds,
|
|
805
703
|
transactionId,
|
|
@@ -1030,7 +928,18 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
1030
928
|
};
|
|
1031
929
|
},
|
|
1032
930
|
},
|
|
931
|
+
removeTransactionFromAllTabs: {
|
|
932
|
+
prepare(transactionId) {
|
|
933
|
+
return { payload: { transactionId } };
|
|
934
|
+
},
|
|
935
|
+
reducer(draft, action) {
|
|
936
|
+
const { transactionId } = action.payload;
|
|
937
|
+
for (const tab of TRANSACTIONS_TABS) {
|
|
938
|
+
removeTransactionFromTabView(draft.transactionCategorizationView[tab], transactionId);
|
|
939
|
+
}
|
|
940
|
+
},
|
|
941
|
+
},
|
|
1033
942
|
},
|
|
1034
943
|
});
|
|
1035
|
-
export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, updateParentTotalCountForTab, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization,
|
|
944
|
+
export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, updateParentTotalCountForTab, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, removeTransactionFromAllTabs, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, } = expenseAutomationTransactionsView.actions;
|
|
1036
945
|
export default expenseAutomationTransactionsView.reducer;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createSelector } from '@reduxjs/toolkit';
|
|
2
1
|
import omit from 'lodash/omit';
|
|
3
2
|
import { reduceAllFetchState, reduceAnyFetchState, } from '../../../commonStateTypes/reduceFetchState';
|
|
4
3
|
import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
|
|
@@ -7,33 +6,14 @@ import { getTransactionWithCOT } from '../../../entity/transaction/transactionHe
|
|
|
7
6
|
import { getSupportedTransactionsByIds } from '../../../entity/transaction/transactionSelector';
|
|
8
7
|
import { getAccountList, getNestedAccountListHierarchy, getUncategorizedAccounts, } from '../../accountList/accountListSelector';
|
|
9
8
|
import { getClassList, getNestedClassListHierarchy, } from '../../classList/classListSelector';
|
|
9
|
+
import { getResolvedTransferAccounts } from '../../createTransferEntry/createTransferEntrySelector';
|
|
10
10
|
import { getProjectList } from '../../projectList/projectListSelector';
|
|
11
11
|
import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
// in flight) recomputes only when local data changes — and the row-table
|
|
16
|
-
// can read a single map lookup per row instead of re-iterating the
|
|
17
|
-
// row's lineItemById in the render path.
|
|
18
|
-
export const getCategorizationInFlightRecommendationsByTransactionId = createSelector((state) => state.expenseAutomationTransactionsViewState
|
|
19
|
-
.selectedTransactionCategorizationTab, (state) => state.expenseAutomationTransactionsViewState
|
|
20
|
-
.transactionCategorizationView, (selectedTab, transactionCategorizationView) => {
|
|
21
|
-
const tab = transactionCategorizationView[selectedTab];
|
|
22
|
-
const result = {};
|
|
23
|
-
for (const [transactionId, data] of Object.entries(tab.transactionReviewLocalDataById)) {
|
|
24
|
-
const lineItems = Object.values(data.transactionReviewLocalData.lineItemById);
|
|
25
|
-
if (lineItems.some((lineItem) => lineItem.categoryClassRecommendationsFetchState?.fetchState ===
|
|
26
|
-
'In-Progress')) {
|
|
27
|
-
result[transactionId] = true;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return result;
|
|
31
|
-
});
|
|
32
|
-
// Page-level "any line on the active tab is in flight?" flag. Drives the
|
|
33
|
-
// list-page Save disable.
|
|
34
|
-
export const getCategorizationHasInFlightRecommendations = createSelector(getCategorizationInFlightRecommendationsByTransactionId, (inFlightByTransactionId) => Object.keys(inFlightByTransactionId).length > 0);
|
|
12
|
+
// Re-exported here for back-compat. New code should import from
|
|
13
|
+
// `view/createTransferEntry/createTransferEntrySelector`.
|
|
14
|
+
export { getLastTransferEntryReplacement } from '../../createTransferEntry/createTransferEntrySelector';
|
|
35
15
|
export function getExpenseAutomationTransactionView(state) {
|
|
36
|
-
const { accountState, classState, classListState, accountListState, expenseAutomationTransactionsViewState, expenseAutomationViewState, projectState, projectListState, transactionState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
|
|
16
|
+
const { accountState, classState, classListState, accountListState, createTransferEntryState, expenseAutomationTransactionsViewState, expenseAutomationViewState, projectState, projectListState, transactionState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
|
|
37
17
|
const { selectedTransactionCategorizationCompletedSubTab, selectedTransactionCategorizationTab, } = expenseAutomationTransactionsViewState;
|
|
38
18
|
const { uiState, refreshStatus, saveStatus, markAsNotMiscategorizedStatus, transactionIdsBySelectedPeriod, transactionReviewLocalDataById, selectedCheckBoxTransactionIds, transactionIdsWithUnsavedData, uploadReceiptStatusById, selectedTransactionId, selectedTransactionLineId, } = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTransactionCategorizationTab];
|
|
39
19
|
const uncategorizedIncomeExpense = getUncategorizedAccounts(accountState, accountListState, 'accountList');
|
|
@@ -127,8 +107,7 @@ export function getExpenseAutomationTransactionView(state) {
|
|
|
127
107
|
: [];
|
|
128
108
|
const transactionCompletionStatus = allSteps.find((step) => step.step === 'transaction_categorization')?.completionStatus;
|
|
129
109
|
const completionStatus = transactionCompletionStatus ?? 'incomplete';
|
|
130
|
-
const
|
|
131
|
-
const hasInFlightCategoryClassRecommendations = getCategorizationHasInFlightRecommendations(state);
|
|
110
|
+
const { transferAccountsList, creditCardTransferAccountsList } = getResolvedTransferAccounts(state);
|
|
132
111
|
return {
|
|
133
112
|
version: 0,
|
|
134
113
|
fetchState: fetchStatus.fetchState,
|
|
@@ -142,8 +121,6 @@ export function getExpenseAutomationTransactionView(state) {
|
|
|
142
121
|
classList: allClasses,
|
|
143
122
|
accountsHierarchyList,
|
|
144
123
|
classHierarchyList,
|
|
145
|
-
hasInFlightCategoryClassRecommendations,
|
|
146
|
-
inFlightCategoryClassRecommendationsByTransactionId,
|
|
147
124
|
isAccountingProjectsEnabled,
|
|
148
125
|
projectList,
|
|
149
126
|
transactionLocalData,
|
|
@@ -151,6 +128,7 @@ export function getExpenseAutomationTransactionView(state) {
|
|
|
151
128
|
refreshStatus,
|
|
152
129
|
saveStatus,
|
|
153
130
|
markAsNotMiscategorizedStatus,
|
|
131
|
+
createTransferEntryStatus: createTransferEntryState.createTransferEntryStatus,
|
|
154
132
|
completionStatus,
|
|
155
133
|
totalCountByTab,
|
|
156
134
|
parentTabTotalCountByTab,
|
|
@@ -158,6 +136,8 @@ export function getExpenseAutomationTransactionView(state) {
|
|
|
158
136
|
uploadReceiptStatusById,
|
|
159
137
|
selectedTransactionId,
|
|
160
138
|
selectedTransactionLineId,
|
|
139
|
+
creditCardTransferAccountsList,
|
|
140
|
+
transferAccounts: transferAccountsList,
|
|
161
141
|
selectedTransactionCategorizationCompletedSubTab,
|
|
162
142
|
};
|
|
163
143
|
}
|