@zeniai/client-epic-state 5.1.17 → 5.1.18-betaRD2
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/entity/customer/customerPayload.d.ts +7 -1
- package/lib/entity/customer/customerPayload.js +32 -9
- package/lib/entity/reimbursement/reimbursementPayload.js +2 -1
- package/lib/entity/transaction/payloadTypes/customerTransactionPayload.js +3 -1
- package/lib/entity/transaction/payloadTypes/transactionLinePayload.js +1 -1
- package/lib/entity/transaction/payloadTypes/vendorTransactionPayload.d.ts +1 -1
- package/lib/entity/transactionActivityLog/transactionActivityLogPayload.js +4 -1
- package/lib/esm/entity/customer/customerPayload.js +30 -8
- package/lib/esm/entity/reimbursement/reimbursementPayload.js +3 -2
- package/lib/esm/entity/transaction/payloadTypes/customerTransactionPayload.js +3 -1
- package/lib/esm/entity/transaction/payloadTypes/transactionLinePayload.js +2 -2
- package/lib/esm/entity/transactionActivityLog/transactionActivityLogPayload.js +5 -2
- package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +20 -4
- package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic.js +48 -4
- package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +92 -18
- package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +19 -3
- package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic.d.ts +2 -2
- package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic.js +47 -3
- package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +7 -0
- package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +4 -1
- package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +93 -19
- package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +16 -0
- package/package.json +1 -1
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { URLPayload } from '../../commonPayloadTypes/urlPayload';
|
|
2
2
|
import { Customer, CustomerBase } from './customerState';
|
|
3
3
|
export interface CustomerBasePayload {
|
|
4
|
-
customer_id: string;
|
|
4
|
+
customer_id: string | null;
|
|
5
5
|
customer_name: string;
|
|
6
6
|
logo?: URLPayload;
|
|
7
7
|
qbo_id?: string;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolves the effective customer identifier from a payload.
|
|
11
|
+
* For JE/Deposit transactions, customer_id is null at runtime — qbo_id is the
|
|
12
|
+
* actual identity.
|
|
13
|
+
*/
|
|
14
|
+
export declare const getCustomerPayloadIdentifier: (customer: CustomerBasePayload) => string | null;
|
|
9
15
|
export declare const toCustomerBase: (payload: CustomerBasePayload, parseOutParentName: boolean) => CustomerBase;
|
|
10
16
|
export declare const toCustomerBasePayload: (customer: CustomerBase | undefined, customerUpdates?: CustomerBase) => CustomerBasePayload;
|
|
11
17
|
export interface CustomerPayload extends CustomerBasePayload {
|
|
@@ -1,16 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toCustomer = exports.toCustomerBasePayload = exports.toCustomerBase = void 0;
|
|
3
|
+
exports.toCustomer = exports.toCustomerBasePayload = exports.toCustomerBase = exports.getCustomerPayloadIdentifier = void 0;
|
|
4
4
|
const urlPayload_1 = require("../../commonPayloadTypes/urlPayload");
|
|
5
5
|
const zeniDayJS_1 = require("../../zeniDayJS");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Resolves the effective customer identifier from a payload.
|
|
8
|
+
* For JE/Deposit transactions, customer_id is null at runtime — qbo_id is the
|
|
9
|
+
* actual identity.
|
|
10
|
+
*/
|
|
11
|
+
const getCustomerPayloadIdentifier = (customer) => {
|
|
12
|
+
const cid = customer.customer_id;
|
|
13
|
+
if (cid != null && cid !== '') {
|
|
14
|
+
return cid;
|
|
15
|
+
}
|
|
16
|
+
const qid = customer.qbo_id;
|
|
17
|
+
if (qid != null && qid !== '') {
|
|
18
|
+
return qid;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
};
|
|
22
|
+
exports.getCustomerPayloadIdentifier = getCustomerPayloadIdentifier;
|
|
23
|
+
const toCustomerBase = (payload, parseOutParentName) => {
|
|
24
|
+
const id = (0, exports.getCustomerPayloadIdentifier)(payload);
|
|
25
|
+
if (id == null) {
|
|
26
|
+
throw new Error(`toCustomerBase: no identifier (customer_id/qbo_id) found for customer "${payload.customer_name}".`);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
id,
|
|
30
|
+
name: parseOutParentName
|
|
31
|
+
? removeParentName(payload.customer_name)
|
|
32
|
+
: payload.customer_name,
|
|
33
|
+
qboId: payload.qbo_id,
|
|
34
|
+
logo: payload.logo != null ? (0, urlPayload_1.toURL)(payload.logo) : undefined,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
14
37
|
exports.toCustomerBase = toCustomerBase;
|
|
15
38
|
const toCustomerBasePayload = (customer, customerUpdates) => {
|
|
16
39
|
if (customerUpdates != null && customerUpdates.id != null) {
|
|
@@ -184,7 +184,8 @@ const toLineDetail = (payload) => ({
|
|
|
184
184
|
account: payload.account?.account_id != null
|
|
185
185
|
? (0, accountPayload_1.mapAccountBasePayloadToAccountBase)(payload.account)
|
|
186
186
|
: undefined,
|
|
187
|
-
customer: payload.customer
|
|
187
|
+
customer: payload.customer != null &&
|
|
188
|
+
(0, customerPayload_1.getCustomerPayloadIdentifier)(payload.customer) != null
|
|
188
189
|
? (0, customerPayload_1.toCustomerBase)(payload.customer, false)
|
|
189
190
|
: undefined,
|
|
190
191
|
});
|
|
@@ -12,7 +12,9 @@ const toCustomerTransaction = (payload) => {
|
|
|
12
12
|
const transaction = (0, transactionPayload_1.toTransaction)(payload);
|
|
13
13
|
return {
|
|
14
14
|
...transaction,
|
|
15
|
-
customer: payload.customer_id
|
|
15
|
+
customer: Boolean(payload.customer_id) === true
|
|
16
|
+
? (0, customerPayload_1.toCustomerBase)(payload, false)
|
|
17
|
+
: undefined,
|
|
16
18
|
documentId: payload.document_id,
|
|
17
19
|
paymentType: (0, paymentType_1.toPaymentTypeStrict)(payload.payment_type),
|
|
18
20
|
paymentTypeName: payload.payment_type_name,
|
|
@@ -39,7 +39,7 @@ const toTransactionWithAccountAndClassLine = (payload, currency, type = 'transac
|
|
|
39
39
|
class: (0, classPayload_1.mapClassBasePayloadToClassBase)(payload.line_detail.class ?? {}), // Note: why bother about empty {} object ? because sometimes backend sends empty objects, sometimes it sends undefined or null
|
|
40
40
|
account: (0, accountPayload_1.mapAccountBasePayloadToAccountBase)(payload.line_detail.account),
|
|
41
41
|
customer: payload.line_detail.customer != null &&
|
|
42
|
-
payload.line_detail.customer
|
|
42
|
+
(0, customerPayload_1.getCustomerPayloadIdentifier)(payload.line_detail.customer) != null
|
|
43
43
|
? (0, customerPayload_1.toCustomerBase)(payload.line_detail.customer, false)
|
|
44
44
|
: undefined,
|
|
45
45
|
vendor: payload.line_detail.vendor != null &&
|
|
@@ -12,7 +12,7 @@ export interface VendorTransactionPayload extends VendorBasePayload, Omit<Transa
|
|
|
12
12
|
payment_type_name?: string;
|
|
13
13
|
}
|
|
14
14
|
export interface VendorTransactionWithCustomerPayload extends VendorTransactionPayload {
|
|
15
|
-
customer_id: string;
|
|
15
|
+
customer_id: string | null;
|
|
16
16
|
customer_name: string;
|
|
17
17
|
}
|
|
18
18
|
export declare const isVendorTransactionPayload: (payload: TransactionPayload) => boolean;
|
|
@@ -18,7 +18,10 @@ const toTransactionActivityLogLine = (line) => {
|
|
|
18
18
|
class: line.accounting_class
|
|
19
19
|
? (0, classPayload_1.mapClassBasePayloadToClassBase)(line.accounting_class)
|
|
20
20
|
: undefined,
|
|
21
|
-
customer: line.customer
|
|
21
|
+
customer: line.customer != null &&
|
|
22
|
+
(0, customerPayload_1.getCustomerPayloadIdentifier)(line.customer) != null
|
|
23
|
+
? (0, customerPayload_1.toCustomerBase)(line.customer, false)
|
|
24
|
+
: undefined,
|
|
22
25
|
vendor: line.vendor != null && (0, vendorPayload_1.getVendorPayloadIdentifier)(line.vendor) != null
|
|
23
26
|
? (0, vendorPayload_1.mapVendorBasePayloadToVendorBase)(line.vendor)
|
|
24
27
|
: undefined,
|
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
import { toURL } from '../../commonPayloadTypes/urlPayload';
|
|
2
2
|
import { date } from '../../zeniDayJS';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the effective customer identifier from a payload.
|
|
5
|
+
* For JE/Deposit transactions, customer_id is null at runtime — qbo_id is the
|
|
6
|
+
* actual identity.
|
|
7
|
+
*/
|
|
8
|
+
export const getCustomerPayloadIdentifier = (customer) => {
|
|
9
|
+
const cid = customer.customer_id;
|
|
10
|
+
if (cid != null && cid !== '') {
|
|
11
|
+
return cid;
|
|
12
|
+
}
|
|
13
|
+
const qid = customer.qbo_id;
|
|
14
|
+
if (qid != null && qid !== '') {
|
|
15
|
+
return qid;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
};
|
|
19
|
+
export const toCustomerBase = (payload, parseOutParentName) => {
|
|
20
|
+
const id = getCustomerPayloadIdentifier(payload);
|
|
21
|
+
if (id == null) {
|
|
22
|
+
throw new Error(`toCustomerBase: no identifier (customer_id/qbo_id) found for customer "${payload.customer_name}".`);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
id,
|
|
26
|
+
name: parseOutParentName
|
|
27
|
+
? removeParentName(payload.customer_name)
|
|
28
|
+
: payload.customer_name,
|
|
29
|
+
qboId: payload.qbo_id,
|
|
30
|
+
logo: payload.logo != null ? toURL(payload.logo) : undefined,
|
|
31
|
+
};
|
|
32
|
+
};
|
|
11
33
|
export const toCustomerBasePayload = (customer, customerUpdates) => {
|
|
12
34
|
if (customerUpdates != null && customerUpdates.id != null) {
|
|
13
35
|
return {
|
|
@@ -5,7 +5,7 @@ import { date, dateNow } from '../../zeniDayJS';
|
|
|
5
5
|
import { mapAccountBasePayloadToAccountBase, toAccountPayload, } from '../account/accountPayload';
|
|
6
6
|
import { toBillPaymentMethod, toBillPaymentStatus, toBillStage, toBillStatus, toBulkProcessingDetail, toDeletionInfo, } from '../billPay/billTransaction/billTransactionPayload';
|
|
7
7
|
import { mapClassBasePayloadToClassBase, toClassBasePayload, } from '../class/classPayload';
|
|
8
|
-
import { toCustomerBase, toCustomerBasePayload, } from '../customer/customerPayload';
|
|
8
|
+
import { getCustomerPayloadIdentifier, toCustomerBase, toCustomerBasePayload, } from '../customer/customerPayload';
|
|
9
9
|
import { mapMerchantBasePayloadToMerchantBase, toMerchantBasePayload, } from '../merchant/merchantPayload';
|
|
10
10
|
import { toAttachment, } from '../transaction/payloadTypes/attachmentPayload';
|
|
11
11
|
const ALL_REIMBURSEMENT_TRANSACTION_TYPE = ['reimbursement'];
|
|
@@ -178,7 +178,8 @@ const toLineDetail = (payload) => ({
|
|
|
178
178
|
account: payload.account?.account_id != null
|
|
179
179
|
? mapAccountBasePayloadToAccountBase(payload.account)
|
|
180
180
|
: undefined,
|
|
181
|
-
customer: payload.customer
|
|
181
|
+
customer: payload.customer != null &&
|
|
182
|
+
getCustomerPayloadIdentifier(payload.customer) != null
|
|
182
183
|
? toCustomerBase(payload.customer, false)
|
|
183
184
|
: undefined,
|
|
184
185
|
});
|
|
@@ -8,7 +8,9 @@ export const toCustomerTransaction = (payload) => {
|
|
|
8
8
|
const transaction = toTransaction(payload);
|
|
9
9
|
return {
|
|
10
10
|
...transaction,
|
|
11
|
-
customer: payload.customer_id
|
|
11
|
+
customer: Boolean(payload.customer_id) === true
|
|
12
|
+
? toCustomerBase(payload, false)
|
|
13
|
+
: undefined,
|
|
12
14
|
documentId: payload.document_id,
|
|
13
15
|
paymentType: toPaymentTypeStrict(payload.payment_type),
|
|
14
16
|
paymentTypeName: payload.payment_type_name,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { toAmountWC } from '../../../commonStateTypes/amount';
|
|
2
2
|
import { mapAccountBasePayloadToAccountBase, toAccountPayload, } from '../../account/accountPayload';
|
|
3
3
|
import { mapClassBasePayloadToClassBase, toClassBasePayload, } from '../../class/classPayload';
|
|
4
|
-
import { toCustomerBase, toCustomerBasePayload, } from '../../customer/customerPayload';
|
|
4
|
+
import { getCustomerPayloadIdentifier, toCustomerBase, toCustomerBasePayload, } from '../../customer/customerPayload';
|
|
5
5
|
import { mapProjectBasePayloadToProjectBase, toProjectBasePayload, } from '../../project/projectPayload';
|
|
6
6
|
import { getVendorPayloadIdentifier, mapVendorBasePayloadToVendorBase, toVendorBasePayload, } from '../../vendor/vendorPayload';
|
|
7
7
|
import { toCategorizationStatusType, toPlatformLineDetailType, } from '../stateTypes/transactionLine';
|
|
@@ -35,7 +35,7 @@ export const toTransactionWithAccountAndClassLine = (payload, currency, type = '
|
|
|
35
35
|
class: mapClassBasePayloadToClassBase(payload.line_detail.class ?? {}), // Note: why bother about empty {} object ? because sometimes backend sends empty objects, sometimes it sends undefined or null
|
|
36
36
|
account: mapAccountBasePayloadToAccountBase(payload.line_detail.account),
|
|
37
37
|
customer: payload.line_detail.customer != null &&
|
|
38
|
-
payload.line_detail.customer
|
|
38
|
+
getCustomerPayloadIdentifier(payload.line_detail.customer) != null
|
|
39
39
|
? toCustomerBase(payload.line_detail.customer, false)
|
|
40
40
|
: undefined,
|
|
41
41
|
vendor: payload.line_detail.vendor != null &&
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { date } from '../../zeniDayJS';
|
|
2
2
|
import { mapAccountBasePayloadToAccountBase, } from '../account/accountPayload';
|
|
3
3
|
import { mapClassBasePayloadToClassBase, } from '../class/classPayload';
|
|
4
|
-
import { toCustomerBase } from '../customer/customerPayload';
|
|
4
|
+
import { getCustomerPayloadIdentifier, toCustomerBase } from '../customer/customerPayload';
|
|
5
5
|
import { toAISummary, } from '../transaction/payloadTypes/transactionLinePayload';
|
|
6
6
|
import { getVendorPayloadIdentifier, mapVendorBasePayloadToVendorBase, } from '../vendor/vendorPayload';
|
|
7
7
|
import { toActivityLogEventType, } from './transactionActivityLogState';
|
|
@@ -14,7 +14,10 @@ const toTransactionActivityLogLine = (line) => {
|
|
|
14
14
|
class: line.accounting_class
|
|
15
15
|
? mapClassBasePayloadToClassBase(line.accounting_class)
|
|
16
16
|
: undefined,
|
|
17
|
-
customer: line.customer
|
|
17
|
+
customer: line.customer != null &&
|
|
18
|
+
getCustomerPayloadIdentifier(line.customer) != null
|
|
19
|
+
? toCustomerBase(line.customer, false)
|
|
20
|
+
: undefined,
|
|
18
21
|
vendor: line.vendor != null && getVendorPayloadIdentifier(line.vendor) != null
|
|
19
22
|
? mapVendorBasePayloadToVendorBase(line.vendor)
|
|
20
23
|
: undefined,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { from, of } from 'rxjs';
|
|
2
|
-
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
2
|
+
import { catchError, filter, groupBy, mergeMap, switchMap } from 'rxjs/operators';
|
|
3
3
|
import { toString } from '../../../../commonStateTypes/timePeriod';
|
|
4
4
|
import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
|
|
5
5
|
import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
|
|
@@ -8,7 +8,10 @@ import { getLineItemsByTransactionsIdsFromResponse } from '../../helpers/transac
|
|
|
8
8
|
import { fetchTransactionCategorization, fetchTransactionCategorizationFailure, fetchTransactionCategorizationSuccess, initializeTransactionCategorizationViewLocalData, resetOtherTabsFetchState, updateParentTotalCountForTab, updateTotalCountForTransactionCategorization, updateTransactionCategorizationUIState, } from '../../reducers/transactionsViewReducer';
|
|
9
9
|
import { DEFAULT_COMPLETED_SUB_TAB } from '../../types/completedSubTab';
|
|
10
10
|
import { TRANSACTIONS_TABS, toTransactionsSortKey, } from '../../types/transactionsViewState';
|
|
11
|
-
export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchTransactionCategorization.match),
|
|
11
|
+
export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchTransactionCategorization.match),
|
|
12
|
+
// Per-tab switchMap: cancels in-flight requests for the same tab on a new
|
|
13
|
+
// request, but allows both tabs to fetch concurrently (e.g. dual search).
|
|
14
|
+
groupBy((action) => action.payload.selectedTab), mergeMap((group$) => group$.pipe(switchMap((action) => {
|
|
12
15
|
const { expenseAutomationViewState: { selectedPeriodByTenantId }, expenseAutomationTransactionsViewState, } = state$.value;
|
|
13
16
|
const { period, keepExistingListItems, selectedTab, refreshViewInBackground, } = action.payload;
|
|
14
17
|
const uiState = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTab].uiState;
|
|
@@ -24,6 +27,10 @@ export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) =>
|
|
|
24
27
|
page_token: pageToken,
|
|
25
28
|
page_size: 25,
|
|
26
29
|
search_text: uiState.searchString,
|
|
30
|
+
// Always scope to the active tab's auto_categorized status;
|
|
31
|
+
// cross-status search (omit or true) is reserved for Receipts
|
|
32
|
+
// manual-match.
|
|
33
|
+
search_all_statuses: false,
|
|
27
34
|
sub_tab: selectedTab === 'autoCategorized'
|
|
28
35
|
? expenseAutomationTransactionsViewState.selectedTransactionCategorizationCompletedSubTab
|
|
29
36
|
: DEFAULT_COMPLETED_SUB_TAB,
|
|
@@ -36,10 +43,19 @@ export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) =>
|
|
|
36
43
|
updateActions.push(updateTransactions(response.data.transactions, (value) => value.transaction_id, 'merge'));
|
|
37
44
|
const transactionIds = response.data.transactions.map((transaction) => transaction.transaction_id);
|
|
38
45
|
const lineItemsByTransactionIds = getLineItemsByTransactionsIdsFromResponse(response.data.transactions);
|
|
46
|
+
// Only propagate the fields the response provides (pageToken).
|
|
47
|
+
// Do NOT spread the captured uiState — that would re-apply
|
|
48
|
+
// searchString/sortKey/etc. from request-start and overwrite
|
|
49
|
+
// any state changes made during the in-flight request (e.g.
|
|
50
|
+
// user clearing search triggers restoreFullDatasetSnapshot
|
|
51
|
+
// before this response lands).
|
|
52
|
+
// pageToken is also set by saveTransactionCategorizationLocalData
|
|
53
|
+
// via initializeTransactionCategorizationViewLocalDataEpic, so
|
|
54
|
+
// null values are handled correctly there regardless of the
|
|
55
|
+
// != null guard in updateTransactionCategorizationUIState.
|
|
39
56
|
updateActions.push(updateTransactionCategorizationUIState({
|
|
40
57
|
selectedTab,
|
|
41
58
|
uiState: {
|
|
42
|
-
...uiState,
|
|
43
59
|
pageToken: response.data.next_page_token ?? null,
|
|
44
60
|
},
|
|
45
61
|
}));
|
|
@@ -79,4 +95,4 @@ export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) =>
|
|
|
79
95
|
status: createZeniAPIStatus('Unexpected Error', 'fetch Transaction Categorization REST API call errored out' +
|
|
80
96
|
JSON.stringify(error)),
|
|
81
97
|
}))));
|
|
82
|
-
}));
|
|
98
|
+
}))));
|
|
@@ -6,7 +6,8 @@ import { fetchAccountList } from '../../../accountList/accountListReducer';
|
|
|
6
6
|
import { fetchClassList } from '../../../classList/classListReducer';
|
|
7
7
|
import { fetchOwnerList } from '../../../ownerList/ownerListReducer';
|
|
8
8
|
import { fetchProjectList } from '../../../projectList/projectListReducer';
|
|
9
|
-
import { fetchTransactionCategorization, fetchTransactionCategorizationView, } from '../../reducers/transactionsViewReducer';
|
|
9
|
+
import { fetchTransactionCategorization, fetchTransactionCategorizationView, restoreFullDatasetSnapshot, } from '../../reducers/transactionsViewReducer';
|
|
10
|
+
import { TRANSACTIONS_TABS, } from '../../types/transactionsViewState';
|
|
10
11
|
export const fetchTransactionCategorizationViewEpic = (actions$, state$) => actions$.pipe(filter(fetchTransactionCategorizationView.match), mergeMap((action) => {
|
|
11
12
|
const { selectedTab, cacheOverride, keepExistingListItems, pageToken, period, refreshViewInBackground, searchString, resetListItems, isUncategorizedExpenseCategoryEnabled, } = action.payload;
|
|
12
13
|
const updateActions = [];
|
|
@@ -16,8 +17,14 @@ export const fetchTransactionCategorizationViewEpic = (actions$, state$) => acti
|
|
|
16
17
|
month: period.start.month,
|
|
17
18
|
year: period.start.year,
|
|
18
19
|
};
|
|
19
|
-
const
|
|
20
|
-
|
|
20
|
+
const periodId = toMonthYearPeriodId(monthYearPeriod);
|
|
21
|
+
const transactionIds = transactionIdsBySelectedPeriod[periodId] ?? [];
|
|
22
|
+
const hasUsableSnapshot = (tabKey) => {
|
|
23
|
+
const snapshot = expenseAutomationTransactionsViewState.transactionCategorizationView[tabKey].fullDatasetSnapshot;
|
|
24
|
+
return (snapshot != null &&
|
|
25
|
+
snapshot.periodId === periodId &&
|
|
26
|
+
snapshot.transactionIds.length > 0);
|
|
27
|
+
};
|
|
21
28
|
const accountList = state$.value.accountListState.byReportId['accountList'];
|
|
22
29
|
if (accountList.hasValidState() === false &&
|
|
23
30
|
accountList.fetchState === 'Not-Started') {
|
|
@@ -39,10 +46,47 @@ export const fetchTransactionCategorizationViewEpic = (actions$, state$) => acti
|
|
|
39
46
|
vendorOwnerList.fetchState === 'Not-Started') {
|
|
40
47
|
updateActions.push(fetchOwnerList());
|
|
41
48
|
}
|
|
42
|
-
|
|
49
|
+
// When clearing search (searchString === ''), restore from the pre-search
|
|
50
|
+
// snapshot if one exists — avoids a round-trip since all data is already
|
|
51
|
+
// in the entity store. Fall back to a normal fetch if no snapshot (the
|
|
52
|
+
// tab was never loaded before the search was applied).
|
|
53
|
+
if (searchString === '' && hasUsableSnapshot(selectedTab)) {
|
|
54
|
+
updateActions.push(restoreFullDatasetSnapshot({ selectedTab, period }));
|
|
55
|
+
}
|
|
56
|
+
else if (cacheOverride === true ||
|
|
43
57
|
fetchState === 'Not-Started' ||
|
|
44
58
|
transactionIds.length === 0) {
|
|
45
59
|
updateActions.push(fetchTransactionCategorization(selectedTab, period, cacheOverride, keepExistingListItems, searchString, pageToken, refreshViewInBackground, resetListItems, isUncategorizedExpenseCategoryEnabled));
|
|
46
60
|
}
|
|
61
|
+
// On the initial load of a tab (searchString undefined = non-search fetch),
|
|
62
|
+
// preload every other tab that hasn't been fetched yet for this period.
|
|
63
|
+
// This makes switching to the other tab instant instead of triggering a
|
|
64
|
+
// fresh fetch on click. refreshViewInBackground=true keeps it silent.
|
|
65
|
+
if (searchString === undefined) {
|
|
66
|
+
TRANSACTIONS_TABS.filter((tab) => tab !== selectedTab).forEach((otherTab) => {
|
|
67
|
+
const otherTabFetchState = expenseAutomationTransactionsViewState.transactionCategorizationView[otherTab].fetchState;
|
|
68
|
+
if (otherTabFetchState === 'Not-Started') {
|
|
69
|
+
updateActions.push(fetchTransactionCategorization(otherTab, period, false, false, undefined, undefined, true, undefined, isUncategorizedExpenseCategoryEnabled));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// When search changes (searchString defined, including empty string to
|
|
74
|
+
// clear), also update every other tab so all tabs always reflect the same
|
|
75
|
+
// search query. Skip if the other tab already has this search string to
|
|
76
|
+
// avoid redundant work on noop updates. On clear, restore from snapshot
|
|
77
|
+
// if available; otherwise issue a background fetch.
|
|
78
|
+
if (searchString !== undefined) {
|
|
79
|
+
TRANSACTIONS_TABS.filter((tab) => tab !== selectedTab).forEach((otherTab) => {
|
|
80
|
+
const otherTabSearchString = expenseAutomationTransactionsViewState.transactionCategorizationView[otherTab].uiState.searchString;
|
|
81
|
+
if (searchString !== otherTabSearchString) {
|
|
82
|
+
if (searchString === '' && hasUsableSnapshot(otherTab)) {
|
|
83
|
+
updateActions.push(restoreFullDatasetSnapshot({ selectedTab: otherTab, period }));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
updateActions.push(fetchTransactionCategorization(otherTab, period, true, keepExistingListItems, searchString, undefined, true, true, isUncategorizedExpenseCategoryEnabled));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
47
91
|
return from(updateActions);
|
|
48
92
|
}));
|
|
@@ -38,6 +38,7 @@ export const initialTransactionTabViewState = {
|
|
|
38
38
|
hasValidState() {
|
|
39
39
|
return this.fetchState == 'Completed';
|
|
40
40
|
},
|
|
41
|
+
fullDatasetSnapshot: undefined,
|
|
41
42
|
transactionReviewLocalDataById: {},
|
|
42
43
|
transactionIdsBySelectedPeriod: {},
|
|
43
44
|
selectedCheckBoxTransactionIds: [],
|
|
@@ -156,24 +157,52 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
156
157
|
}
|
|
157
158
|
draft.transactionCategorizationView[selectedTab].uiState.pageToken =
|
|
158
159
|
pageToken ?? null;
|
|
159
|
-
|
|
160
|
-
|
|
160
|
+
// Only overwrite when explicitly set; undefined means a non-search fetch
|
|
161
|
+
// (tab switch, period change) should preserve the existing search string.
|
|
162
|
+
if (searchString !== undefined) {
|
|
163
|
+
draft.transactionCategorizationView[selectedTab].uiState.searchString =
|
|
164
|
+
searchString;
|
|
165
|
+
}
|
|
161
166
|
draft.transactionCategorizationView[selectedTab].error = undefined;
|
|
167
|
+
const monthYearPeriod = {
|
|
168
|
+
month: period.start.month,
|
|
169
|
+
year: period.start.year,
|
|
170
|
+
};
|
|
171
|
+
const periodId = toMonthYearPeriodId(monthYearPeriod);
|
|
172
|
+
const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
|
|
173
|
+
.transactionIdsBySelectedPeriod[periodId] ?? [];
|
|
174
|
+
// Snapshot before any list mutation so clearing the search restores
|
|
175
|
+
// instantly without an API round-trip — for BOTH active and synced tabs.
|
|
176
|
+
// Active-tab search dispatches resetListItems=false, so this must live
|
|
177
|
+
// outside the resetListItems block. fullDatasetSnapshot == null prevents
|
|
178
|
+
// overwriting the original snapshot on search refinement ('ama'→'amazon').
|
|
179
|
+
if (searchString !== undefined &&
|
|
180
|
+
searchString !== '' &&
|
|
181
|
+
transactionIdsBySelectedPeriod.length > 0 &&
|
|
182
|
+
draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot ==
|
|
183
|
+
null) {
|
|
184
|
+
draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
|
|
185
|
+
{
|
|
186
|
+
parentTotalCount: draft.parentTotalCountByTab[selectedTab][periodId],
|
|
187
|
+
periodId,
|
|
188
|
+
totalCount: draft.transactionCategorizationView[selectedTab].uiState
|
|
189
|
+
.totalCount[periodId],
|
|
190
|
+
transactionIds: [...transactionIdsBySelectedPeriod],
|
|
191
|
+
transactionReviewLocalDataById: {
|
|
192
|
+
...draft.transactionCategorizationView[selectedTab]
|
|
193
|
+
.transactionReviewLocalDataById,
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
162
197
|
// Reset list when we change the sort order and fetch list again
|
|
163
198
|
if (resetListItems === true) {
|
|
164
|
-
const monthYearPeriod = {
|
|
165
|
-
month: period.start.month,
|
|
166
|
-
year: period.start.year,
|
|
167
|
-
};
|
|
168
|
-
const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
|
|
169
|
-
.transactionIdsBySelectedPeriod[toMonthYearPeriodId(monthYearPeriod)];
|
|
170
199
|
transactionIdsBySelectedPeriod.forEach((transactionId) => {
|
|
171
200
|
delete draft.transactionCategorizationView[selectedTab]
|
|
172
201
|
.transactionReviewLocalDataById[transactionId];
|
|
173
202
|
});
|
|
174
203
|
draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds =
|
|
175
204
|
draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds.filter((selectedCheckBoxTransactionId) => transactionIdsBySelectedPeriod.includes(selectedCheckBoxTransactionId));
|
|
176
|
-
draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[
|
|
205
|
+
draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[periodId] = [];
|
|
177
206
|
draft.transactionCategorizationView[selectedTab].refreshStatus = {
|
|
178
207
|
fetchState: 'Not-Started',
|
|
179
208
|
error: undefined,
|
|
@@ -305,10 +334,12 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
305
334
|
}
|
|
306
335
|
},
|
|
307
336
|
updateTransactionFilters(draft, action) {
|
|
308
|
-
const {
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
|
|
337
|
+
const { filters } = action.payload;
|
|
338
|
+
// Filters are tab-agnostic (payee/category/class/amount), so apply to
|
|
339
|
+
// all tabs so switching tabs after filtering shows consistent results.
|
|
340
|
+
TRANSACTIONS_TABS.forEach((tab) => {
|
|
341
|
+
draft.transactionCategorizationView[tab].filters = filters;
|
|
342
|
+
});
|
|
312
343
|
},
|
|
313
344
|
saveTransactionCategorization: {
|
|
314
345
|
prepare(selectedTab, transactionIds, cotTrackingByTransactionId, isUncategorizedExpenseCategoryEnabled) {
|
|
@@ -660,19 +691,62 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
660
691
|
resetOtherTabsFetchState(draft, action) {
|
|
661
692
|
const { tabs, refreshViewInBackground } = action.payload;
|
|
662
693
|
tabs.forEach((tab) => {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
694
|
+
const tabFetchState = draft.transactionCategorizationView[tab].fetchState;
|
|
695
|
+
if (refreshViewInBackground !== true) {
|
|
696
|
+
// Foreground fetch completed: mark other tabs as Completed.
|
|
666
697
|
draft.transactionCategorizationView[tab].fetchState = 'Completed';
|
|
667
698
|
draft.transactionCategorizationView[tab].error = undefined;
|
|
668
699
|
}
|
|
669
|
-
else {
|
|
700
|
+
else if (tabFetchState !== 'In-Progress') {
|
|
701
|
+
// Background fetch completed: reset the other tab's refreshStatus,
|
|
702
|
+
// but only when it is not mid-load. During a dual-search the active
|
|
703
|
+
// tab has its own foreground fetch in flight — clobbering its
|
|
704
|
+
// fetchState here would drop the skeleton and show an empty list.
|
|
670
705
|
draft.transactionCategorizationView[tab].refreshStatus = {
|
|
671
706
|
fetchState: 'Not-Started',
|
|
672
707
|
error: undefined,
|
|
673
708
|
};
|
|
674
709
|
}
|
|
710
|
+
// If tabFetchState === 'In-Progress' and refreshViewInBackground === true,
|
|
711
|
+
// leave the other tab's state untouched — it has its own active fetch.
|
|
712
|
+
});
|
|
713
|
+
},
|
|
714
|
+
restoreFullDatasetSnapshot(draft, action) {
|
|
715
|
+
const { selectedTab, period } = action.payload;
|
|
716
|
+
const periodId = toMonthYearPeriodId({
|
|
717
|
+
month: period.start.month,
|
|
718
|
+
year: period.start.year,
|
|
719
|
+
});
|
|
720
|
+
const snapshot = draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot;
|
|
721
|
+
if (snapshot == null || snapshot.periodId !== periodId) {
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
draft.transactionCategorizationView[selectedTab]
|
|
725
|
+
.transactionIdsBySelectedPeriod[periodId] = snapshot.transactionIds;
|
|
726
|
+
// Merge back per-transaction local data; skip entries that were updated
|
|
727
|
+
// during the search (e.g. a save that happened while filtered results
|
|
728
|
+
// were showing) so those saves are not overwritten.
|
|
729
|
+
Object.entries(snapshot.transactionReviewLocalDataById).forEach(([transactionId, localData]) => {
|
|
730
|
+
if (draft.transactionCategorizationView[selectedTab]
|
|
731
|
+
.transactionReviewLocalDataById[transactionId] == null) {
|
|
732
|
+
draft.transactionCategorizationView[selectedTab]
|
|
733
|
+
.transactionReviewLocalDataById[transactionId] = localData;
|
|
734
|
+
}
|
|
675
735
|
});
|
|
736
|
+
if (snapshot.totalCount != null) {
|
|
737
|
+
draft.transactionCategorizationView[selectedTab].uiState.totalCount[periodId] = snapshot.totalCount;
|
|
738
|
+
}
|
|
739
|
+
if (snapshot.parentTotalCount != null) {
|
|
740
|
+
draft.parentTotalCountByTab[selectedTab][periodId] =
|
|
741
|
+
snapshot.parentTotalCount;
|
|
742
|
+
}
|
|
743
|
+
draft.transactionCategorizationView[selectedTab].uiState.searchString =
|
|
744
|
+
'';
|
|
745
|
+
draft.transactionCategorizationView[selectedTab].uiState.pageToken = null;
|
|
746
|
+
draft.transactionCategorizationView[selectedTab].fetchState = 'Completed';
|
|
747
|
+
draft.transactionCategorizationView[selectedTab].error = undefined;
|
|
748
|
+
draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
|
|
749
|
+
undefined;
|
|
676
750
|
},
|
|
677
751
|
fetchTransactionCategorizationFailure(draft, action) {
|
|
678
752
|
const { selectedTab, selectedPeriod } = action.payload;
|
|
@@ -1054,5 +1128,5 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
1054
1128
|
},
|
|
1055
1129
|
},
|
|
1056
1130
|
});
|
|
1057
|
-
export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, updateTransactionFilters, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, updateParentTotalCountForTab, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization, markCategoryClassRecommendationsInProgressForCategorization, markCategoryClassRecommendationsCompletedForCategorization, markCategoryClassRecommendationsFailureForCategorization, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, removeTransactionFromAllTabs, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, } = expenseAutomationTransactionsView.actions;
|
|
1131
|
+
export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, updateTransactionFilters, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, updateParentTotalCountForTab, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, restoreFullDatasetSnapshot, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization, markCategoryClassRecommendationsInProgressForCategorization, markCategoryClassRecommendationsCompletedForCategorization, markCategoryClassRecommendationsFailureForCategorization, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, removeTransactionFromAllTabs, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, } = expenseAutomationTransactionsView.actions;
|
|
1058
1132
|
export default expenseAutomationTransactionsView.reducer;
|
|
@@ -11,7 +11,10 @@ const transactionCategorizationLocalDataHelper_1 = require("../../helpers/transa
|
|
|
11
11
|
const transactionsViewReducer_1 = require("../../reducers/transactionsViewReducer");
|
|
12
12
|
const completedSubTab_1 = require("../../types/completedSubTab");
|
|
13
13
|
const transactionsViewState_1 = require("../../types/transactionsViewState");
|
|
14
|
-
const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(transactionsViewReducer_1.fetchTransactionCategorization.match),
|
|
14
|
+
const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(transactionsViewReducer_1.fetchTransactionCategorization.match),
|
|
15
|
+
// Per-tab switchMap: cancels in-flight requests for the same tab on a new
|
|
16
|
+
// request, but allows both tabs to fetch concurrently (e.g. dual search).
|
|
17
|
+
(0, operators_1.groupBy)((action) => action.payload.selectedTab), (0, operators_1.mergeMap)((group$) => group$.pipe((0, operators_1.switchMap)((action) => {
|
|
15
18
|
const { expenseAutomationViewState: { selectedPeriodByTenantId }, expenseAutomationTransactionsViewState, } = state$.value;
|
|
16
19
|
const { period, keepExistingListItems, selectedTab, refreshViewInBackground, } = action.payload;
|
|
17
20
|
const uiState = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTab].uiState;
|
|
@@ -27,6 +30,10 @@ const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => action
|
|
|
27
30
|
page_token: pageToken,
|
|
28
31
|
page_size: 25,
|
|
29
32
|
search_text: uiState.searchString,
|
|
33
|
+
// Always scope to the active tab's auto_categorized status;
|
|
34
|
+
// cross-status search (omit or true) is reserved for Receipts
|
|
35
|
+
// manual-match.
|
|
36
|
+
search_all_statuses: false,
|
|
30
37
|
sub_tab: selectedTab === 'autoCategorized'
|
|
31
38
|
? expenseAutomationTransactionsViewState.selectedTransactionCategorizationCompletedSubTab
|
|
32
39
|
: completedSubTab_1.DEFAULT_COMPLETED_SUB_TAB,
|
|
@@ -39,10 +46,19 @@ const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => action
|
|
|
39
46
|
updateActions.push((0, transactionReducer_1.updateTransactions)(response.data.transactions, (value) => value.transaction_id, 'merge'));
|
|
40
47
|
const transactionIds = response.data.transactions.map((transaction) => transaction.transaction_id);
|
|
41
48
|
const lineItemsByTransactionIds = (0, transactionCategorizationLocalDataHelper_1.getLineItemsByTransactionsIdsFromResponse)(response.data.transactions);
|
|
49
|
+
// Only propagate the fields the response provides (pageToken).
|
|
50
|
+
// Do NOT spread the captured uiState — that would re-apply
|
|
51
|
+
// searchString/sortKey/etc. from request-start and overwrite
|
|
52
|
+
// any state changes made during the in-flight request (e.g.
|
|
53
|
+
// user clearing search triggers restoreFullDatasetSnapshot
|
|
54
|
+
// before this response lands).
|
|
55
|
+
// pageToken is also set by saveTransactionCategorizationLocalData
|
|
56
|
+
// via initializeTransactionCategorizationViewLocalDataEpic, so
|
|
57
|
+
// null values are handled correctly there regardless of the
|
|
58
|
+
// != null guard in updateTransactionCategorizationUIState.
|
|
42
59
|
updateActions.push((0, transactionsViewReducer_1.updateTransactionCategorizationUIState)({
|
|
43
60
|
selectedTab,
|
|
44
61
|
uiState: {
|
|
45
|
-
...uiState,
|
|
46
62
|
pageToken: response.data.next_page_token ?? null,
|
|
47
63
|
},
|
|
48
64
|
}));
|
|
@@ -82,5 +98,5 @@ const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => action
|
|
|
82
98
|
status: (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'fetch Transaction Categorization REST API call errored out' +
|
|
83
99
|
JSON.stringify(error)),
|
|
84
100
|
}))));
|
|
85
|
-
}));
|
|
101
|
+
}))));
|
|
86
102
|
exports.fetchTransactionCategorizationEpic = fetchTransactionCategorizationEpic;
|
|
@@ -5,6 +5,6 @@ import { fetchAccountList } from '../../../accountList/accountListReducer';
|
|
|
5
5
|
import { fetchClassList } from '../../../classList/classListReducer';
|
|
6
6
|
import { fetchOwnerList } from '../../../ownerList/ownerListReducer';
|
|
7
7
|
import { fetchProjectList } from '../../../projectList/projectListReducer';
|
|
8
|
-
import { fetchTransactionCategorization, fetchTransactionCategorizationView } from '../../reducers/transactionsViewReducer';
|
|
9
|
-
export type ActionType = ReturnType<typeof fetchTransactionCategorizationView> | ReturnType<typeof fetchTransactionCategorization> | ReturnType<typeof fetchAccountList> | ReturnType<typeof fetchClassList> | ReturnType<typeof fetchOwnerList> | ReturnType<typeof fetchProjectList>;
|
|
8
|
+
import { fetchTransactionCategorization, fetchTransactionCategorizationView, restoreFullDatasetSnapshot } from '../../reducers/transactionsViewReducer';
|
|
9
|
+
export type ActionType = ReturnType<typeof fetchTransactionCategorizationView> | ReturnType<typeof fetchTransactionCategorization> | ReturnType<typeof restoreFullDatasetSnapshot> | ReturnType<typeof fetchAccountList> | ReturnType<typeof fetchClassList> | ReturnType<typeof fetchOwnerList> | ReturnType<typeof fetchProjectList>;
|
|
10
10
|
export declare const fetchTransactionCategorizationViewEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>) => Observable<ActionType>;
|
|
@@ -10,6 +10,7 @@ const classListReducer_1 = require("../../../classList/classListReducer");
|
|
|
10
10
|
const ownerListReducer_1 = require("../../../ownerList/ownerListReducer");
|
|
11
11
|
const projectListReducer_1 = require("../../../projectList/projectListReducer");
|
|
12
12
|
const transactionsViewReducer_1 = require("../../reducers/transactionsViewReducer");
|
|
13
|
+
const transactionsViewState_1 = require("../../types/transactionsViewState");
|
|
13
14
|
const fetchTransactionCategorizationViewEpic = (actions$, state$) => actions$.pipe((0, operators_1.filter)(transactionsViewReducer_1.fetchTransactionCategorizationView.match), (0, operators_1.mergeMap)((action) => {
|
|
14
15
|
const { selectedTab, cacheOverride, keepExistingListItems, pageToken, period, refreshViewInBackground, searchString, resetListItems, isUncategorizedExpenseCategoryEnabled, } = action.payload;
|
|
15
16
|
const updateActions = [];
|
|
@@ -19,8 +20,14 @@ const fetchTransactionCategorizationViewEpic = (actions$, state$) => actions$.pi
|
|
|
19
20
|
month: period.start.month,
|
|
20
21
|
year: period.start.year,
|
|
21
22
|
};
|
|
22
|
-
const
|
|
23
|
-
|
|
23
|
+
const periodId = (0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod);
|
|
24
|
+
const transactionIds = transactionIdsBySelectedPeriod[periodId] ?? [];
|
|
25
|
+
const hasUsableSnapshot = (tabKey) => {
|
|
26
|
+
const snapshot = expenseAutomationTransactionsViewState.transactionCategorizationView[tabKey].fullDatasetSnapshot;
|
|
27
|
+
return (snapshot != null &&
|
|
28
|
+
snapshot.periodId === periodId &&
|
|
29
|
+
snapshot.transactionIds.length > 0);
|
|
30
|
+
};
|
|
24
31
|
const accountList = state$.value.accountListState.byReportId['accountList'];
|
|
25
32
|
if (accountList.hasValidState() === false &&
|
|
26
33
|
accountList.fetchState === 'Not-Started') {
|
|
@@ -42,11 +49,48 @@ const fetchTransactionCategorizationViewEpic = (actions$, state$) => actions$.pi
|
|
|
42
49
|
vendorOwnerList.fetchState === 'Not-Started') {
|
|
43
50
|
updateActions.push((0, ownerListReducer_1.fetchOwnerList)());
|
|
44
51
|
}
|
|
45
|
-
|
|
52
|
+
// When clearing search (searchString === ''), restore from the pre-search
|
|
53
|
+
// snapshot if one exists — avoids a round-trip since all data is already
|
|
54
|
+
// in the entity store. Fall back to a normal fetch if no snapshot (the
|
|
55
|
+
// tab was never loaded before the search was applied).
|
|
56
|
+
if (searchString === '' && hasUsableSnapshot(selectedTab)) {
|
|
57
|
+
updateActions.push((0, transactionsViewReducer_1.restoreFullDatasetSnapshot)({ selectedTab, period }));
|
|
58
|
+
}
|
|
59
|
+
else if (cacheOverride === true ||
|
|
46
60
|
fetchState === 'Not-Started' ||
|
|
47
61
|
transactionIds.length === 0) {
|
|
48
62
|
updateActions.push((0, transactionsViewReducer_1.fetchTransactionCategorization)(selectedTab, period, cacheOverride, keepExistingListItems, searchString, pageToken, refreshViewInBackground, resetListItems, isUncategorizedExpenseCategoryEnabled));
|
|
49
63
|
}
|
|
64
|
+
// On the initial load of a tab (searchString undefined = non-search fetch),
|
|
65
|
+
// preload every other tab that hasn't been fetched yet for this period.
|
|
66
|
+
// This makes switching to the other tab instant instead of triggering a
|
|
67
|
+
// fresh fetch on click. refreshViewInBackground=true keeps it silent.
|
|
68
|
+
if (searchString === undefined) {
|
|
69
|
+
transactionsViewState_1.TRANSACTIONS_TABS.filter((tab) => tab !== selectedTab).forEach((otherTab) => {
|
|
70
|
+
const otherTabFetchState = expenseAutomationTransactionsViewState.transactionCategorizationView[otherTab].fetchState;
|
|
71
|
+
if (otherTabFetchState === 'Not-Started') {
|
|
72
|
+
updateActions.push((0, transactionsViewReducer_1.fetchTransactionCategorization)(otherTab, period, false, false, undefined, undefined, true, undefined, isUncategorizedExpenseCategoryEnabled));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// When search changes (searchString defined, including empty string to
|
|
77
|
+
// clear), also update every other tab so all tabs always reflect the same
|
|
78
|
+
// search query. Skip if the other tab already has this search string to
|
|
79
|
+
// avoid redundant work on noop updates. On clear, restore from snapshot
|
|
80
|
+
// if available; otherwise issue a background fetch.
|
|
81
|
+
if (searchString !== undefined) {
|
|
82
|
+
transactionsViewState_1.TRANSACTIONS_TABS.filter((tab) => tab !== selectedTab).forEach((otherTab) => {
|
|
83
|
+
const otherTabSearchString = expenseAutomationTransactionsViewState.transactionCategorizationView[otherTab].uiState.searchString;
|
|
84
|
+
if (searchString !== otherTabSearchString) {
|
|
85
|
+
if (searchString === '' && hasUsableSnapshot(otherTab)) {
|
|
86
|
+
updateActions.push((0, transactionsViewReducer_1.restoreFullDatasetSnapshot)({ selectedTab: otherTab, period }));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
updateActions.push((0, transactionsViewReducer_1.fetchTransactionCategorization)(otherTab, period, true, keepExistingListItems, searchString, undefined, true, true, isUncategorizedExpenseCategoryEnabled));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
50
94
|
return (0, rxjs_1.from)(updateActions);
|
|
51
95
|
}));
|
|
52
96
|
exports.fetchTransactionCategorizationViewEpic = fetchTransactionCategorizationViewEpic;
|
|
@@ -19,6 +19,13 @@ export interface TransactionCategorizationQueryPayload {
|
|
|
19
19
|
* on the backend.
|
|
20
20
|
*/
|
|
21
21
|
sub_tab: CompletedSubTab;
|
|
22
|
+
/**
|
|
23
|
+
* When `false`, the backend scopes the search to the active tab's
|
|
24
|
+
* categorization status (respects `auto_categorized`). When `true` or
|
|
25
|
+
* omitted, the backend searches across all statuses — the legacy behaviour
|
|
26
|
+
* that the Receipts manual-match flow relies on.
|
|
27
|
+
*/
|
|
28
|
+
search_all_statuses?: boolean;
|
|
22
29
|
}
|
|
23
30
|
export interface TransactionCategorizationPayload {
|
|
24
31
|
next_page_token: string | null;
|
|
@@ -137,7 +137,10 @@ export declare const fetchTransactionCategorization: import("@reduxjs/toolkit").
|
|
|
137
137
|
}, "expenseAutomationTransactionsView/fetchTransactionCategorizationSuccess">, resetOtherTabsFetchState: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
138
138
|
tabs: TransactionsTab[];
|
|
139
139
|
refreshViewInBackground?: boolean;
|
|
140
|
-
}, "expenseAutomationTransactionsView/resetOtherTabsFetchState">,
|
|
140
|
+
}, "expenseAutomationTransactionsView/resetOtherTabsFetchState">, restoreFullDatasetSnapshot: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
141
|
+
period: TimePeriod;
|
|
142
|
+
selectedTab: TransactionsTab;
|
|
143
|
+
}, "expenseAutomationTransactionsView/restoreFullDatasetSnapshot">, fetchTransactionCategorizationView: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[selectedTab: "review" | "autoCategorized", period: TimePeriod, cacheOverride?: any, keepExistingListItems?: any, searchString?: string | undefined, pageToken?: PageToken | undefined, refreshViewInBackground?: any, resetListItems?: any, isUncategorizedExpenseCategoryEnabled?: boolean | undefined], {
|
|
141
144
|
selectedTab: "review" | "autoCategorized";
|
|
142
145
|
period: TimePeriod;
|
|
143
146
|
searchString: string | undefined;
|
|
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
var _a;
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.uploadTransactionCategorizationReceiptSuccess = exports.updateTransactionCategorizationUploadReceiptState = exports.removeTransactionFromAllTabs = exports.backgroundRefetchReviewTab = exports.syncTransactionCategorizationFromDetailSave = exports.updateSelectedTransactionId = exports.markCategoryClassRecommendationsFailureForCategorization = exports.markCategoryClassRecommendationsCompletedForCategorization = exports.markCategoryClassRecommendationsInProgressForCategorization = exports.setEntityRecommendationForLineIdsForCategorization = exports.clearExpenseAutomationTransactionsView = exports.clearExpenseAutomationTransactionsViewPerTabView = exports.updateSelectedCheckboxTransactionIds = exports.fetchTransactionCategorizationView = exports.resetOtherTabsFetchState = exports.fetchTransactionCategorizationSuccess = exports.updateParentTotalCountForTab = exports.updateTotalCountForTransactionCategorization = exports.setAllItemsToCategoryClassInLocalDataForCategorization = exports.updateSelectedCustomerForTransaction = exports.updateSelectedVendorForTransaction = exports.updateStatusForTransactionNotMiscategorizedUpdateForCategorization = exports.markTransactionAsNotMiscategorized = exports.updateTransactionCategorizationSaveStatus = exports.updateTransactionCategorizationCompletedSubTab = exports.updateCurrentSelectedTransactionCategorizationTab = exports.updateTransactionCategorization = exports.saveTransactionCategorization = exports.fetchTransactionCategorizationFailure = exports.saveTransactionCategorizationLocalData = exports.initializeTransactionCategorizationViewLocalData = exports.updateTransactionFilters = exports.updateTransactionCategorizationUIState = exports.fetchTransactionCategorization = exports.initialState = exports.initialTransactionTabViewState = void 0;
|
|
7
|
+
exports.uploadTransactionCategorizationReceiptSuccess = exports.updateTransactionCategorizationUploadReceiptState = exports.removeTransactionFromAllTabs = exports.backgroundRefetchReviewTab = exports.syncTransactionCategorizationFromDetailSave = exports.updateSelectedTransactionId = exports.markCategoryClassRecommendationsFailureForCategorization = exports.markCategoryClassRecommendationsCompletedForCategorization = exports.markCategoryClassRecommendationsInProgressForCategorization = exports.setEntityRecommendationForLineIdsForCategorization = exports.clearExpenseAutomationTransactionsView = exports.clearExpenseAutomationTransactionsViewPerTabView = exports.updateSelectedCheckboxTransactionIds = exports.fetchTransactionCategorizationView = exports.restoreFullDatasetSnapshot = exports.resetOtherTabsFetchState = exports.fetchTransactionCategorizationSuccess = exports.updateParentTotalCountForTab = exports.updateTotalCountForTransactionCategorization = exports.setAllItemsToCategoryClassInLocalDataForCategorization = exports.updateSelectedCustomerForTransaction = exports.updateSelectedVendorForTransaction = exports.updateStatusForTransactionNotMiscategorizedUpdateForCategorization = exports.markTransactionAsNotMiscategorized = exports.updateTransactionCategorizationSaveStatus = exports.updateTransactionCategorizationCompletedSubTab = exports.updateCurrentSelectedTransactionCategorizationTab = exports.updateTransactionCategorization = exports.saveTransactionCategorization = exports.fetchTransactionCategorizationFailure = exports.saveTransactionCategorizationLocalData = exports.initializeTransactionCategorizationViewLocalData = exports.updateTransactionFilters = exports.updateTransactionCategorizationUIState = exports.fetchTransactionCategorization = exports.initialState = exports.initialTransactionTabViewState = void 0;
|
|
8
8
|
const toolkit_1 = require("@reduxjs/toolkit");
|
|
9
9
|
const get_1 = __importDefault(require("lodash/get"));
|
|
10
10
|
const uniq_1 = __importDefault(require("lodash/uniq"));
|
|
@@ -45,6 +45,7 @@ exports.initialTransactionTabViewState = {
|
|
|
45
45
|
hasValidState() {
|
|
46
46
|
return this.fetchState == 'Completed';
|
|
47
47
|
},
|
|
48
|
+
fullDatasetSnapshot: undefined,
|
|
48
49
|
transactionReviewLocalDataById: {},
|
|
49
50
|
transactionIdsBySelectedPeriod: {},
|
|
50
51
|
selectedCheckBoxTransactionIds: [],
|
|
@@ -163,24 +164,52 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
|
|
|
163
164
|
}
|
|
164
165
|
draft.transactionCategorizationView[selectedTab].uiState.pageToken =
|
|
165
166
|
pageToken ?? null;
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
// Only overwrite when explicitly set; undefined means a non-search fetch
|
|
168
|
+
// (tab switch, period change) should preserve the existing search string.
|
|
169
|
+
if (searchString !== undefined) {
|
|
170
|
+
draft.transactionCategorizationView[selectedTab].uiState.searchString =
|
|
171
|
+
searchString;
|
|
172
|
+
}
|
|
168
173
|
draft.transactionCategorizationView[selectedTab].error = undefined;
|
|
174
|
+
const monthYearPeriod = {
|
|
175
|
+
month: period.start.month,
|
|
176
|
+
year: period.start.year,
|
|
177
|
+
};
|
|
178
|
+
const periodId = (0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod);
|
|
179
|
+
const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
|
|
180
|
+
.transactionIdsBySelectedPeriod[periodId] ?? [];
|
|
181
|
+
// Snapshot before any list mutation so clearing the search restores
|
|
182
|
+
// instantly without an API round-trip — for BOTH active and synced tabs.
|
|
183
|
+
// Active-tab search dispatches resetListItems=false, so this must live
|
|
184
|
+
// outside the resetListItems block. fullDatasetSnapshot == null prevents
|
|
185
|
+
// overwriting the original snapshot on search refinement ('ama'→'amazon').
|
|
186
|
+
if (searchString !== undefined &&
|
|
187
|
+
searchString !== '' &&
|
|
188
|
+
transactionIdsBySelectedPeriod.length > 0 &&
|
|
189
|
+
draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot ==
|
|
190
|
+
null) {
|
|
191
|
+
draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
|
|
192
|
+
{
|
|
193
|
+
parentTotalCount: draft.parentTotalCountByTab[selectedTab][periodId],
|
|
194
|
+
periodId,
|
|
195
|
+
totalCount: draft.transactionCategorizationView[selectedTab].uiState
|
|
196
|
+
.totalCount[periodId],
|
|
197
|
+
transactionIds: [...transactionIdsBySelectedPeriod],
|
|
198
|
+
transactionReviewLocalDataById: {
|
|
199
|
+
...draft.transactionCategorizationView[selectedTab]
|
|
200
|
+
.transactionReviewLocalDataById,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
169
204
|
// Reset list when we change the sort order and fetch list again
|
|
170
205
|
if (resetListItems === true) {
|
|
171
|
-
const monthYearPeriod = {
|
|
172
|
-
month: period.start.month,
|
|
173
|
-
year: period.start.year,
|
|
174
|
-
};
|
|
175
|
-
const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
|
|
176
|
-
.transactionIdsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod)];
|
|
177
206
|
transactionIdsBySelectedPeriod.forEach((transactionId) => {
|
|
178
207
|
delete draft.transactionCategorizationView[selectedTab]
|
|
179
208
|
.transactionReviewLocalDataById[transactionId];
|
|
180
209
|
});
|
|
181
210
|
draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds =
|
|
182
211
|
draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds.filter((selectedCheckBoxTransactionId) => transactionIdsBySelectedPeriod.includes(selectedCheckBoxTransactionId));
|
|
183
|
-
draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[
|
|
212
|
+
draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[periodId] = [];
|
|
184
213
|
draft.transactionCategorizationView[selectedTab].refreshStatus = {
|
|
185
214
|
fetchState: 'Not-Started',
|
|
186
215
|
error: undefined,
|
|
@@ -312,10 +341,12 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
|
|
|
312
341
|
}
|
|
313
342
|
},
|
|
314
343
|
updateTransactionFilters(draft, action) {
|
|
315
|
-
const {
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
|
|
344
|
+
const { filters } = action.payload;
|
|
345
|
+
// Filters are tab-agnostic (payee/category/class/amount), so apply to
|
|
346
|
+
// all tabs so switching tabs after filtering shows consistent results.
|
|
347
|
+
transactionsViewState_1.TRANSACTIONS_TABS.forEach((tab) => {
|
|
348
|
+
draft.transactionCategorizationView[tab].filters = filters;
|
|
349
|
+
});
|
|
319
350
|
},
|
|
320
351
|
saveTransactionCategorization: {
|
|
321
352
|
prepare(selectedTab, transactionIds, cotTrackingByTransactionId, isUncategorizedExpenseCategoryEnabled) {
|
|
@@ -667,19 +698,62 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
|
|
|
667
698
|
resetOtherTabsFetchState(draft, action) {
|
|
668
699
|
const { tabs, refreshViewInBackground } = action.payload;
|
|
669
700
|
tabs.forEach((tab) => {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
701
|
+
const tabFetchState = draft.transactionCategorizationView[tab].fetchState;
|
|
702
|
+
if (refreshViewInBackground !== true) {
|
|
703
|
+
// Foreground fetch completed: mark other tabs as Completed.
|
|
673
704
|
draft.transactionCategorizationView[tab].fetchState = 'Completed';
|
|
674
705
|
draft.transactionCategorizationView[tab].error = undefined;
|
|
675
706
|
}
|
|
676
|
-
else {
|
|
707
|
+
else if (tabFetchState !== 'In-Progress') {
|
|
708
|
+
// Background fetch completed: reset the other tab's refreshStatus,
|
|
709
|
+
// but only when it is not mid-load. During a dual-search the active
|
|
710
|
+
// tab has its own foreground fetch in flight — clobbering its
|
|
711
|
+
// fetchState here would drop the skeleton and show an empty list.
|
|
677
712
|
draft.transactionCategorizationView[tab].refreshStatus = {
|
|
678
713
|
fetchState: 'Not-Started',
|
|
679
714
|
error: undefined,
|
|
680
715
|
};
|
|
681
716
|
}
|
|
717
|
+
// If tabFetchState === 'In-Progress' and refreshViewInBackground === true,
|
|
718
|
+
// leave the other tab's state untouched — it has its own active fetch.
|
|
719
|
+
});
|
|
720
|
+
},
|
|
721
|
+
restoreFullDatasetSnapshot(draft, action) {
|
|
722
|
+
const { selectedTab, period } = action.payload;
|
|
723
|
+
const periodId = (0, timePeriod_1.toMonthYearPeriodId)({
|
|
724
|
+
month: period.start.month,
|
|
725
|
+
year: period.start.year,
|
|
726
|
+
});
|
|
727
|
+
const snapshot = draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot;
|
|
728
|
+
if (snapshot == null || snapshot.periodId !== periodId) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
draft.transactionCategorizationView[selectedTab]
|
|
732
|
+
.transactionIdsBySelectedPeriod[periodId] = snapshot.transactionIds;
|
|
733
|
+
// Merge back per-transaction local data; skip entries that were updated
|
|
734
|
+
// during the search (e.g. a save that happened while filtered results
|
|
735
|
+
// were showing) so those saves are not overwritten.
|
|
736
|
+
Object.entries(snapshot.transactionReviewLocalDataById).forEach(([transactionId, localData]) => {
|
|
737
|
+
if (draft.transactionCategorizationView[selectedTab]
|
|
738
|
+
.transactionReviewLocalDataById[transactionId] == null) {
|
|
739
|
+
draft.transactionCategorizationView[selectedTab]
|
|
740
|
+
.transactionReviewLocalDataById[transactionId] = localData;
|
|
741
|
+
}
|
|
682
742
|
});
|
|
743
|
+
if (snapshot.totalCount != null) {
|
|
744
|
+
draft.transactionCategorizationView[selectedTab].uiState.totalCount[periodId] = snapshot.totalCount;
|
|
745
|
+
}
|
|
746
|
+
if (snapshot.parentTotalCount != null) {
|
|
747
|
+
draft.parentTotalCountByTab[selectedTab][periodId] =
|
|
748
|
+
snapshot.parentTotalCount;
|
|
749
|
+
}
|
|
750
|
+
draft.transactionCategorizationView[selectedTab].uiState.searchString =
|
|
751
|
+
'';
|
|
752
|
+
draft.transactionCategorizationView[selectedTab].uiState.pageToken = null;
|
|
753
|
+
draft.transactionCategorizationView[selectedTab].fetchState = 'Completed';
|
|
754
|
+
draft.transactionCategorizationView[selectedTab].error = undefined;
|
|
755
|
+
draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
|
|
756
|
+
undefined;
|
|
683
757
|
},
|
|
684
758
|
fetchTransactionCategorizationFailure(draft, action) {
|
|
685
759
|
const { selectedTab, selectedPeriod } = action.payload;
|
|
@@ -1061,5 +1135,5 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
|
|
|
1061
1135
|
},
|
|
1062
1136
|
},
|
|
1063
1137
|
});
|
|
1064
|
-
_a = expenseAutomationTransactionsView.actions, exports.fetchTransactionCategorization = _a.fetchTransactionCategorization, exports.updateTransactionCategorizationUIState = _a.updateTransactionCategorizationUIState, exports.updateTransactionFilters = _a.updateTransactionFilters, exports.initializeTransactionCategorizationViewLocalData = _a.initializeTransactionCategorizationViewLocalData, exports.saveTransactionCategorizationLocalData = _a.saveTransactionCategorizationLocalData, exports.fetchTransactionCategorizationFailure = _a.fetchTransactionCategorizationFailure, exports.saveTransactionCategorization = _a.saveTransactionCategorization, exports.updateTransactionCategorization = _a.updateTransactionCategorization, exports.updateCurrentSelectedTransactionCategorizationTab = _a.updateCurrentSelectedTransactionCategorizationTab, exports.updateTransactionCategorizationCompletedSubTab = _a.updateTransactionCategorizationCompletedSubTab, exports.updateTransactionCategorizationSaveStatus = _a.updateTransactionCategorizationSaveStatus, exports.markTransactionAsNotMiscategorized = _a.markTransactionAsNotMiscategorized, exports.updateStatusForTransactionNotMiscategorizedUpdateForCategorization = _a.updateStatusForTransactionNotMiscategorizedUpdateForCategorization, exports.updateSelectedVendorForTransaction = _a.updateSelectedVendorForTransaction, exports.updateSelectedCustomerForTransaction = _a.updateSelectedCustomerForTransaction, exports.setAllItemsToCategoryClassInLocalDataForCategorization = _a.setAllItemsToCategoryClassInLocalDataForCategorization, exports.updateTotalCountForTransactionCategorization = _a.updateTotalCountForTransactionCategorization, exports.updateParentTotalCountForTab = _a.updateParentTotalCountForTab, exports.fetchTransactionCategorizationSuccess = _a.fetchTransactionCategorizationSuccess, exports.resetOtherTabsFetchState = _a.resetOtherTabsFetchState, exports.fetchTransactionCategorizationView = _a.fetchTransactionCategorizationView, exports.updateSelectedCheckboxTransactionIds = _a.updateSelectedCheckboxTransactionIds, exports.clearExpenseAutomationTransactionsViewPerTabView = _a.clearExpenseAutomationTransactionsViewPerTabView, exports.clearExpenseAutomationTransactionsView = _a.clearExpenseAutomationTransactionsView, exports.setEntityRecommendationForLineIdsForCategorization = _a.setEntityRecommendationForLineIdsForCategorization, exports.markCategoryClassRecommendationsInProgressForCategorization = _a.markCategoryClassRecommendationsInProgressForCategorization, exports.markCategoryClassRecommendationsCompletedForCategorization = _a.markCategoryClassRecommendationsCompletedForCategorization, exports.markCategoryClassRecommendationsFailureForCategorization = _a.markCategoryClassRecommendationsFailureForCategorization, exports.updateSelectedTransactionId = _a.updateSelectedTransactionId, exports.syncTransactionCategorizationFromDetailSave = _a.syncTransactionCategorizationFromDetailSave, exports.backgroundRefetchReviewTab = _a.backgroundRefetchReviewTab, exports.removeTransactionFromAllTabs = _a.removeTransactionFromAllTabs, exports.updateTransactionCategorizationUploadReceiptState = _a.updateTransactionCategorizationUploadReceiptState, exports.uploadTransactionCategorizationReceiptSuccess = _a.uploadTransactionCategorizationReceiptSuccess;
|
|
1138
|
+
_a = expenseAutomationTransactionsView.actions, exports.fetchTransactionCategorization = _a.fetchTransactionCategorization, exports.updateTransactionCategorizationUIState = _a.updateTransactionCategorizationUIState, exports.updateTransactionFilters = _a.updateTransactionFilters, exports.initializeTransactionCategorizationViewLocalData = _a.initializeTransactionCategorizationViewLocalData, exports.saveTransactionCategorizationLocalData = _a.saveTransactionCategorizationLocalData, exports.fetchTransactionCategorizationFailure = _a.fetchTransactionCategorizationFailure, exports.saveTransactionCategorization = _a.saveTransactionCategorization, exports.updateTransactionCategorization = _a.updateTransactionCategorization, exports.updateCurrentSelectedTransactionCategorizationTab = _a.updateCurrentSelectedTransactionCategorizationTab, exports.updateTransactionCategorizationCompletedSubTab = _a.updateTransactionCategorizationCompletedSubTab, exports.updateTransactionCategorizationSaveStatus = _a.updateTransactionCategorizationSaveStatus, exports.markTransactionAsNotMiscategorized = _a.markTransactionAsNotMiscategorized, exports.updateStatusForTransactionNotMiscategorizedUpdateForCategorization = _a.updateStatusForTransactionNotMiscategorizedUpdateForCategorization, exports.updateSelectedVendorForTransaction = _a.updateSelectedVendorForTransaction, exports.updateSelectedCustomerForTransaction = _a.updateSelectedCustomerForTransaction, exports.setAllItemsToCategoryClassInLocalDataForCategorization = _a.setAllItemsToCategoryClassInLocalDataForCategorization, exports.updateTotalCountForTransactionCategorization = _a.updateTotalCountForTransactionCategorization, exports.updateParentTotalCountForTab = _a.updateParentTotalCountForTab, exports.fetchTransactionCategorizationSuccess = _a.fetchTransactionCategorizationSuccess, exports.resetOtherTabsFetchState = _a.resetOtherTabsFetchState, exports.restoreFullDatasetSnapshot = _a.restoreFullDatasetSnapshot, exports.fetchTransactionCategorizationView = _a.fetchTransactionCategorizationView, exports.updateSelectedCheckboxTransactionIds = _a.updateSelectedCheckboxTransactionIds, exports.clearExpenseAutomationTransactionsViewPerTabView = _a.clearExpenseAutomationTransactionsViewPerTabView, exports.clearExpenseAutomationTransactionsView = _a.clearExpenseAutomationTransactionsView, exports.setEntityRecommendationForLineIdsForCategorization = _a.setEntityRecommendationForLineIdsForCategorization, exports.markCategoryClassRecommendationsInProgressForCategorization = _a.markCategoryClassRecommendationsInProgressForCategorization, exports.markCategoryClassRecommendationsCompletedForCategorization = _a.markCategoryClassRecommendationsCompletedForCategorization, exports.markCategoryClassRecommendationsFailureForCategorization = _a.markCategoryClassRecommendationsFailureForCategorization, exports.updateSelectedTransactionId = _a.updateSelectedTransactionId, exports.syncTransactionCategorizationFromDetailSave = _a.syncTransactionCategorizationFromDetailSave, exports.backgroundRefetchReviewTab = _a.backgroundRefetchReviewTab, exports.removeTransactionFromAllTabs = _a.removeTransactionFromAllTabs, exports.updateTransactionCategorizationUploadReceiptState = _a.updateTransactionCategorizationUploadReceiptState, exports.uploadTransactionCategorizationReceiptSuccess = _a.uploadTransactionCategorizationReceiptSuccess;
|
|
1065
1139
|
exports.default = expenseAutomationTransactionsView.reducer;
|
|
@@ -94,8 +94,24 @@ export interface SupportedTransactionCategorization {
|
|
|
94
94
|
transactionId?: ID;
|
|
95
95
|
}
|
|
96
96
|
export declare const initialSupportedTransactionCategorization: SupportedTransactionCategorization;
|
|
97
|
+
/**
|
|
98
|
+
* Snapshot of a tab's full (pre-search) dataset taken the moment the user
|
|
99
|
+
* first applies a non-empty search string. When the search is cleared the
|
|
100
|
+
* view epic restores from here instead of issuing a new API request.
|
|
101
|
+
*
|
|
102
|
+
* Only saved when there are actual rows to restore (`transactionIds` is
|
|
103
|
+
* non-empty); cleared once consumed or replaced by a fresh search.
|
|
104
|
+
*/
|
|
105
|
+
export interface FullDatasetSnapshot {
|
|
106
|
+
parentTotalCount: number | undefined;
|
|
107
|
+
periodId: MonthYearPeriodId;
|
|
108
|
+
totalCount: number | undefined;
|
|
109
|
+
transactionIds: ID[];
|
|
110
|
+
transactionReviewLocalDataById: Record<ID | TransactionDetailKey, SupportedTransactionCategorization>;
|
|
111
|
+
}
|
|
97
112
|
export interface TransactionsTabViewState extends FetchedState {
|
|
98
113
|
filters: TransactionFilters;
|
|
114
|
+
fullDatasetSnapshot: FullDatasetSnapshot | undefined;
|
|
99
115
|
markAsNotMiscategorizedStatus: FetchStateAndError;
|
|
100
116
|
refreshStatus: FetchStateAndError;
|
|
101
117
|
saveStatus: FetchStateAndError;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeniai/client-epic-state",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.18-betaRD2",
|
|
4
4
|
"description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/esm/index.js",
|