@zeniai/client-epic-state 5.0.95-beta1ND → 5.0.95-betaML1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonStateTypes/animations.d.ts +1 -1
- package/lib/commonStateTypes/animations.js +0 -1
- package/lib/entity/company/companyPayload.d.ts +0 -4
- package/lib/entity/company/companyPayload.js +0 -4
- package/lib/entity/company/companyStateTypes.d.ts +0 -4
- package/lib/entity/paymentAccount/paymentAccountSelector.d.ts +0 -1
- package/lib/entity/paymentAccount/paymentAccountSelector.js +0 -4
- package/lib/entity/tenant/epic/deleteConnectionEpic.d.ts +1 -1
- package/lib/entity/tenant/epic/saveAPIKeyConnectionEpic.d.ts +1 -1
- package/lib/entity/tenant/epic/saveConnectorCredentialsEpic.d.ts +3 -3
- package/lib/entity/tenant/epic/saveOAuthConnectionEpic.d.ts +1 -1
- package/lib/entity/tenant/tenantPayload.d.ts +0 -1
- package/lib/entity/tenant/tenantReducer.d.ts +25 -38
- package/lib/entity/tenant/tenantReducer.js +5 -65
- package/lib/entity/tenant/tenantState.d.ts +0 -1
- package/lib/epic.d.ts +1 -6
- package/lib/epic.js +1 -7
- package/lib/esm/commonStateTypes/animations.js +0 -1
- package/lib/esm/entity/company/companyPayload.js +0 -4
- package/lib/esm/entity/paymentAccount/paymentAccountSelector.js +0 -3
- package/lib/esm/entity/tenant/tenantReducer.js +3 -63
- package/lib/esm/epic.js +1 -7
- package/lib/esm/index.js +8 -11
- package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/initializeTransactionCategorizationLocalDataEpic.js +15 -0
- package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +63 -5
- package/lib/esm/view/fileView/epic/deleteFileEpic.js +2 -2
- package/lib/esm/view/fileView/epic/deleteFileListEpic.js +2 -2
- package/lib/esm/view/fileView/epic/updateFileNameEpic.js +2 -2
- package/lib/esm/view/onboardingView/customerView/onboardingCustomerViewReducer.js +50 -217
- package/lib/esm/view/onboardingView/customerView/onboardingCustomerViewSelector.js +1 -7
- package/lib/esm/view/onboardingView/customerView/onboardingCustomerViewState.js +0 -12
- package/lib/esm/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.js +12 -3
- package/lib/esm/view/recommendation/recommendationReducer.js +18 -1
- package/lib/esm/view/recommendation/recommendationStaleResponseHelper.js +26 -0
- package/lib/esm/view/spendManagement/commonSetup/setupViewReducer.js +52 -105
- package/lib/esm/view/spendManagement/commonSetup/setupViewSelector.js +1 -6
- package/lib/esm/view/transactionDetail/epics/initializeTransactionDetailLocalDataEpic.js +7 -0
- package/lib/esm/view/transactionDetail/transactionDetailReducer.js +44 -3
- package/lib/index.d.ts +9 -13
- package/lib/index.js +35 -54
- package/lib/view/expenseAutomationView/epics/transactionCategorization/initializeTransactionCategorizationLocalDataEpic.d.ts +2 -1
- package/lib/view/expenseAutomationView/epics/transactionCategorization/initializeTransactionCategorizationLocalDataEpic.js +15 -0
- package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +63 -5
- package/lib/view/fileView/epic/deleteFileEpic.js +1 -1
- package/lib/view/fileView/epic/deleteFileListEpic.js +1 -1
- package/lib/view/fileView/epic/updateFileNameEpic.js +1 -1
- package/lib/view/onboardingView/customerView/onboardingCustomerViewPayload.d.ts +0 -3
- package/lib/view/onboardingView/customerView/onboardingCustomerViewReducer.d.ts +4 -41
- package/lib/view/onboardingView/customerView/onboardingCustomerViewReducer.js +51 -219
- package/lib/view/onboardingView/customerView/onboardingCustomerViewSelector.d.ts +1 -5
- package/lib/view/onboardingView/customerView/onboardingCustomerViewSelector.js +2 -11
- package/lib/view/onboardingView/customerView/onboardingCustomerViewState.d.ts +0 -40
- package/lib/view/onboardingView/customerView/onboardingCustomerViewState.js +1 -13
- package/lib/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.d.ts +2 -2
- package/lib/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.js +10 -1
- package/lib/view/recommendation/recommendationReducer.d.ts +3 -1
- package/lib/view/recommendation/recommendationReducer.js +19 -2
- package/lib/view/recommendation/recommendationStaleResponseHelper.d.ts +7 -0
- package/lib/view/recommendation/recommendationStaleResponseHelper.js +30 -0
- package/lib/view/spendManagement/commonSetup/setupViewReducer.d.ts +2 -28
- package/lib/view/spendManagement/commonSetup/setupViewReducer.js +53 -106
- package/lib/view/spendManagement/commonSetup/setupViewSelector.d.ts +2 -7
- package/lib/view/spendManagement/commonSetup/setupViewSelector.js +1 -6
- package/lib/view/spendManagement/commonSetup/setupViewState.d.ts +0 -19
- package/lib/view/transactionDetail/epics/initializeTransactionDetailLocalDataEpic.d.ts +2 -1
- package/lib/view/transactionDetail/epics/initializeTransactionDetailLocalDataEpic.js +7 -0
- package/lib/view/transactionDetail/transactionDetailReducer.js +44 -3
- package/package.json +1 -1
- package/lib/entity/tenant/epic/initEmailConnectOAuthEpic.d.ts +0 -21
- package/lib/entity/tenant/epic/initEmailConnectOAuthEpic.js +0 -54
- package/lib/esm/entity/tenant/epic/initEmailConnectOAuthEpic.js +0 -50
- package/lib/esm/view/onboardingView/customerView/epic/aiAgentsActivation/fetchAiAgentsActivationStatusEpic.js +0 -42
- package/lib/esm/view/onboardingView/customerView/epic/customerDetails/acknowledgeOnboardingAiActivationViewedEpic.js +0 -29
- package/lib/esm/view/onboardingView/customerView/epic/customerDetails/acknowledgeOnboardingAiFinanceTeamEpic.js +0 -40
- package/lib/esm/view/spendManagement/commonSetup/epic/setup/parseUploadedKybDocumentEpic.js +0 -58
- package/lib/esm/view/spendManagement/commonSetup/epic/setup/parseUploadedKycDocumentEpic.js +0 -77
- package/lib/esm/view/spendManagement/commonSetup/kycKybAutofillActions.js +0 -14
- package/lib/esm/view/spendManagement/commonSetup/kycKybParseMapper.js +0 -210
- package/lib/esm/view/spendManagement/commonSetup/types/kycKybAutofill.js +0 -50
- package/lib/view/onboardingView/customerView/epic/aiAgentsActivation/fetchAiAgentsActivationStatusEpic.d.ts +0 -7
- package/lib/view/onboardingView/customerView/epic/aiAgentsActivation/fetchAiAgentsActivationStatusEpic.js +0 -46
- package/lib/view/onboardingView/customerView/epic/customerDetails/acknowledgeOnboardingAiActivationViewedEpic.d.ts +0 -8
- package/lib/view/onboardingView/customerView/epic/customerDetails/acknowledgeOnboardingAiActivationViewedEpic.js +0 -33
- package/lib/view/onboardingView/customerView/epic/customerDetails/acknowledgeOnboardingAiFinanceTeamEpic.d.ts +0 -9
- package/lib/view/onboardingView/customerView/epic/customerDetails/acknowledgeOnboardingAiFinanceTeamEpic.js +0 -44
- package/lib/view/spendManagement/commonSetup/epic/setup/parseUploadedKybDocumentEpic.d.ts +0 -28
- package/lib/view/spendManagement/commonSetup/epic/setup/parseUploadedKybDocumentEpic.js +0 -62
- package/lib/view/spendManagement/commonSetup/epic/setup/parseUploadedKycDocumentEpic.d.ts +0 -9
- package/lib/view/spendManagement/commonSetup/epic/setup/parseUploadedKycDocumentEpic.js +0 -81
- package/lib/view/spendManagement/commonSetup/kycKybAutofillActions.d.ts +0 -33
- package/lib/view/spendManagement/commonSetup/kycKybAutofillActions.js +0 -17
- package/lib/view/spendManagement/commonSetup/kycKybParseMapper.d.ts +0 -36
- package/lib/view/spendManagement/commonSetup/kycKybParseMapper.js +0 -218
- package/lib/view/spendManagement/commonSetup/types/kycKybAutofill.d.ts +0 -154
- package/lib/view/spendManagement/commonSetup/types/kycKybAutofill.js +0 -55
|
@@ -3,6 +3,7 @@ import { filter, mergeMap } from 'rxjs/operators';
|
|
|
3
3
|
import { getTransactionWithCOT } from '../../../../entity/transaction/transactionHelper';
|
|
4
4
|
import { getSupportedTransactionsByIds } from '../../../../entity/transaction/transactionSelector';
|
|
5
5
|
import { getUncategorizedAccounts } from '../../../accountList/accountListSelector';
|
|
6
|
+
import { cancelEntityRecommendationsForLineUpdate } from '../../../recommendation/recommendationReducer';
|
|
6
7
|
import { mergeTabSpecificLineItems, toTransactionDetailLocalData, } from '../../helpers/transactionCategorizationLocalDataHelper';
|
|
7
8
|
import { initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, } from '../../reducers/transactionsViewReducer';
|
|
8
9
|
import { initialSupportedTransactionCategorization, } from '../../types/transactionsViewState';
|
|
@@ -51,6 +52,20 @@ export const initializeTransactionCategorizationViewLocalDataEpic = (actions$, s
|
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
});
|
|
55
|
+
// Cancel any in-flight line-update recommendations fetch for the
|
|
56
|
+
// transactions whose local data is about to be wiped. Only runs
|
|
57
|
+
// for the destructive `keepExistingListItems === false` branch
|
|
58
|
+
// (list-page Cancel footer, modal confirm-reset, and the silent
|
|
59
|
+
// `backgroundRefetchReviewTab` path); the merge branch keeps
|
|
60
|
+
// existing per-line state intact and races no fetches.
|
|
61
|
+
if (keepExistingListItems === false) {
|
|
62
|
+
transactions.forEach((transaction) => {
|
|
63
|
+
fetchActions.push(cancelEntityRecommendationsForLineUpdate({
|
|
64
|
+
id: transaction.id,
|
|
65
|
+
type: transaction.type,
|
|
66
|
+
}));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
54
69
|
fetchActions.push(saveTransactionCategorizationLocalData({
|
|
55
70
|
selectedTab,
|
|
56
71
|
transactionIds,
|
|
@@ -3,6 +3,7 @@ import recordGet from 'lodash/get';
|
|
|
3
3
|
import uniq from 'lodash/uniq';
|
|
4
4
|
import { toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
|
|
5
5
|
import { isVendorTransaction } from '../../../entity/transaction/stateTypes/vendorTransaction';
|
|
6
|
+
import { getLineIdsWhereEntityStillMatches } from '../../recommendation/recommendationStaleResponseHelper';
|
|
6
7
|
import { filterAutoTabLineItems, mergeTabSpecificLineItems, removeTransactionFromTabView, setEntityRecommendationForLineIdInLocalData, shouldAutoSelectAndAdd, toSetAllLineItemsToCategoryClass, toTransactionDetailLocalData, } from '../helpers/transactionCategorizationLocalDataHelper';
|
|
7
8
|
import { DEFAULT_COMPLETED_SUB_TAB, } from '../types/completedSubTab';
|
|
8
9
|
import { TRANSACTIONS_TABS, } from '../types/transactionsViewState';
|
|
@@ -516,11 +517,66 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
516
517
|
const transactionLocalData = recordGet(draft.transactionCategorizationView[selectedTab]
|
|
517
518
|
.transactionReviewLocalDataById, transactionId.id, undefined);
|
|
518
519
|
if (transactionLocalData != null) {
|
|
519
|
-
|
|
520
|
+
// Stale-response guard. Filter `lineIds` down to lines whose
|
|
521
|
+
// currently selected entity still matches the payload
|
|
522
|
+
// entity, so a response that lands after the user (or the
|
|
523
|
+
// silent backgroundRefetchReviewTab path) wiped local data
|
|
524
|
+
// can't overwrite the restored state. Lines that fail the
|
|
525
|
+
// guard are still released to Completed below so their
|
|
526
|
+
// inline skeletons resolve. Complements the epic-level
|
|
527
|
+
// `takeUntil`.
|
|
528
|
+
//
|
|
529
|
+
// List flow: ONLY vendor uses page-level `selectedEntity`;
|
|
530
|
+
// customer + JE + deposit all use per-line
|
|
531
|
+
// `selectedEntityLine`. This differs from the detail flow
|
|
532
|
+
// (where customer is page-level too) and matches the
|
|
533
|
+
// dichotomy in `setEntityRecommendationForLineIdInLocalData`.
|
|
534
|
+
const usesPageLevelEntity = isVendorTransaction(transaction);
|
|
535
|
+
const matchedLineIds = getLineIdsWhereEntityStillMatches({
|
|
536
|
+
lineIds,
|
|
537
|
+
payloadEntity: entity,
|
|
538
|
+
getCurrentEntityForLine: (lineId) => usesPageLevelEntity
|
|
539
|
+
? transactionLocalData.transactionReviewLocalData.selectedEntity
|
|
540
|
+
: transactionLocalData.transactionReviewLocalData.lineItemById[lineId]?.selectedEntityLine,
|
|
541
|
+
});
|
|
542
|
+
// When every line failed the guard, skip the helper entirely.
|
|
543
|
+
// The list-flow helper's `else` branch writes
|
|
544
|
+
// recommendations + account/class onto vendor-txn lines even
|
|
545
|
+
// when they're not in `recommendationLineIds` (so callers
|
|
546
|
+
// can fold full-transaction recs into untouched lines), and
|
|
547
|
+
// we don't want any of those writes when the whole response
|
|
548
|
+
// is stale. We still flip the per-line fetch flag so
|
|
549
|
+
// skeletons resolve.
|
|
550
|
+
if (matchedLineIds.length === 0) {
|
|
551
|
+
const existingLocalData = transactionLocalData.transactionReviewLocalData;
|
|
552
|
+
const releasedLineItems = { ...existingLocalData.lineItemById };
|
|
553
|
+
lineIds.forEach((lineId) => {
|
|
554
|
+
const lineItem = releasedLineItems[lineId];
|
|
555
|
+
if (lineItem != null) {
|
|
556
|
+
releasedLineItems[lineId] = {
|
|
557
|
+
...lineItem,
|
|
558
|
+
categoryClassRecommendationsFetchState: {
|
|
559
|
+
fetchState: 'Completed',
|
|
560
|
+
error: undefined,
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
draft.transactionCategorizationView[selectedTab].transactionReviewLocalDataById[transaction.id] = {
|
|
566
|
+
transactionId: transaction.id,
|
|
567
|
+
transactionReviewLocalData: {
|
|
568
|
+
...existingLocalData,
|
|
569
|
+
lineItemById: releasedLineItems,
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const newLocalData = setEntityRecommendationForLineIdInLocalData(draft.transactionCategorizationView[selectedTab], transaction, transactionLocalData.transactionReviewLocalData, entity, matchedLineIds, uncategorizedAccounts, action.payload.recommendationWithCOTByLineId, isUncategorizedExpenseCategoryEnabled, isAccountingClassesEnabled);
|
|
520
575
|
// Release the per-line fetch flag for every line this fetch
|
|
521
|
-
// covered
|
|
522
|
-
//
|
|
523
|
-
//
|
|
576
|
+
// covered (matched AND unmatched) so the inline skeleton
|
|
577
|
+
// resolves on stale lines too. Done in-place on the freshly
|
|
578
|
+
// returned local data (the helper produces a new mutable
|
|
579
|
+
// object), before we write it back to the draft.
|
|
524
580
|
lineIds.forEach((lineId) => {
|
|
525
581
|
const lineItem = newLocalData.lineItemById[lineId];
|
|
526
582
|
if (lineItem != null) {
|
|
@@ -537,7 +593,9 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
537
593
|
transactionId: transaction.id,
|
|
538
594
|
transactionReviewLocalData: newLocalData,
|
|
539
595
|
};
|
|
540
|
-
// Mark transaction as dirty (autofill happened)
|
|
596
|
+
// Mark transaction as dirty (autofill happened). We only
|
|
597
|
+
// reach this point with at least one matched line, so
|
|
598
|
+
// recommendations were applied.
|
|
541
599
|
if (!draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData.includes(transaction.id)) {
|
|
542
600
|
draft.transactionCategorizationView[selectedTab].transactionIdsWithUnsavedData = [
|
|
543
601
|
...draft.transactionCategorizationView[selectedTab]
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { of } from 'rxjs';
|
|
2
|
-
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
2
|
+
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
3
3
|
import { removeFiles } from '../../../entity/file/fileReducer';
|
|
4
4
|
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
5
5
|
import { deleteFile, deleteFileFailure, deleteFileSuccess, } from '../fileViewReducer';
|
|
6
|
-
export const deleteFileEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(deleteFile.match),
|
|
6
|
+
export const deleteFileEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(deleteFile.match), switchMap((action) => {
|
|
7
7
|
/**
|
|
8
8
|
* tenantId is required for deleting onboarding files.
|
|
9
9
|
*/
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { of } from 'rxjs';
|
|
2
|
-
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
2
|
+
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
3
3
|
import { removeFiles } from '../../../entity/file/fileReducer';
|
|
4
4
|
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
5
5
|
import { deleteFileList, deleteFileListFailure, deleteFileListSuccess, } from '../fileViewReducer';
|
|
6
|
-
export const deleteFileListEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(deleteFileList.match),
|
|
6
|
+
export const deleteFileListEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(deleteFileList.match), switchMap((action) => {
|
|
7
7
|
const { fileIds } = action.payload;
|
|
8
8
|
return zeniAPI
|
|
9
9
|
.deleteAndGetJSON(`${zeniAPI.apiEndPoints.fileMicroServiceBaseUrl}/1.0/files?query=${encodeURIComponent(JSON.stringify({ file_ids: fileIds }))}`)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { of } from 'rxjs';
|
|
2
|
-
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
2
|
+
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
3
3
|
import { updateFiles } from '../../../entity/file/fileReducer';
|
|
4
4
|
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
5
5
|
import { updateFileName, updateFileNameFailure, updateFileNameSuccess, } from '../fileViewReducer';
|
|
6
|
-
export const updateFileNameEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(updateFileName.match),
|
|
6
|
+
export const updateFileNameEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(updateFileName.match), switchMap((action) => {
|
|
7
7
|
/**
|
|
8
8
|
* tenantId is required for updating onboarding file names.
|
|
9
9
|
*/
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { createSlice } from '@reduxjs/toolkit';
|
|
2
2
|
import { date } from '../../../zeniDayJS';
|
|
3
|
-
import { parseUploadedKybDocument, parseUploadedKybDocumentFailure, parseUploadedKycDocument, parseUploadedKycDocumentFailure, } from '../../spendManagement/commonSetup/kycKybAutofillActions';
|
|
4
|
-
import { emptyAiAgentsActivationSubview, } from './onboardingCustomerViewState';
|
|
5
3
|
const initialStatus = {
|
|
6
4
|
fetchState: 'Not-Started',
|
|
7
5
|
error: undefined,
|
|
@@ -11,31 +9,61 @@ export const initialState = {
|
|
|
11
9
|
saveStatus: initialStatus,
|
|
12
10
|
dashboardLoadedStatus: initialStatus,
|
|
13
11
|
currentStep: 'link_payment_account',
|
|
14
|
-
parsingDocumentFileIds: [],
|
|
15
12
|
companyDetails: {
|
|
16
|
-
autoFilledFields: [],
|
|
17
13
|
localData: undefined,
|
|
18
14
|
},
|
|
19
|
-
companyOfficerUpdateStatus:
|
|
20
|
-
|
|
21
|
-
autoFilledFields: [],
|
|
15
|
+
companyOfficerUpdateStatus: {
|
|
16
|
+
Officer_1: {
|
|
22
17
|
localData: undefined,
|
|
23
18
|
sendOtpStatus: initialStatus,
|
|
24
19
|
otpverificationStatus: initialStatus,
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
20
|
+
},
|
|
21
|
+
Officer_2: {
|
|
22
|
+
localData: undefined,
|
|
23
|
+
sendOtpStatus: initialStatus,
|
|
24
|
+
otpverificationStatus: initialStatus,
|
|
25
|
+
},
|
|
26
|
+
Officer_3: {
|
|
27
|
+
localData: undefined,
|
|
28
|
+
sendOtpStatus: initialStatus,
|
|
29
|
+
otpverificationStatus: initialStatus,
|
|
30
|
+
},
|
|
31
|
+
Officer_4: {
|
|
32
|
+
localData: undefined,
|
|
33
|
+
sendOtpStatus: initialStatus,
|
|
34
|
+
otpverificationStatus: initialStatus,
|
|
35
|
+
},
|
|
36
|
+
Officer_5: {
|
|
37
|
+
localData: undefined,
|
|
38
|
+
sendOtpStatus: initialStatus,
|
|
39
|
+
otpverificationStatus: initialStatus,
|
|
40
|
+
},
|
|
41
|
+
Officer_6: {
|
|
42
|
+
localData: undefined,
|
|
43
|
+
sendOtpStatus: initialStatus,
|
|
44
|
+
otpverificationStatus: initialStatus,
|
|
45
|
+
},
|
|
46
|
+
Officer_7: {
|
|
47
|
+
localData: undefined,
|
|
48
|
+
sendOtpStatus: initialStatus,
|
|
49
|
+
otpverificationStatus: initialStatus,
|
|
50
|
+
},
|
|
51
|
+
Officer_8: {
|
|
52
|
+
localData: undefined,
|
|
53
|
+
sendOtpStatus: initialStatus,
|
|
54
|
+
otpverificationStatus: initialStatus,
|
|
55
|
+
},
|
|
56
|
+
Officer_9: {
|
|
57
|
+
localData: undefined,
|
|
58
|
+
sendOtpStatus: initialStatus,
|
|
59
|
+
otpverificationStatus: initialStatus,
|
|
60
|
+
},
|
|
61
|
+
Officer_10: {
|
|
62
|
+
localData: undefined,
|
|
63
|
+
sendOtpStatus: initialStatus,
|
|
64
|
+
otpverificationStatus: initialStatus,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
39
67
|
primaryContactDetails: {},
|
|
40
68
|
paymentAccountDetails: {
|
|
41
69
|
fetchStatus: initialStatus,
|
|
@@ -63,7 +91,6 @@ export const initialState = {
|
|
|
63
91
|
},
|
|
64
92
|
fetchState: 'Not-Started',
|
|
65
93
|
error: undefined,
|
|
66
|
-
aiAgentsActivation: { ...emptyAiAgentsActivationSubview },
|
|
67
94
|
uiState: {
|
|
68
95
|
isQBOPopupOpen: false,
|
|
69
96
|
isCardPaymentPopupOpen: false,
|
|
@@ -153,126 +180,6 @@ const onboardingCustomerView = createSlice({
|
|
|
153
180
|
};
|
|
154
181
|
},
|
|
155
182
|
},
|
|
156
|
-
acknowledgeOnboardingAiFinanceTeam: {
|
|
157
|
-
reducer(draft) {
|
|
158
|
-
draft.aiFinanceTeamAcknowledgementStatus = {
|
|
159
|
-
fetchState: 'In-Progress',
|
|
160
|
-
error: undefined,
|
|
161
|
-
};
|
|
162
|
-
},
|
|
163
|
-
prepare(companyId) {
|
|
164
|
-
return {
|
|
165
|
-
payload: { companyId },
|
|
166
|
-
};
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
acknowledgeOnboardingAiFinanceTeamSuccess(draft) {
|
|
170
|
-
draft.aiFinanceTeamAcknowledgementStatus = {
|
|
171
|
-
fetchState: 'Completed',
|
|
172
|
-
error: undefined,
|
|
173
|
-
};
|
|
174
|
-
},
|
|
175
|
-
acknowledgeOnboardingAiFinanceTeamFailure: {
|
|
176
|
-
reducer(draft, action) {
|
|
177
|
-
draft.aiFinanceTeamAcknowledgementStatus = {
|
|
178
|
-
fetchState: 'Error',
|
|
179
|
-
error: action.payload,
|
|
180
|
-
};
|
|
181
|
-
},
|
|
182
|
-
prepare(error) {
|
|
183
|
-
return {
|
|
184
|
-
payload: error,
|
|
185
|
-
};
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
acknowledgeOnboardingAiActivationViewed: {
|
|
189
|
-
reducer(draft) {
|
|
190
|
-
draft.aiActivationViewedAcknowledgementStatus = {
|
|
191
|
-
fetchState: 'In-Progress',
|
|
192
|
-
error: undefined,
|
|
193
|
-
};
|
|
194
|
-
},
|
|
195
|
-
prepare(companyId) {
|
|
196
|
-
return {
|
|
197
|
-
payload: { companyId },
|
|
198
|
-
};
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
acknowledgeOnboardingAiActivationViewedSuccess(draft) {
|
|
202
|
-
draft.aiActivationViewedAcknowledgementStatus = {
|
|
203
|
-
fetchState: 'Completed',
|
|
204
|
-
error: undefined,
|
|
205
|
-
};
|
|
206
|
-
},
|
|
207
|
-
acknowledgeOnboardingAiActivationViewedFailure: {
|
|
208
|
-
reducer(draft, action) {
|
|
209
|
-
draft.aiActivationViewedAcknowledgementStatus = {
|
|
210
|
-
fetchState: 'Error',
|
|
211
|
-
error: action.payload,
|
|
212
|
-
};
|
|
213
|
-
},
|
|
214
|
-
prepare(error) {
|
|
215
|
-
return {
|
|
216
|
-
payload: error,
|
|
217
|
-
};
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
fetchAiAgentsActivationStatus(draft) {
|
|
221
|
-
draft.aiAgentsActivation.fetchStatus = {
|
|
222
|
-
fetchState: 'In-Progress',
|
|
223
|
-
error: undefined,
|
|
224
|
-
};
|
|
225
|
-
},
|
|
226
|
-
fetchAiAgentsActivationStatusSuccess: {
|
|
227
|
-
reducer(draft, action) {
|
|
228
|
-
draft.aiAgentsActivation.fetchStatus = {
|
|
229
|
-
fetchState: 'Completed',
|
|
230
|
-
error: undefined,
|
|
231
|
-
};
|
|
232
|
-
draft.aiAgentsActivation.phases = action.payload.phases;
|
|
233
|
-
draft.aiAgentsActivation.counts = action.payload.counts;
|
|
234
|
-
},
|
|
235
|
-
prepare(payload) {
|
|
236
|
-
return { payload };
|
|
237
|
-
},
|
|
238
|
-
},
|
|
239
|
-
fetchAiAgentsActivationStatusFailure: {
|
|
240
|
-
reducer(draft, action) {
|
|
241
|
-
draft.aiAgentsActivation.fetchStatus = {
|
|
242
|
-
fetchState: 'Error',
|
|
243
|
-
error: action.payload,
|
|
244
|
-
};
|
|
245
|
-
},
|
|
246
|
-
prepare(error) {
|
|
247
|
-
return { payload: error };
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
aiAgentsActivationCountsUpdated: {
|
|
251
|
-
reducer(draft, action) {
|
|
252
|
-
draft.aiAgentsActivation.counts = {
|
|
253
|
-
...draft.aiAgentsActivation.counts,
|
|
254
|
-
...action.payload,
|
|
255
|
-
};
|
|
256
|
-
},
|
|
257
|
-
prepare(payload) {
|
|
258
|
-
return { payload };
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
aiAgentsActivationPhaseChanged: {
|
|
262
|
-
reducer(draft, action) {
|
|
263
|
-
const target = draft.aiAgentsActivation.phases;
|
|
264
|
-
const index = target.findIndex((phase) => phase.key === action.payload.key);
|
|
265
|
-
if (index === -1) {
|
|
266
|
-
target.push(action.payload);
|
|
267
|
-
}
|
|
268
|
-
else {
|
|
269
|
-
target[index] = { ...target[index], ...action.payload };
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
prepare(payload) {
|
|
273
|
-
return { payload };
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
183
|
updateOnboardingCustomerViewDashboardLoaded: {
|
|
277
184
|
reducer(draft) {
|
|
278
185
|
draft.dashboardLoadedStatus = {
|
|
@@ -685,81 +592,7 @@ const onboardingCustomerView = createSlice({
|
|
|
685
592
|
clearOnboardingCustomerView(draft) {
|
|
686
593
|
Object.assign(draft, initialState);
|
|
687
594
|
},
|
|
688
|
-
/** Merge AI-parsed officer fields into the officer's onboarding localData. */
|
|
689
|
-
applyKycDocumentAutofillForOnboarding: {
|
|
690
|
-
reducer(draft, action) {
|
|
691
|
-
const { officerType, values, autoFilledFieldNames, fileId } = action.payload;
|
|
692
|
-
const slot = draft.companyOfficerUpdateStatus[officerType];
|
|
693
|
-
slot.localData = Object.assign({}, slot.localData, values);
|
|
694
|
-
slot.autoFilledFields = Array.from(new Set([...slot.autoFilledFields, ...autoFilledFieldNames]));
|
|
695
|
-
draft.parsingDocumentFileIds = draft.parsingDocumentFileIds.filter((id) => id !== fileId);
|
|
696
|
-
},
|
|
697
|
-
prepare(payload) {
|
|
698
|
-
return { payload };
|
|
699
|
-
},
|
|
700
|
-
},
|
|
701
|
-
/** Merge AI-parsed company fields into companyDetails onboarding localData. */
|
|
702
|
-
applyKybDocumentAutofillForOnboarding: {
|
|
703
|
-
reducer(draft, action) {
|
|
704
|
-
const { values, autoFilledFieldNames, fileId } = action.payload;
|
|
705
|
-
draft.companyDetails.localData = Object.assign({}, draft.companyDetails.localData, values);
|
|
706
|
-
const prev = draft.companyDetails.autoFilledFields ?? [];
|
|
707
|
-
draft.companyDetails.autoFilledFields = Array.from(new Set([...prev, ...autoFilledFieldNames]));
|
|
708
|
-
draft.parsingDocumentFileIds = draft.parsingDocumentFileIds.filter((id) => id !== fileId);
|
|
709
|
-
},
|
|
710
|
-
prepare(payload) {
|
|
711
|
-
return { payload };
|
|
712
|
-
},
|
|
713
|
-
},
|
|
714
|
-
/** Wipe AI-autofill badges during onboarding. */
|
|
715
|
-
clearKycKybAutofillForOnboarding: {
|
|
716
|
-
reducer(draft, action) {
|
|
717
|
-
const { officerType } = action.payload;
|
|
718
|
-
if (officerType != null) {
|
|
719
|
-
draft.companyOfficerUpdateStatus[officerType].autoFilledFields = [];
|
|
720
|
-
}
|
|
721
|
-
else {
|
|
722
|
-
draft.companyDetails.autoFilledFields = [];
|
|
723
|
-
Object.keys(draft.companyOfficerUpdateStatus).forEach((key) => {
|
|
724
|
-
draft.companyOfficerUpdateStatus[key].autoFilledFields = [];
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
|
-
},
|
|
728
|
-
prepare(payload = {}) {
|
|
729
|
-
return { payload };
|
|
730
|
-
},
|
|
731
|
-
},
|
|
732
|
-
},
|
|
733
|
-
extraReducers: (builder) => {
|
|
734
|
-
const addParsingFileId = (draft, fileId) => {
|
|
735
|
-
if (!draft.parsingDocumentFileIds.includes(fileId)) {
|
|
736
|
-
draft.parsingDocumentFileIds.push(fileId);
|
|
737
|
-
}
|
|
738
|
-
};
|
|
739
|
-
const removeParsingFileId = (draft, fileId) => {
|
|
740
|
-
draft.parsingDocumentFileIds = draft.parsingDocumentFileIds.filter((id) => id !== fileId);
|
|
741
|
-
};
|
|
742
|
-
builder.addCase(parseUploadedKycDocument, (draft, action) => {
|
|
743
|
-
if (action.payload.target === 'onboarding') {
|
|
744
|
-
addParsingFileId(draft, action.payload.fileId);
|
|
745
|
-
}
|
|
746
|
-
});
|
|
747
|
-
builder.addCase(parseUploadedKybDocument, (draft, action) => {
|
|
748
|
-
if (action.payload.target === 'onboarding') {
|
|
749
|
-
addParsingFileId(draft, action.payload.fileId);
|
|
750
|
-
}
|
|
751
|
-
});
|
|
752
|
-
builder.addCase(parseUploadedKycDocumentFailure, (draft, action) => {
|
|
753
|
-
if (action.payload.target === 'onboarding') {
|
|
754
|
-
removeParsingFileId(draft, action.payload.fileId);
|
|
755
|
-
}
|
|
756
|
-
});
|
|
757
|
-
builder.addCase(parseUploadedKybDocumentFailure, (draft, action) => {
|
|
758
|
-
if (action.payload.target === 'onboarding') {
|
|
759
|
-
removeParsingFileId(draft, action.payload.fileId);
|
|
760
|
-
}
|
|
761
|
-
});
|
|
762
595
|
},
|
|
763
596
|
});
|
|
764
|
-
export const { fetchOnboardingCustomerView, fetchOnboardingCustomerSetupView, fetchOnboardingCustomerSetupViewSuccess, fetchOnboardingCustomerSetupViewFailure, updateOnboardingCustomerViewCompleteStatus, updateOnboardingCustomerViewCompleteStatusSuccess, updateOnboardingCustomerViewCompleteStatusFailure,
|
|
597
|
+
export const { fetchOnboardingCustomerView, fetchOnboardingCustomerSetupView, fetchOnboardingCustomerSetupViewSuccess, fetchOnboardingCustomerSetupViewFailure, updateOnboardingCustomerViewCompleteStatus, updateOnboardingCustomerViewCompleteStatusSuccess, updateOnboardingCustomerViewCompleteStatusFailure, updateOnboardingCustomerViewDashboardLoaded, updateOnboardingCustomerViewDashboardLoadedSuccess, updateOnboardingCustomerViewDashboardLoadedFailure, updateOnboardingCustomerView, updateOnboardingCustomerViewSuccess, updateOnboardingCustomerViewFailure, updateOnboardingPaymentAccountStatus, updateOnboardingPaymentAccountStatusSuccess, updateOnboardingPaymentAccountStatusFailure, updateOnboardingPaymentAccountLoginStatus, updateOnboardingPaymentAccountLoginStatusSuccess, updateOnboardingPaymentAccountLoginStatusFailure, establishOnboardingPlaidConnection, establishOnboardingPlaidConnectionSuccess, establishOnboardingPlaidConnectionFailure, getOnboardingPlaidLinkToken, getOnboardingPlaidLinkTokenSuccess, getOnboardingPlaidLinkTokenFailure, updateCurrentStep, updateOnboardingCustomerViewAccountDetails, updateOnboardingCustomerViewLocalStoreData, saveOnboardingCustomerViewDataInLocalStore, saveOnboardingCompnayOfficerPhoneInLocalStore, updateOnboardingCustomerViewUIState, sendOnboardingOfficerOtp, sendOnboardingOfficerOtpSuccess, sendOnboardingOfficerOtpFailure, resendOnboardingOfficerOtp, verifyOnboardingOfficerOtp, verifyOnboardingOfficerOtpSuccess, verifyOnboardingOfficerOtpFailure, clearOnboardingCustomerViewDataInLocalStore, clearOnboardingCustomerView, } = onboardingCustomerView.actions;
|
|
765
598
|
export default onboardingCustomerView.reducer;
|
|
@@ -11,7 +11,7 @@ import { getCompanyAndIdentityDetails, } from '../../spendManagement/commonSetup
|
|
|
11
11
|
import { ALL_ONBOARDING_STEPS, ALL_ZENI_USER_ONBOARDING_STEPS, REQUIRED_ONBOARDING_STEPS, ZENI_USER_REQUIRED_ONBOARDING_STEPS, toOnboardingStepType, } from './onboardingCustomerViewState';
|
|
12
12
|
export function getOnboardingCustomerView(state, companyId) {
|
|
13
13
|
const { fileState, paymentAccountState, onboardingCustomerViewState, userState, userRoleState, subscriptionPaymentAccountState, companyConfigState, cardPaymentViewState, countryListState, addressViewState, } = state;
|
|
14
|
-
const { companyDetails, companyOfficerUpdateStatus, primaryContactDetails, subscriptionPaymentAccountIds, paymentAccountDetails, plaidConnectionDetails,
|
|
14
|
+
const { companyDetails, companyOfficerUpdateStatus, primaryContactDetails, subscriptionPaymentAccountIds, paymentAccountDetails, plaidConnectionDetails, updateStatus, saveStatus, currentStep, uiState, fetchState, error, } = onboardingCustomerViewState;
|
|
15
15
|
const { paymentAccountIds } = paymentAccountDetails;
|
|
16
16
|
let subscriptionPaymentAccount;
|
|
17
17
|
let paymentAccount;
|
|
@@ -54,7 +54,6 @@ export function getOnboardingCustomerView(state, companyId) {
|
|
|
54
54
|
companyDetails,
|
|
55
55
|
companyOfficerUpdateStatus,
|
|
56
56
|
primaryContactDetails,
|
|
57
|
-
parsingDocumentFileIds,
|
|
58
57
|
});
|
|
59
58
|
const companyAddress = addressViewState.newAddressState?.company_address;
|
|
60
59
|
if (companyAddress == null &&
|
|
@@ -120,7 +119,6 @@ export function getOnboardingCustomerView(state, companyId) {
|
|
|
120
119
|
return {
|
|
121
120
|
companyDetailsLocalData,
|
|
122
121
|
companyOfficerLocalData,
|
|
123
|
-
parsingDocumentFileIds,
|
|
124
122
|
subscriptionBillingAddress: subscriptionBillingAddress?.addressToCreate,
|
|
125
123
|
companyView,
|
|
126
124
|
allNationalityCountries,
|
|
@@ -325,7 +323,3 @@ export const getProductSettingsString = (productSettings) => {
|
|
|
325
323
|
.map((key) => productKeyMap[key])
|
|
326
324
|
.join(',');
|
|
327
325
|
};
|
|
328
|
-
const fromActivationView = (state) => state.onboardingCustomerViewState.aiAgentsActivation;
|
|
329
|
-
export const getAiAgentsActivationSubview = (state) => fromActivationView(state);
|
|
330
|
-
export const getAiAgentsActivationPhases = (state) => fromActivationView(state).phases;
|
|
331
|
-
export const getAiAgentsActivationCounts = (state) => fromActivationView(state).counts;
|
|
@@ -26,15 +26,3 @@ export const ALL_ONBOARDING_STEPS = [
|
|
|
26
26
|
...ALL_ZENI_USER_ONBOARDING_STEPS_WITHOUT_ACCOUNT,
|
|
27
27
|
];
|
|
28
28
|
export const toOnboardingStepType = (v) => stringToUnion(v, ALL_ONBOARDING_STEPS);
|
|
29
|
-
export const emptyAiAgentsActivationCounts = {
|
|
30
|
-
customerCount: 0,
|
|
31
|
-
glAccountCount: 0,
|
|
32
|
-
targetTransactionCount: 0,
|
|
33
|
-
transactionCount: 0,
|
|
34
|
-
vendorCount: 0,
|
|
35
|
-
};
|
|
36
|
-
export const emptyAiAgentsActivationSubview = {
|
|
37
|
-
counts: emptyAiAgentsActivationCounts,
|
|
38
|
-
fetchStatus: { fetchState: 'Not-Started', error: undefined },
|
|
39
|
-
phases: [],
|
|
40
|
-
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { concat, of, timer } from 'rxjs';
|
|
2
|
-
import { catchError, debounce, filter, groupBy, mergeMap, switchMap, } from 'rxjs/operators';
|
|
2
|
+
import { catchError, debounce, filter, groupBy, mergeMap, switchMap, takeUntil, } from 'rxjs/operators';
|
|
3
3
|
import { getIsAccountingClassesEnabled } from '../../entity/tenant/tenantSelector';
|
|
4
4
|
import { getTransactionWithCOT, getTransactionWithCOTFromRecommendations, } from '../../entity/transaction/transactionHelper';
|
|
5
5
|
import { createZeniAPIStatus, isSuccessResponse } from '../../responsePayload';
|
|
@@ -7,7 +7,7 @@ import { getUncategorizedAccounts } from '../accountList/accountListSelector';
|
|
|
7
7
|
import { markCategoryClassRecommendationsCompletedForCategorization, markCategoryClassRecommendationsFailureForCategorization, markCategoryClassRecommendationsInProgressForCategorization, setEntityRecommendationForLineIdsForCategorization, } from '../expenseAutomationView/reducers/transactionsViewReducer';
|
|
8
8
|
import { markCategoryClassRecommendationsCompletedForTransactionDetail, markCategoryClassRecommendationsFailureForTransactionDetail, markCategoryClassRecommendationsInProgressForTransactionDetail, setEntityRecommendationForLineIdsForTransactionDetail, } from '../transactionDetail/transactionDetailReducer';
|
|
9
9
|
import { buildRecommendationUrl, getRecommendationByLineIdWithCOTFromRecommendationPayloadWithCOT, } from './recommendationHelper';
|
|
10
|
-
import { fetchEntityRecommendationsForLineUpdate } from './recommendationReducer';
|
|
10
|
+
import { cancelEntityRecommendationsForLineUpdate, fetchEntityRecommendationsForLineUpdate, } from './recommendationReducer';
|
|
11
11
|
// Close the per-target groupBy bucket after this much idle time so inactive
|
|
12
12
|
// groups don't accumulate forever (each group holds a Subject + a chain of
|
|
13
13
|
// active subscribers; without a duration selector groupBy retains them for
|
|
@@ -83,5 +83,14 @@ export const fetchEntityRecommendationsForLineUpdateEpic = (actions$, state$, ze
|
|
|
83
83
|
? markCategoryClassRecommendationsFailureForCategorization(payload.selectedTab, transactionId, lineIds, status)
|
|
84
84
|
: markCategoryClassRecommendationsFailureForTransactionDetail(transactionId, lineIds, status));
|
|
85
85
|
}));
|
|
86
|
-
|
|
86
|
+
// takeUntil kills the in-flight HTTP and suppresses every
|
|
87
|
+
// downstream success/failure/markCompleted emission once a
|
|
88
|
+
// cancel action lands for the same transactionId. groupBy +
|
|
89
|
+
// switchMap already handle re-fetch-on-same-target; takeUntil
|
|
90
|
+
// covers the user-initiated Cancel (detail footer + list
|
|
91
|
+
// footer) and the silent backgroundRefetchReviewTab path,
|
|
92
|
+
// which both wipe local data without dispatching another
|
|
93
|
+
// fetch on the same target.
|
|
94
|
+
const cancel$ = actions$.pipe(filter(cancelEntityRecommendationsForLineUpdate.match), filter((a) => a.payload.transactionId.id === transactionId.id));
|
|
95
|
+
return concat(of(startAction), httpStream$.pipe(takeUntil(cancel$)));
|
|
87
96
|
}))));
|
|
@@ -58,6 +58,23 @@ const recommendation = createSlice({
|
|
|
58
58
|
return { payload: args };
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
|
+
// No-op reducer; the action exists purely so the line-update epic
|
|
62
|
+
// can observe it via `takeUntil` and kill an in-flight HTTP request
|
|
63
|
+
// when the user (or a background refresh) wipes local data before
|
|
64
|
+
// the response lands. Without this, a stale response races the reset
|
|
65
|
+
// and overwrites the user's intent. Cancel is keyed by
|
|
66
|
+
// `transactionId` only: per-page Cancel buttons cancel everything
|
|
67
|
+
// under that transaction (including JE sibling line sets), which
|
|
68
|
+
// matches the UI semantics on both detail and categorization pages.
|
|
69
|
+
cancelEntityRecommendationsForLineUpdate: {
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
71
|
+
reducer(_draft, _action) {
|
|
72
|
+
// intentionally empty
|
|
73
|
+
},
|
|
74
|
+
prepare(transactionId) {
|
|
75
|
+
return { payload: { transactionId } };
|
|
76
|
+
},
|
|
77
|
+
},
|
|
61
78
|
fetchRecommendationByEntityName: {
|
|
62
79
|
reducer(draft, action) {
|
|
63
80
|
const { entityName, entityType } = action.payload;
|
|
@@ -136,7 +153,7 @@ const recommendation = createSlice({
|
|
|
136
153
|
},
|
|
137
154
|
},
|
|
138
155
|
});
|
|
139
|
-
export const { fetchEntityRecommendationsByTransactionId, fetchEntityRecommendationsForLineUpdate, fetchRecommendationByEntityName, fetchRecommendationByEntityId, updateRecommendationForEntity, updateRecommendationForEntityFailure, indicateReadyToInitializeRecommendation, clearRecommendation, } = recommendation.actions;
|
|
156
|
+
export const { fetchEntityRecommendationsByTransactionId, fetchEntityRecommendationsForLineUpdate, cancelEntityRecommendationsForLineUpdate, fetchRecommendationByEntityName, fetchRecommendationByEntityId, updateRecommendationForEntity, updateRecommendationForEntityFailure, indicateReadyToInitializeRecommendation, clearRecommendation, } = recommendation.actions;
|
|
140
157
|
export default recommendation.reducer;
|
|
141
158
|
const doUpdateRecommendationForEntity = (draft, entityId, entityType, payload) => {
|
|
142
159
|
const key = getEntityRecommendationKey(entityType, entityId);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Two entities refer to the same real-world thing iff they share both
|
|
2
|
+
// type and id. We deliberately compare on these fields only — qboId /
|
|
3
|
+
// zeniId can lag during entity-create flows where the persisted id
|
|
4
|
+
// hasn't been roundtripped yet, while `id` is the local-source-of-truth
|
|
5
|
+
// the line carries.
|
|
6
|
+
const entitiesMatch = (a, b) => a != null && b != null && a.type === b.type && a.id === b.id;
|
|
7
|
+
// Filters `lineIds` down to those whose currently selected entity (as
|
|
8
|
+
// reported by the caller) still matches the payload entity at the
|
|
9
|
+
// moment the success reducer fires. Used by the two
|
|
10
|
+
// `setEntityRecommendationForLineIds…` reducers as a defensive guard
|
|
11
|
+
// against stale responses landing after the user (or a background
|
|
12
|
+
// refresh) has changed or wiped their selection.
|
|
13
|
+
//
|
|
14
|
+
// Each flow decides separately where the "current entity" lives,
|
|
15
|
+
// because the rules diverge:
|
|
16
|
+
// - Detail flow: vendor + customer transactions read from page-level
|
|
17
|
+
// `selectedEntity`; JE / deposit reads per-line `lineEntity`.
|
|
18
|
+
// - List flow: ONLY vendor reads from page-level `selectedEntity`;
|
|
19
|
+
// customer, JE, deposit all read per-line `selectedEntityLine`.
|
|
20
|
+
// Pushing the lookup into the caller keeps this helper a pure
|
|
21
|
+
// comparator and avoids the helper having to know which flow / page-
|
|
22
|
+
// level rule applies.
|
|
23
|
+
export const getLineIdsWhereEntityStillMatches = (args) => {
|
|
24
|
+
const { lineIds, payloadEntity, getCurrentEntityForLine } = args;
|
|
25
|
+
return lineIds.filter((lineId) => entitiesMatch(payloadEntity, getCurrentEntityForLine(lineId)));
|
|
26
|
+
};
|