@zeniai/client-epic-state 5.1.16 → 5.1.18-betaRD1
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 +84 -12
- 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 +85 -13
- 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,8 +157,12 @@ 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;
|
|
162
167
|
// Reset list when we change the sort order and fetch list again
|
|
163
168
|
if (resetListItems === true) {
|
|
@@ -165,8 +170,30 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
165
170
|
month: period.start.month,
|
|
166
171
|
year: period.start.year,
|
|
167
172
|
};
|
|
173
|
+
const periodId = toMonthYearPeriodId(monthYearPeriod);
|
|
168
174
|
const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
|
|
169
|
-
.transactionIdsBySelectedPeriod[
|
|
175
|
+
.transactionIdsBySelectedPeriod[periodId] ?? [];
|
|
176
|
+
// When starting a non-empty search, snapshot the current full dataset
|
|
177
|
+
// so clearing the search can restore instantly without an API round-trip.
|
|
178
|
+
// Only save the snapshot when there are rows to restore; skip for tabs
|
|
179
|
+
// that have never been loaded (empty list → no useful snapshot).
|
|
180
|
+
if (searchString !== undefined &&
|
|
181
|
+
searchString !== '' &&
|
|
182
|
+
transactionIdsBySelectedPeriod.length > 0) {
|
|
183
|
+
const snapshot = {
|
|
184
|
+
parentTotalCount: draft.parentTotalCountByTab[selectedTab][periodId],
|
|
185
|
+
periodId,
|
|
186
|
+
totalCount: draft.transactionCategorizationView[selectedTab].uiState
|
|
187
|
+
.totalCount[periodId],
|
|
188
|
+
transactionIds: [...transactionIdsBySelectedPeriod],
|
|
189
|
+
transactionReviewLocalDataById: {
|
|
190
|
+
...draft.transactionCategorizationView[selectedTab]
|
|
191
|
+
.transactionReviewLocalDataById,
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
|
|
195
|
+
snapshot;
|
|
196
|
+
}
|
|
170
197
|
transactionIdsBySelectedPeriod.forEach((transactionId) => {
|
|
171
198
|
delete draft.transactionCategorizationView[selectedTab]
|
|
172
199
|
.transactionReviewLocalDataById[transactionId];
|
|
@@ -305,10 +332,12 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
305
332
|
}
|
|
306
333
|
},
|
|
307
334
|
updateTransactionFilters(draft, action) {
|
|
308
|
-
const {
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
|
|
335
|
+
const { filters } = action.payload;
|
|
336
|
+
// Filters are tab-agnostic (payee/category/class/amount), so apply to
|
|
337
|
+
// all tabs so switching tabs after filtering shows consistent results.
|
|
338
|
+
TRANSACTIONS_TABS.forEach((tab) => {
|
|
339
|
+
draft.transactionCategorizationView[tab].filters = filters;
|
|
340
|
+
});
|
|
312
341
|
},
|
|
313
342
|
saveTransactionCategorization: {
|
|
314
343
|
prepare(selectedTab, transactionIds, cotTrackingByTransactionId, isUncategorizedExpenseCategoryEnabled) {
|
|
@@ -660,20 +689,63 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
660
689
|
resetOtherTabsFetchState(draft, action) {
|
|
661
690
|
const { tabs, refreshViewInBackground } = action.payload;
|
|
662
691
|
tabs.forEach((tab) => {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
692
|
+
const tabFetchState = draft.transactionCategorizationView[tab].fetchState;
|
|
693
|
+
if (refreshViewInBackground !== true) {
|
|
694
|
+
// Foreground fetch completed: mark other tabs as Completed.
|
|
666
695
|
draft.transactionCategorizationView[tab].fetchState = 'Completed';
|
|
667
696
|
draft.transactionCategorizationView[tab].error = undefined;
|
|
668
697
|
}
|
|
669
|
-
else {
|
|
698
|
+
else if (tabFetchState !== 'In-Progress') {
|
|
699
|
+
// Background fetch completed: reset the other tab's refreshStatus,
|
|
700
|
+
// but only when it is not mid-load. During a dual-search the active
|
|
701
|
+
// tab has its own foreground fetch in flight — clobbering its
|
|
702
|
+
// fetchState here would drop the skeleton and show an empty list.
|
|
670
703
|
draft.transactionCategorizationView[tab].refreshStatus = {
|
|
671
704
|
fetchState: 'Not-Started',
|
|
672
705
|
error: undefined,
|
|
673
706
|
};
|
|
674
707
|
}
|
|
708
|
+
// If tabFetchState === 'In-Progress' and refreshViewInBackground === true,
|
|
709
|
+
// leave the other tab's state untouched — it has its own active fetch.
|
|
675
710
|
});
|
|
676
711
|
},
|
|
712
|
+
restoreFullDatasetSnapshot(draft, action) {
|
|
713
|
+
const { selectedTab, period } = action.payload;
|
|
714
|
+
const periodId = toMonthYearPeriodId({
|
|
715
|
+
month: period.start.month,
|
|
716
|
+
year: period.start.year,
|
|
717
|
+
});
|
|
718
|
+
const snapshot = draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot;
|
|
719
|
+
if (snapshot == null || snapshot.periodId !== periodId) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
draft.transactionCategorizationView[selectedTab]
|
|
723
|
+
.transactionIdsBySelectedPeriod[periodId] = snapshot.transactionIds;
|
|
724
|
+
// Merge back per-transaction local data; skip entries that were updated
|
|
725
|
+
// during the search (e.g. a save that happened while filtered results
|
|
726
|
+
// were showing) so those saves are not overwritten.
|
|
727
|
+
Object.entries(snapshot.transactionReviewLocalDataById).forEach(([transactionId, localData]) => {
|
|
728
|
+
if (draft.transactionCategorizationView[selectedTab]
|
|
729
|
+
.transactionReviewLocalDataById[transactionId] == null) {
|
|
730
|
+
draft.transactionCategorizationView[selectedTab]
|
|
731
|
+
.transactionReviewLocalDataById[transactionId] = localData;
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
if (snapshot.totalCount != null) {
|
|
735
|
+
draft.transactionCategorizationView[selectedTab].uiState.totalCount[periodId] = snapshot.totalCount;
|
|
736
|
+
}
|
|
737
|
+
if (snapshot.parentTotalCount != null) {
|
|
738
|
+
draft.parentTotalCountByTab[selectedTab][periodId] =
|
|
739
|
+
snapshot.parentTotalCount;
|
|
740
|
+
}
|
|
741
|
+
draft.transactionCategorizationView[selectedTab].uiState.searchString =
|
|
742
|
+
'';
|
|
743
|
+
draft.transactionCategorizationView[selectedTab].uiState.pageToken = null;
|
|
744
|
+
draft.transactionCategorizationView[selectedTab].fetchState = 'Completed';
|
|
745
|
+
draft.transactionCategorizationView[selectedTab].error = undefined;
|
|
746
|
+
draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
|
|
747
|
+
undefined;
|
|
748
|
+
},
|
|
677
749
|
fetchTransactionCategorizationFailure(draft, action) {
|
|
678
750
|
const { selectedTab, selectedPeriod } = action.payload;
|
|
679
751
|
draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)] =
|
|
@@ -1054,5 +1126,5 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
1054
1126
|
},
|
|
1055
1127
|
},
|
|
1056
1128
|
});
|
|
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;
|
|
1129
|
+
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
1130
|
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,8 +164,12 @@ 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;
|
|
169
174
|
// Reset list when we change the sort order and fetch list again
|
|
170
175
|
if (resetListItems === true) {
|
|
@@ -172,8 +177,30 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
|
|
|
172
177
|
month: period.start.month,
|
|
173
178
|
year: period.start.year,
|
|
174
179
|
};
|
|
180
|
+
const periodId = (0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod);
|
|
175
181
|
const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
|
|
176
|
-
.transactionIdsBySelectedPeriod[
|
|
182
|
+
.transactionIdsBySelectedPeriod[periodId] ?? [];
|
|
183
|
+
// When starting a non-empty search, snapshot the current full dataset
|
|
184
|
+
// so clearing the search can restore instantly without an API round-trip.
|
|
185
|
+
// Only save the snapshot when there are rows to restore; skip for tabs
|
|
186
|
+
// that have never been loaded (empty list → no useful snapshot).
|
|
187
|
+
if (searchString !== undefined &&
|
|
188
|
+
searchString !== '' &&
|
|
189
|
+
transactionIdsBySelectedPeriod.length > 0) {
|
|
190
|
+
const snapshot = {
|
|
191
|
+
parentTotalCount: draft.parentTotalCountByTab[selectedTab][periodId],
|
|
192
|
+
periodId,
|
|
193
|
+
totalCount: draft.transactionCategorizationView[selectedTab].uiState
|
|
194
|
+
.totalCount[periodId],
|
|
195
|
+
transactionIds: [...transactionIdsBySelectedPeriod],
|
|
196
|
+
transactionReviewLocalDataById: {
|
|
197
|
+
...draft.transactionCategorizationView[selectedTab]
|
|
198
|
+
.transactionReviewLocalDataById,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
|
|
202
|
+
snapshot;
|
|
203
|
+
}
|
|
177
204
|
transactionIdsBySelectedPeriod.forEach((transactionId) => {
|
|
178
205
|
delete draft.transactionCategorizationView[selectedTab]
|
|
179
206
|
.transactionReviewLocalDataById[transactionId];
|
|
@@ -312,10 +339,12 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
|
|
|
312
339
|
}
|
|
313
340
|
},
|
|
314
341
|
updateTransactionFilters(draft, action) {
|
|
315
|
-
const {
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
|
|
342
|
+
const { filters } = action.payload;
|
|
343
|
+
// Filters are tab-agnostic (payee/category/class/amount), so apply to
|
|
344
|
+
// all tabs so switching tabs after filtering shows consistent results.
|
|
345
|
+
transactionsViewState_1.TRANSACTIONS_TABS.forEach((tab) => {
|
|
346
|
+
draft.transactionCategorizationView[tab].filters = filters;
|
|
347
|
+
});
|
|
319
348
|
},
|
|
320
349
|
saveTransactionCategorization: {
|
|
321
350
|
prepare(selectedTab, transactionIds, cotTrackingByTransactionId, isUncategorizedExpenseCategoryEnabled) {
|
|
@@ -667,20 +696,63 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
|
|
|
667
696
|
resetOtherTabsFetchState(draft, action) {
|
|
668
697
|
const { tabs, refreshViewInBackground } = action.payload;
|
|
669
698
|
tabs.forEach((tab) => {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
699
|
+
const tabFetchState = draft.transactionCategorizationView[tab].fetchState;
|
|
700
|
+
if (refreshViewInBackground !== true) {
|
|
701
|
+
// Foreground fetch completed: mark other tabs as Completed.
|
|
673
702
|
draft.transactionCategorizationView[tab].fetchState = 'Completed';
|
|
674
703
|
draft.transactionCategorizationView[tab].error = undefined;
|
|
675
704
|
}
|
|
676
|
-
else {
|
|
705
|
+
else if (tabFetchState !== 'In-Progress') {
|
|
706
|
+
// Background fetch completed: reset the other tab's refreshStatus,
|
|
707
|
+
// but only when it is not mid-load. During a dual-search the active
|
|
708
|
+
// tab has its own foreground fetch in flight — clobbering its
|
|
709
|
+
// fetchState here would drop the skeleton and show an empty list.
|
|
677
710
|
draft.transactionCategorizationView[tab].refreshStatus = {
|
|
678
711
|
fetchState: 'Not-Started',
|
|
679
712
|
error: undefined,
|
|
680
713
|
};
|
|
681
714
|
}
|
|
715
|
+
// If tabFetchState === 'In-Progress' and refreshViewInBackground === true,
|
|
716
|
+
// leave the other tab's state untouched — it has its own active fetch.
|
|
682
717
|
});
|
|
683
718
|
},
|
|
719
|
+
restoreFullDatasetSnapshot(draft, action) {
|
|
720
|
+
const { selectedTab, period } = action.payload;
|
|
721
|
+
const periodId = (0, timePeriod_1.toMonthYearPeriodId)({
|
|
722
|
+
month: period.start.month,
|
|
723
|
+
year: period.start.year,
|
|
724
|
+
});
|
|
725
|
+
const snapshot = draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot;
|
|
726
|
+
if (snapshot == null || snapshot.periodId !== periodId) {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
draft.transactionCategorizationView[selectedTab]
|
|
730
|
+
.transactionIdsBySelectedPeriod[periodId] = snapshot.transactionIds;
|
|
731
|
+
// Merge back per-transaction local data; skip entries that were updated
|
|
732
|
+
// during the search (e.g. a save that happened while filtered results
|
|
733
|
+
// were showing) so those saves are not overwritten.
|
|
734
|
+
Object.entries(snapshot.transactionReviewLocalDataById).forEach(([transactionId, localData]) => {
|
|
735
|
+
if (draft.transactionCategorizationView[selectedTab]
|
|
736
|
+
.transactionReviewLocalDataById[transactionId] == null) {
|
|
737
|
+
draft.transactionCategorizationView[selectedTab]
|
|
738
|
+
.transactionReviewLocalDataById[transactionId] = localData;
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
if (snapshot.totalCount != null) {
|
|
742
|
+
draft.transactionCategorizationView[selectedTab].uiState.totalCount[periodId] = snapshot.totalCount;
|
|
743
|
+
}
|
|
744
|
+
if (snapshot.parentTotalCount != null) {
|
|
745
|
+
draft.parentTotalCountByTab[selectedTab][periodId] =
|
|
746
|
+
snapshot.parentTotalCount;
|
|
747
|
+
}
|
|
748
|
+
draft.transactionCategorizationView[selectedTab].uiState.searchString =
|
|
749
|
+
'';
|
|
750
|
+
draft.transactionCategorizationView[selectedTab].uiState.pageToken = null;
|
|
751
|
+
draft.transactionCategorizationView[selectedTab].fetchState = 'Completed';
|
|
752
|
+
draft.transactionCategorizationView[selectedTab].error = undefined;
|
|
753
|
+
draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
|
|
754
|
+
undefined;
|
|
755
|
+
},
|
|
684
756
|
fetchTransactionCategorizationFailure(draft, action) {
|
|
685
757
|
const { selectedTab, selectedPeriod } = action.payload;
|
|
686
758
|
draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)] =
|
|
@@ -1061,5 +1133,5 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
|
|
|
1061
1133
|
},
|
|
1062
1134
|
},
|
|
1063
1135
|
});
|
|
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;
|
|
1136
|
+
_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
1137
|
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-betaRD1",
|
|
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",
|