@zeniai/client-epic-state 5.3.16 → 5.3.18-betaAK1
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/tenant/epic/fetchAllTenantsEpic.d.ts +2 -4
- package/lib/entity/tenant/epic/fetchAllTenantsEpic.js +45 -42
- package/lib/entity/tenant/tenantPayload.d.ts +17 -1
- package/lib/entity/tenant/tenantReducer.d.ts +2 -2
- package/lib/entity/tenant/tenantReducer.js +29 -18
- package/lib/entity/tenant/tenantSelector.d.ts +14 -6
- package/lib/entity/tenant/tenantSelector.js +98 -97
- package/lib/entity/tenant/tenantState.d.ts +10 -0
- package/lib/esm/entity/tenant/epic/fetchAllTenantsEpic.js +49 -46
- package/lib/esm/entity/tenant/tenantReducer.js +28 -17
- package/lib/esm/entity/tenant/tenantSelector.js +94 -96
- package/lib/esm/index.js +2 -2
- package/lib/esm/view/dashboard/dashboardEpic.js +0 -4
- package/lib/esm/view/dashboard/dashboardSelector.js +2 -3
- package/lib/esm/view/financeStatement/financeStatementEpic.js +1 -5
- package/lib/esm/view/people/peoplePageEpic.js +0 -4
- package/lib/index.d.ts +2 -2
- package/lib/index.js +49 -47
- package/lib/view/dashboard/dashboardEpic.js +0 -4
- package/lib/view/dashboard/dashboardSelector.d.ts +0 -2
- package/lib/view/dashboard/dashboardSelector.js +1 -2
- package/lib/view/financeStatement/financeStatementEpic.js +1 -5
- package/lib/view/people/peoplePageEpic.js +0 -4
- package/package.json +1 -1
|
@@ -1,62 +1,65 @@
|
|
|
1
|
-
import { defer, from, of } from 'rxjs';
|
|
2
|
-
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
1
|
+
import { defer, from, merge, of } from 'rxjs';
|
|
2
|
+
import { catchError, filter, map, mergeMap, switchMap, take, } from 'rxjs/operators';
|
|
3
3
|
import { toMonth } from '../../../commonStateTypes/timePeriod';
|
|
4
|
-
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
4
|
+
import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
|
|
5
5
|
import { getSessionEndpointsLoader } from '../../../sessionEndpointsLoader';
|
|
6
|
-
import { initializeSelectedPeriodForTenants } from '../../../view/expenseAutomationView/expenseAutomationViewReducer';
|
|
7
6
|
import { date, dateNow } from '../../../zeniDayJS';
|
|
8
|
-
import {
|
|
9
|
-
|
|
7
|
+
import { fetchActiveTenant, fetchAllTenants, updateTenantFailure, updateTenantSuccess, updateTenantSummariesSuccess, updateTenantsFailure, } from '../tenantReducer';
|
|
8
|
+
// Only what the tenant switcher and tenant lookups read (~0.3 KB a tenant
|
|
9
|
+
// instead of ~18 KB with full records and companies). A tenant's full record
|
|
10
|
+
// comes from `fetchActiveTenant` when it is opened.
|
|
11
|
+
const TENANT_SUMMARIES_QUERY = {
|
|
12
|
+
is_full: false,
|
|
13
|
+
is_include_companies_in_response: false,
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The tenant the app lands on: the selected one, or the most recently opened
|
|
17
|
+
* when none is selected (sign-in, the root route).
|
|
18
|
+
*/
|
|
19
|
+
function getLandingTenantId(tenants, currentTenantId) {
|
|
20
|
+
if (currentTenantId != null &&
|
|
21
|
+
tenants.some((tenant) => tenant.tenant_id === currentTenantId)) {
|
|
22
|
+
return currentTenantId;
|
|
23
|
+
}
|
|
24
|
+
const lastCheckIn = (tenant) => tenant.user_last_check_in_time_into_tenant != null
|
|
25
|
+
? date(tenant.user_last_check_in_time_into_tenant).valueOf()
|
|
26
|
+
: 0;
|
|
27
|
+
return tenants.reduce((mostRecent, tenant) => mostRecent == null || lastCheckIn(tenant) > lastCheckIn(mostRecent)
|
|
28
|
+
? tenant
|
|
29
|
+
: mostRecent, undefined)?.tenant_id;
|
|
30
|
+
}
|
|
10
31
|
export const fetchAllTenantsEpic = (actions$, state, zeniAPI) => {
|
|
11
|
-
const queryParam = {
|
|
12
|
-
is_include_companies_in_response: true,
|
|
13
|
-
};
|
|
14
32
|
return actions$.pipe(filter(fetchAllTenants.match), switchMap((action) => {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const currentTenantId = state.value.tenantState.currentTenantId;
|
|
19
|
-
if (isSuccessResponse(response) && response.data != null) {
|
|
20
|
-
const defaultSelectedPeriodsForTenants = getDefaultSelectedPeriodsForTenants(response.data);
|
|
21
|
-
let actions = [
|
|
22
|
-
updateTenantsSuccess(response.data),
|
|
23
|
-
initializeSelectedPeriodForTenants({
|
|
24
|
-
selectedPeriodByTenantId: defaultSelectedPeriodsForTenants,
|
|
25
|
-
}),
|
|
26
|
-
];
|
|
27
|
-
if (response.data.companies != null) {
|
|
28
|
-
let companiesPayload = response.data.companies;
|
|
29
|
-
if (currentTenantId != null) {
|
|
30
|
-
// filter out this tenant ID related company detail
|
|
31
|
-
// Find companyID for this tenant
|
|
32
|
-
const currentTenantPayload = response.data.tenants.find((tenant) => tenant.tenant_id === currentTenantId);
|
|
33
|
-
if (currentTenantPayload != null) {
|
|
34
|
-
companiesPayload = response.data.companies.filter((company) => company.company_id !== currentTenantPayload.company_id);
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
console.warn('Selected tenant record not found in response', currentTenantId);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
actions = [
|
|
41
|
-
updateCompanies({
|
|
42
|
-
payload: companiesPayload,
|
|
43
|
-
schema: {},
|
|
44
|
-
}),
|
|
45
|
-
...actions,
|
|
46
|
-
];
|
|
47
|
-
}
|
|
48
|
-
return from(actions);
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
33
|
+
const { userId, zeniSessionId } = action.payload;
|
|
34
|
+
const fetchTenants$ = defer(() => zeniAPI.getJSON(`${zeniAPI.apiEndPoints.tenantMicroServiceBaseUrl}/1.0/users/${userId}/tenants?query=${encodeURIComponent(JSON.stringify(TENANT_SUMMARIES_QUERY))}`, Object.assign({ 'zeni-user-id': userId }, { 'zeni-tenant-id': '' }, zeniSessionId != null ? { 'zeni-session-id': zeniSessionId } : {}))).pipe(mergeMap((response) => {
|
|
35
|
+
if (!isSuccessResponse(response) || response.data == null) {
|
|
51
36
|
return of(updateTenantsFailure(response.status));
|
|
52
37
|
}
|
|
38
|
+
const { tenants } = response.data;
|
|
39
|
+
if (tenants.length === 0) {
|
|
40
|
+
return of(updateTenantsFailure(createZeniAPIStatus('Unexpected error', 'Empty list of tenants')));
|
|
41
|
+
}
|
|
42
|
+
const tenantsLoaded = updateTenantSummariesSuccess(tenants);
|
|
43
|
+
const { tenantState } = state.value;
|
|
44
|
+
const landingTenantId = getLandingTenantId(tenants, tenantState.currentTenantId);
|
|
45
|
+
const isLandingTenantLoadedOrLoading = landingTenantId == null ||
|
|
46
|
+
tenantState.tenantsById[landingTenantId] != null ||
|
|
47
|
+
tenantState.activeTenantFetchState.fetchState === 'In-Progress';
|
|
48
|
+
if (isLandingTenantLoadedOrLoading) {
|
|
49
|
+
return of(tenantsLoaded);
|
|
50
|
+
}
|
|
51
|
+
// Screens that wait for this fetch (sign-in, the root route) read the
|
|
52
|
+
// landing tenant's roles and product settings, which the list does
|
|
53
|
+
// not carry, so it completes only once that tenant's record is in.
|
|
54
|
+
return merge(actions$.pipe(filter((landingAction) => updateTenantSuccess.match(landingAction) ||
|
|
55
|
+
updateTenantFailure.match(landingAction)), take(1), map(() => tenantsLoaded)), of(fetchActiveTenant(userId, landingTenantId, zeniSessionId)));
|
|
53
56
|
}), catchError((error) => of(updateTenantsFailure(createZeniAPIStatus('Unexpected error', 'Fetch All Tenants REST API call errored out' +
|
|
54
57
|
JSON.stringify(error))))));
|
|
55
58
|
const sessionEndpointsLoader = getSessionEndpointsLoader();
|
|
56
59
|
if (sessionEndpointsLoader == null) {
|
|
57
60
|
return fetchTenants$;
|
|
58
61
|
}
|
|
59
|
-
return defer(() => from(sessionEndpointsLoader(
|
|
62
|
+
return defer(() => from(sessionEndpointsLoader(userId, zeniSessionId))).pipe(switchMap(() => fetchTenants$), catchError((error) => of(updateTenantsFailure(createZeniAPIStatus('Unexpected error', 'Loading session endpoints errored out: ' +
|
|
60
63
|
JSON.stringify(error))))));
|
|
61
64
|
}));
|
|
62
65
|
};
|
|
@@ -17,6 +17,7 @@ export const initialState = {
|
|
|
17
17
|
error: undefined,
|
|
18
18
|
accountLocked: false,
|
|
19
19
|
tenantsById: {},
|
|
20
|
+
tenantSummariesById: {},
|
|
20
21
|
onboardingTenantsById: {},
|
|
21
22
|
tenantIds: [],
|
|
22
23
|
signInState: 'Not-Started',
|
|
@@ -107,26 +108,24 @@ const tenant = createSlice({
|
|
|
107
108
|
});
|
|
108
109
|
},
|
|
109
110
|
},
|
|
110
|
-
|
|
111
|
-
const { tenants, user } = action.payload;
|
|
111
|
+
updateTenantSummariesSuccess(draft, action) {
|
|
112
112
|
draft.fetchState = 'Completed';
|
|
113
113
|
draft.error = undefined;
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
114
|
+
const tenantSummariesById = {};
|
|
115
|
+
action.payload.forEach((payload) => {
|
|
116
|
+
tenantSummariesById[payload.tenant_id] =
|
|
117
|
+
mapTenantSummaryPayloadToTenantSummary(payload);
|
|
118
|
+
});
|
|
119
|
+
draft.tenantSummariesById = tenantSummariesById;
|
|
120
|
+
draft.tenantIds = Object.keys(tenantSummariesById);
|
|
121
|
+
// A tenant missing from the list is no longer accessible to the user.
|
|
122
|
+
const accessibleTenantsById = {};
|
|
123
|
+
Object.values(draft.tenantsById).forEach((tenant) => {
|
|
124
|
+
if (tenantSummariesById[tenant.tenantId] != null) {
|
|
125
|
+
accessibleTenantsById[tenant.tenantId] = tenant;
|
|
125
126
|
}
|
|
126
127
|
});
|
|
127
|
-
draft.tenantsById =
|
|
128
|
-
draft.tenantIds = tenantIds;
|
|
129
|
-
draft.loggedInUser = assignWith(draft.loggedInUser ?? {}, firstUser, (objValue, srcValue) => srcValue ?? objValue);
|
|
128
|
+
draft.tenantsById = accessibleTenantsById;
|
|
130
129
|
},
|
|
131
130
|
updateTenantsFailure(draft, action) {
|
|
132
131
|
draft.fetchState = 'Error';
|
|
@@ -134,6 +133,7 @@ const tenant = createSlice({
|
|
|
134
133
|
draft.error = action.payload;
|
|
135
134
|
draft.tenantIds = [];
|
|
136
135
|
draft.tenantsById = {};
|
|
136
|
+
draft.tenantSummariesById = {};
|
|
137
137
|
},
|
|
138
138
|
updateTenantSuccess(draft, action) {
|
|
139
139
|
const { tenants, user } = action.payload;
|
|
@@ -242,6 +242,7 @@ const tenant = createSlice({
|
|
|
242
242
|
draft.signInState = 'Error';
|
|
243
243
|
draft.tenantIds = [];
|
|
244
244
|
draft.tenantsById = {};
|
|
245
|
+
draft.tenantSummariesById = {};
|
|
245
246
|
},
|
|
246
247
|
updateTenantReimbursementInfo(draft, action) {
|
|
247
248
|
const { tenant_id, reimbursement_info } = action.payload;
|
|
@@ -934,7 +935,7 @@ const tenant = createSlice({
|
|
|
934
935
|
});
|
|
935
936
|
},
|
|
936
937
|
});
|
|
937
|
-
export const { updateTenants, fetchAllTenants,
|
|
938
|
+
export const { updateTenants, fetchAllTenants, updateTenantSummariesSuccess, updateTenantsFailure, fetchActiveTenant, updateTenantSuccess, updateTenantFailure, updateCurrentTenant, fetchExcludedResources, updateExcludedResourcesSuccess, updateExcludedResourcesFailure, doSignIn, doMagicLinkSignIn, magicLinkSignInSuccess, magicLinkSignInFailure, sendEmailMagicLinkToUser, sendEmailMagicLinkToUserSuccess, sendEmailMagicLinkToUserFailure, updateSignInState, doSignOut, signOutSuccess, sendSessionHeartbeat, sessionHeartbeatSuccess, sessionHeartbeatFailure, updateLoggedInUser, fetchExternalConnections, saveExternalConnection, saveExternalConnectionSuccess, saveExternalConnectionFailure, fetchExternalConnectionsFailure, fetchExternalConnectionsSuccess, saveAPIKeyConnection, saveAPIKeyConnectionSuccess, saveAPIKeyConnectionFailure, saveOAuthConnection, saveOAuthConnectionSuccess, saveOAuthConnectionFailure, saveConnectorCredentials, saveConnectorCredentialsSuccess, saveConnectorCredentialsFailure, initEmailConnectOAuth, initEmailConnectOAuthSuccess, initEmailConnectOAuthFailure, deleteConnection, deleteConnectionSuccess, deleteConnectionFailure, fetchSubscriptionSummaryForTenant, updateSubscriptionSummaryForTenantSuccess, updateSubscriptionSummaryForTenantFailure, updateOnboardingTenants, removeOnboardingTenant, updateTenantReimbursementInfo, updateReferViewedForLoggedInUser, resetSignInState, trigger2FA, updateTenantAccountingClassesEnabled, updateTenantAccountingProjectsEnabled, updateTenantAutoReviewIncomeWithoutCustomer, updateTenantProductSettings, updateTenantCapitalizationOnboardingDismissed, updateTenantCapitalizationAccountOverride, updateTenantMasterTOSInfo, verifyDeviceWithTwoFA, verifyDeviceWithTwoFASuccess, verifyDeviceWithTwoFAFailure, updateTreasuryVideoViewedForLoggedInUser, updateTreasuryPromoRemindMeLaterClickedForLoggedInUser, updateTreasuryPromoIntroClosedByOutsideClickForLoggedInUser, resendVerifyDeviceOTP, resendVerifyDeviceOTPSuccess, resendVerifyDeviceOTPFailure, } = tenant.actions;
|
|
938
939
|
export default tenant.reducer;
|
|
939
940
|
/**
|
|
940
941
|
* Applies treasury promo user fields from the tenants API user block when present.
|
|
@@ -976,6 +977,16 @@ function mapTenantsPayloadToState(tenantsPayload, userTenantPayload) {
|
|
|
976
977
|
const firstUser = mapTenantPayloadToUser(tenantsPayload[0]);
|
|
977
978
|
return [tenantsById, tenantIds, firstUser];
|
|
978
979
|
}
|
|
980
|
+
function mapTenantSummaryPayloadToTenantSummary(payload) {
|
|
981
|
+
return {
|
|
982
|
+
companyId: payload.company_id,
|
|
983
|
+
companyName: payload.company_name,
|
|
984
|
+
tenantEmailDomain: payload.tenant_email_domain,
|
|
985
|
+
tenantId: payload.tenant_id,
|
|
986
|
+
// A tenant never opened sorts last, as in `mapTenantPayloadToTenant`.
|
|
987
|
+
userLastCheckInTime: date(payload.user_last_check_in_time_into_tenant ?? '1970-01-01'),
|
|
988
|
+
};
|
|
989
|
+
}
|
|
979
990
|
function mapTenantPayloadToTenant(payload, userTenantPayload) {
|
|
980
991
|
const tenant = {
|
|
981
992
|
accountingClassIDs: payload.accounting_class_ids,
|
|
@@ -16,50 +16,75 @@ export function getTenant(tenantState, companyState, tenantId) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
export function getTenantIdByCompanyId(tenantState, companyId) {
|
|
19
|
-
return Object.values(tenantState.tenantsById).find((tenant) => tenant.companyId === companyId)
|
|
19
|
+
return (Object.values(tenantState.tenantsById).find((tenant) => tenant.companyId === companyId) ??
|
|
20
|
+
Object.values(tenantState.tenantSummariesById).find((tenantSummary) => tenantSummary.companyId === companyId))?.tenantId;
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Every tenant in the list, most recently opened first. Roles are known only
|
|
24
|
+
* for tenants whose full record is loaded; the list itself carries none.
|
|
25
|
+
*/
|
|
26
|
+
function getTenantListEntriesByCheckInDate(tenantState) {
|
|
27
|
+
const entries = tenantState.tenantIds
|
|
28
|
+
.map((tenantId) => {
|
|
29
|
+
const tenant = tenantState.tenantsById[tenantId];
|
|
30
|
+
const tenantSummary = tenantState.tenantSummariesById[tenantId];
|
|
31
|
+
if (tenant != null) {
|
|
32
|
+
return {
|
|
33
|
+
companyId: tenant.companyId,
|
|
34
|
+
companyName: tenant.companyName,
|
|
35
|
+
tenantEmailDomain: tenant.tenantEmailDomain,
|
|
36
|
+
tenantId: tenant.tenantId,
|
|
37
|
+
userLastCheckInTime: tenant.userLastCheckInTime,
|
|
38
|
+
userRole: tenant.userRole,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return tenantSummary != null
|
|
42
|
+
? { ...tenantSummary, userRole: [] }
|
|
43
|
+
: undefined;
|
|
44
|
+
})
|
|
45
|
+
.filter((entry) => entry != null);
|
|
46
|
+
return orderBy(entries, (entry) => entry.userLastCheckInTime.valueOf(), 'desc');
|
|
30
47
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
companyName,
|
|
46
|
-
userRole,
|
|
47
|
-
companyLocaleInfo,
|
|
48
|
-
};
|
|
48
|
+
/**
|
|
49
|
+
* The selected tenant's full record. With no tenant selected, or one the user
|
|
50
|
+
* can no longer open, it is the most recently opened loaded tenant. A selected
|
|
51
|
+
* tenant that is in the list but not loaded yet reads as no tenant until its
|
|
52
|
+
* fetch lands, so nothing renders another tenant's record in its place.
|
|
53
|
+
*/
|
|
54
|
+
function getCurrentTenantView(tenantState, companyState) {
|
|
55
|
+
const { currentTenantId } = tenantState;
|
|
56
|
+
if (currentTenantId != null) {
|
|
57
|
+
const tenant = getTenant(tenantState, companyState, currentTenantId);
|
|
58
|
+
if (tenant != null ||
|
|
59
|
+
tenantState.tenantSummariesById[currentTenantId] != null) {
|
|
60
|
+
return tenant;
|
|
61
|
+
}
|
|
49
62
|
}
|
|
50
|
-
|
|
63
|
+
const mostRecentLoadedTenant = getTenantListEntriesByCheckInDate(tenantState).find((entry) => tenantState.tenantsById[entry.tenantId] != null);
|
|
64
|
+
return mostRecentLoadedTenant != null
|
|
65
|
+
? getTenant(tenantState, companyState, mostRecentLoadedTenant.tenantId)
|
|
66
|
+
: undefined;
|
|
67
|
+
}
|
|
68
|
+
/** Whether the tenant's full record is loaded, not just its list entry. */
|
|
69
|
+
export const isTenantLoaded = (state, tenantId) => state.tenantState.tenantsById[tenantId] != null;
|
|
70
|
+
/**
|
|
71
|
+
* Whether the selected tenant's full record is loaded. Screens that act on
|
|
72
|
+
* roles or product settings wait for this rather than for either tenant fetch
|
|
73
|
+
* to complete: the tenants list alone carries neither.
|
|
74
|
+
*/
|
|
75
|
+
export const isCurrentTenantLoaded = (state) => {
|
|
76
|
+
const { currentTenantId } = state.tenantState;
|
|
77
|
+
return currentTenantId != null && isTenantLoaded(state, currentTenantId);
|
|
78
|
+
};
|
|
79
|
+
export const getTenantBaseViewForTenantId = createSelector((state) => state.tenantState, (state) => state.companyState, (_state, tenantId) => tenantId, (tenantState, companyState, tenantId) => {
|
|
80
|
+
const entry = getTenantListEntriesByCheckInDate(tenantState).find((listEntry) => listEntry.tenantId === tenantId);
|
|
81
|
+
return entry != null
|
|
82
|
+
? getTenantBaseViewForEntry(entry, companyState)
|
|
83
|
+
: undefined;
|
|
51
84
|
});
|
|
52
85
|
const getCurrentTenantHelper = (tenantState, companyState) => {
|
|
53
|
-
const {
|
|
54
|
-
|
|
55
|
-
if (currentTenantId != null) {
|
|
56
|
-
tenant = getTenant(tenantState, companyState, currentTenantId);
|
|
57
|
-
}
|
|
58
|
-
if (tenant == null && tenantIds.length > 0) {
|
|
59
|
-
const tenants = getTenantsByCheckInDate(tenantState, companyState);
|
|
60
|
-
tenant =
|
|
61
|
-
tenants.tenantsInOrder.length > 0 ? tenants.tenantsInOrder[0] : undefined;
|
|
62
|
-
}
|
|
86
|
+
const { currentTenantId, excludedResourcesByTenant, subscriptionSummaryByTenant, } = tenantState;
|
|
87
|
+
const tenant = getCurrentTenantView(tenantState, companyState);
|
|
63
88
|
let excludedResources = undefined;
|
|
64
89
|
if (tenant != null) {
|
|
65
90
|
excludedResources = excludedResourcesByTenant[tenant.tenantId];
|
|
@@ -120,9 +145,12 @@ const getCurrentTenantHelper = (tenantState, companyState) => {
|
|
|
120
145
|
export const getCurrentTenant = createSelector((state) => state.tenantState, (state) => state.companyState, (tenantState, companyState) => {
|
|
121
146
|
return getCurrentTenantHelper(tenantState, companyState);
|
|
122
147
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
148
|
+
/**
|
|
149
|
+
* The selected tenant's id as soon as it is chosen. Unlike `getCurrentTenant`,
|
|
150
|
+
* it is set before the tenant fetch lands (boot reads it from the cached
|
|
151
|
+
* tenant list).
|
|
152
|
+
*/
|
|
153
|
+
export const getCurrentTenantId = (state) => state.tenantState.currentTenantId;
|
|
126
154
|
export function getOnboardingTenantByTenantId(tenantState, tenantId) {
|
|
127
155
|
const tenant = tenantState.onboardingTenantsById[tenantId];
|
|
128
156
|
if (tenant != null) {
|
|
@@ -133,10 +161,7 @@ export function getOnboardingTenantByTenantId(tenantState, tenantId) {
|
|
|
133
161
|
}
|
|
134
162
|
}
|
|
135
163
|
export const getTenantBaseById = createSelector((state) => state.tenantState, (state) => state.companyState, (tenantState, companyState) => {
|
|
136
|
-
return tenantState.
|
|
137
|
-
const tenant = tenantState.tenantsById[tenantId];
|
|
138
|
-
return getTenantBaseViewForTenant(tenant, companyState);
|
|
139
|
-
});
|
|
164
|
+
return getTenantListEntriesByCheckInDate(tenantState).map((entry) => getTenantBaseViewForEntry(entry, companyState));
|
|
140
165
|
});
|
|
141
166
|
export const getTenantsBaseByCheckInDateSelector = createSelector((state) => state.tenantState, (state) => state.companyState, (tenantState, companyState) => {
|
|
142
167
|
return getTenantsBaseByCheckInDate(tenantState, companyState);
|
|
@@ -165,16 +190,8 @@ export const isTenantUsingZeniCOA = (currentTenant) => {
|
|
|
165
190
|
return currentTenant.company?.companyInfo.isZeniCOAInUse ?? true; // defaulting to true
|
|
166
191
|
};
|
|
167
192
|
export const getCurrentTenantExternalConnections = (tenantState, companyState) => {
|
|
168
|
-
const { externalConnectionsByTenant,
|
|
169
|
-
|
|
170
|
-
if (currentTenantId != null) {
|
|
171
|
-
tenant = getTenant(tenantState, companyState, currentTenantId);
|
|
172
|
-
}
|
|
173
|
-
if (tenant == null && tenantIds.length > 0) {
|
|
174
|
-
const tenants = getTenantsByCheckInDate(tenantState, companyState);
|
|
175
|
-
tenant =
|
|
176
|
-
tenants.tenantsInOrder.length > 0 ? tenants.tenantsInOrder[0] : undefined;
|
|
177
|
-
}
|
|
193
|
+
const { externalConnectionsByTenant, currentTenantId } = tenantState;
|
|
194
|
+
const tenant = getCurrentTenantView(tenantState, companyState);
|
|
178
195
|
let externalConnections = undefined;
|
|
179
196
|
if (tenant != null) {
|
|
180
197
|
externalConnections = externalConnectionsByTenant[tenant.tenantId];
|
|
@@ -188,58 +205,39 @@ export const getCurrentTenantExternalConnections = (tenantState, companyState) =
|
|
|
188
205
|
* Helpers
|
|
189
206
|
*/
|
|
190
207
|
function getTenantsBaseByCheckInDate(tenantState, companyState) {
|
|
191
|
-
const tenants = tenantState.tenantIds
|
|
192
|
-
.map((tenantId) => getTenant(tenantState, companyState, tenantId))
|
|
193
|
-
.filter((value) => value != null);
|
|
194
|
-
const tenantsOrdered = orderBy(tenants, (value) => value.userLastCheckInTime.valueOf(), 'desc');
|
|
195
|
-
const tenantsBaseOrdered = tenantsOrdered.map((tenant) => getTenantBaseViewForTenant(tenant, companyState));
|
|
196
208
|
return {
|
|
197
209
|
order: 'descending_last_check_in',
|
|
198
|
-
tenantsBaseInOrder:
|
|
210
|
+
tenantsBaseInOrder: getTenantListEntriesByCheckInDate(tenantState).map((entry) => getTenantBaseViewForEntry(entry, companyState)),
|
|
199
211
|
};
|
|
200
212
|
}
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
const companyId = tenant.companyId;
|
|
204
|
-
const companyName = companyState.companiesById[companyId]?.companyInfo.name;
|
|
205
|
-
const tenantName = tenant.companyName ?? companyName ?? tenant.tenantEmailDomain ?? '';
|
|
206
|
-
const tenantEmailDomain = tenant.tenantEmailDomain;
|
|
207
|
-
const userRole = tenant.userRole;
|
|
208
|
-
const companyLocaleInfo = companyState.companiesById[companyId]?.companyLocaleInfo;
|
|
213
|
+
const getTenantBaseViewForEntry = (entry, companyState) => {
|
|
214
|
+
const company = companyState.companiesById[entry.companyId];
|
|
209
215
|
return {
|
|
210
|
-
tenantId,
|
|
211
|
-
tenantName
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
216
|
+
tenantId: entry.tenantId,
|
|
217
|
+
tenantName: entry.companyName ??
|
|
218
|
+
company?.companyInfo.name ??
|
|
219
|
+
entry.tenantEmailDomain ??
|
|
220
|
+
'',
|
|
221
|
+
tenantEmailDomain: entry.tenantEmailDomain,
|
|
222
|
+
companyId: entry.companyId,
|
|
223
|
+
// Companies are loaded only for opened tenants; the list carries the name.
|
|
224
|
+
companyName: company?.companyInfo.name ?? entry.companyName,
|
|
225
|
+
userRole: entry.userRole,
|
|
226
|
+
companyLocaleInfo: company?.companyLocaleInfo,
|
|
217
227
|
};
|
|
218
228
|
};
|
|
219
229
|
function getTenantCoreDetailsByCheckInDate(tenantState, companyState) {
|
|
220
|
-
const tenants = tenantState.tenantIds
|
|
221
|
-
.map((tenantId) => getTenant(tenantState, companyState, tenantId))
|
|
222
|
-
.filter((value) => value != null);
|
|
223
|
-
const tenantsOrdered = orderBy(tenants, (value) => value.userLastCheckInTime.valueOf(), 'desc');
|
|
224
|
-
const tenantsCoreDetailsOrdered = tenantsOrdered.map((tenant) => getTenantCoreDetailsViewForTenant(tenant, companyState));
|
|
225
230
|
return {
|
|
226
231
|
order: 'descending_last_check_in',
|
|
227
|
-
tenantsCoreDetailsInOrder:
|
|
232
|
+
tenantsCoreDetailsInOrder: getTenantListEntriesByCheckInDate(tenantState).map((entry) => ({
|
|
233
|
+
tenantId: entry.tenantId,
|
|
234
|
+
tenantName: entry.companyName ??
|
|
235
|
+
companyState.companiesById[entry.companyId]?.companyInfo.name,
|
|
236
|
+
tenantEmailDomain: entry.tenantEmailDomain,
|
|
237
|
+
companyId: entry.companyId,
|
|
238
|
+
})),
|
|
228
239
|
};
|
|
229
240
|
}
|
|
230
|
-
const getTenantCoreDetailsViewForTenant = (tenant, companyState) => {
|
|
231
|
-
const tenantId = tenant.tenantId;
|
|
232
|
-
const companyId = tenant.companyId;
|
|
233
|
-
const companyName = companyState.companiesById[companyId]?.companyInfo.name;
|
|
234
|
-
const tenantName = tenant.companyName ?? companyName;
|
|
235
|
-
const tenantEmailDomain = tenant.tenantEmailDomain;
|
|
236
|
-
return {
|
|
237
|
-
tenantId,
|
|
238
|
-
tenantName,
|
|
239
|
-
tenantEmailDomain,
|
|
240
|
-
companyId,
|
|
241
|
-
};
|
|
242
|
-
};
|
|
243
241
|
export const toTenantCoreDetailsView = (tenantsBaseView) => {
|
|
244
242
|
return tenantsBaseView.map(({ tenantId, tenantName, tenantEmailDomain, companyId }) => {
|
|
245
243
|
return {
|
package/lib/esm/index.js
CHANGED
|
@@ -73,7 +73,7 @@ import { getSnackbar } from './entity/snackbar/snackbarSelector';
|
|
|
73
73
|
import { ALL_WEEK_DAYS, toDayOfWeek, toPriorityCodeType, toTaskStatusCodeType, } from './entity/task/taskState';
|
|
74
74
|
import { getTaskGroupById } from './entity/taskGroup/taskGroupSelector';
|
|
75
75
|
import { deleteConnection, doMagicLinkSignIn, doSignIn, doSignOut, fetchActiveTenant, fetchAllTenants, fetchExcludedResources as fetchExcludedResourcesForTenant, fetchExternalConnections as fetchExternalConnectionsForTenant, fetchSubscriptionSummaryForTenant, initEmailConnectOAuth, resendVerifyDeviceOTP, resetSignInState, saveAPIKeyConnection, saveConnectorCredentials, saveExternalConnection as saveExternalConnectionForTenant, saveOAuthConnection, sendEmailMagicLinkToUser, sendSessionHeartbeat, toExternalIntegrationType, updateCurrentTenant, updateSignInState, verifyDeviceWithTwoFA, } from './entity/tenant/tenantReducer';
|
|
76
|
-
import { DEFAULT_RECEIPTS_EMAIL, getCurrentTenant, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getIsAutoReviewIncomeWithoutCustomerEnabled, getReceiptsEmail, getTenantBaseById, getTenantBaseViewForTenantId, getTenantBaseViewForTenantView, getTenantsBaseByCheckInDateSelector,
|
|
76
|
+
import { DEFAULT_RECEIPTS_EMAIL, getCurrentTenant, getCurrentTenantId, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getIsAutoReviewIncomeWithoutCustomerEnabled, getReceiptsEmail, getTenantBaseById, getTenantBaseViewForTenantId, getTenantBaseViewForTenantView, getTenantsBaseByCheckInDateSelector, getTenantsCoreDetailsByCheckInDateSelector, isCurrentTenantLoaded, isOtherProductsEnabledForTenant, isTenantBankingOnly, isTenantBookkeepingEnabled, isTenantCardsOnly, isTenantLoaded, isTenantUsingZeniCOA, isZeniDomainTenant, toTenantCoreDetailsView, } from './entity/tenant/tenantSelector';
|
|
77
77
|
import { toExternalSupportedTool } from './entity/tenant/tenantTypes';
|
|
78
78
|
import { pushToastNotification } from './entity/toastNotification/toastNotificationReducer';
|
|
79
79
|
import { getLastNotificationTime, getNotifications, } from './entity/toastNotification/toastNotificationSelector';
|
|
@@ -509,7 +509,7 @@ export { toTimeframeTick, mapTimePeriodtoTimeframeTick, toTimePeriod, toAbsolute
|
|
|
509
509
|
export { toVendorSpendTrendFilterTabsTypeStrict, } from './entity/vendorExpense/vendorSpendTrendFilterTabType';
|
|
510
510
|
export { getNumberOfPeriods };
|
|
511
511
|
export { SCHEDULE_DAYS_OF_MONTH, toScheduleDaysOfMonth, toMonth, toMonthStrict, toQuarter, toQuarterStrict, };
|
|
512
|
-
export { getTenantBaseById, getCurrentTenant, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getIsAutoReviewIncomeWithoutCustomerEnabled,
|
|
512
|
+
export { getTenantBaseById, getCurrentTenant, getCurrentTenantId, isCurrentTenantLoaded, isTenantLoaded, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getIsAutoReviewIncomeWithoutCustomerEnabled, getTenantsBaseByCheckInDateSelector, getTenantsCoreDetailsByCheckInDateSelector, toTenantCoreDetailsView, getTenantBaseViewForTenantView, getTenantBaseViewForTenantId, isZeniDomainTenant, isTenantUsingZeniCOA, fetchActiveTenant, fetchAllTenants, updateCurrentTenant, clearAll, doMagicLinkSignIn, verifyDeviceWithTwoFA, resendVerifyDeviceOTP, sendEmailMagicLinkToUser, doSignIn, updateSignInState, doSignOut, sendSessionHeartbeat, resetSignInState, fetchExcludedResourcesForTenant, fetchExternalConnectionsForTenant, saveExternalConnectionForTenant, deleteConnection, saveAPIKeyConnection, saveConnectorCredentials, saveOAuthConnection, initEmailConnectOAuth, toExternalIntegrationType, toExternalSupportedTool, fetchSubscriptionSummaryForTenant, isTenantBankingOnly, isTenantCardsOnly, isTenantBookkeepingEnabled, isOtherProductsEnabledForTenant, DEFAULT_RECEIPTS_EMAIL, getReceiptsEmail, };
|
|
513
513
|
export { toAccountType, toAccountGroupType, getAccountGroupKey, getAllAccounts, getTransactionFilterAccountOptions, };
|
|
514
514
|
export { getAllClasses, getClassById, getClassFilterOptions, } from './entity/class/classSelector';
|
|
515
515
|
export { getForecast };
|
|
@@ -88,10 +88,6 @@ export const fetchDashboardEpic = (actions$, state$) => actions$.pipe(filter(fet
|
|
|
88
88
|
const revenueState = getRevenueReportFetchState(state.revenueState.uiState.selectedForecast?.id, state.revenueState);
|
|
89
89
|
const opExState = getOperatingExpensesReportFetchState(state.opExState.uiState.selectedForecast?.id, state.opExState);
|
|
90
90
|
const netBurnOrIncomeReportFetchState = getNetBurnOrIncomeReportFetchState(state.netBurnOrIncomeState.uiState.selectedForecast?.id, state.netBurnOrIncomeState);
|
|
91
|
-
if (state.tenantState.fetchState !== 'In-Progress' &&
|
|
92
|
-
!state.tenantState.hasValidState()) {
|
|
93
|
-
fetchActions.push(fetchAllTenants(userId));
|
|
94
|
-
}
|
|
95
91
|
if (state.cardBalanceState.fetchState !== 'In-Progress' &&
|
|
96
92
|
!state.cardBalanceState.hasValidState()) {
|
|
97
93
|
fetchActions.push(fetchCardBalance());
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createSelector } from '@reduxjs/toolkit';
|
|
2
2
|
import { newBalancesFilter } from '../../commonStateTypes/coaBalance/coaBalancesFilter';
|
|
3
3
|
import { reduceFetchState } from '../../commonStateTypes/reduceFetchState';
|
|
4
|
-
import { getCurrentTenant
|
|
4
|
+
import { getCurrentTenant } from '../../entity/tenant/tenantSelector';
|
|
5
5
|
import { hasAdminLevelAccess, hasInvestorBankerLevelAccess, } from '../../entity/userRole/userRoleType';
|
|
6
6
|
import { getApAgingReport } from '../apAgingView/apAgingReport/apAgingSelector';
|
|
7
7
|
import { getArAgingReport } from '../arAgingView/arAgingReport/arAgingSelector';
|
|
@@ -34,8 +34,7 @@ const getCashInCashOutLast4Periods = (state) => getCashInCashOut(state.accountSt
|
|
|
34
34
|
const getNetBurnOrIncomeStoryCard = (state) => getNetBurnOrIncomeStoryCardReport(state);
|
|
35
35
|
const apAgingSelector = (state) => getApAgingReport(state);
|
|
36
36
|
const arAgingSelector = (state) => getArAgingReport(state);
|
|
37
|
-
const tenantSelector = createSelector(
|
|
38
|
-
tenantsOrdered,
|
|
37
|
+
const tenantSelector = createSelector(getCurrentTenant, (currentTenant) => ({
|
|
39
38
|
currentTenant,
|
|
40
39
|
}));
|
|
41
40
|
export const getDashboard = createSelector(tenantSelector, getCardBalance, getCashBalance, getOperatingExpensesForLast5Periods, getRevenueForLast4Periods, getTop6Expenses, getNetBurnOrIncomeForLast4Periods, getNetBurnOrIncomeStoryCard, getCashPositionLast5Periods, getCashInCashOutLast4Periods, getInsights, getCompanyPassportView, getTasksCardReport, getBillPayCardReport, getReimbursementCardReport, apAgingSelector, arAgingSelector, getRewardsPlanCard, getDashboardCardsOrdered, (state) => {
|
|
@@ -15,12 +15,8 @@ export const fetchFinanceStatementEpic = (actions$, state$) => actions$.pipe(fil
|
|
|
15
15
|
return of(fetchAllTenants(userId), fetchProfitAndLoss(), fetchBalanceSheet(), fetchCashFlow());
|
|
16
16
|
}
|
|
17
17
|
else {
|
|
18
|
-
const {
|
|
18
|
+
const { financeStatementState } = state;
|
|
19
19
|
const fetchActions = [];
|
|
20
|
-
if (tenantState.fetchState !== 'In-Progress' &&
|
|
21
|
-
!tenantState.hasValidState()) {
|
|
22
|
-
fetchActions.push(fetchAllTenants(userId));
|
|
23
|
-
}
|
|
24
20
|
const pandLFetchState = getPandLReportFetchState(undefined, state.profitAndLossState);
|
|
25
21
|
const balanceSheetFetchState = getBalanceSheetFetchState(state.balanceSheetState);
|
|
26
22
|
const cashFlowFetchState = getCashFlowReportFetchState(state.cashFlowState);
|
|
@@ -10,10 +10,6 @@ export const fetchPeoplePageEpic = (actions$, state$) => actions$.pipe(filter(fe
|
|
|
10
10
|
else {
|
|
11
11
|
const state = state$.value;
|
|
12
12
|
const fetchActions = [];
|
|
13
|
-
if (state.tenantState.fetchState !== 'In-Progress' &&
|
|
14
|
-
!state.tenantState.hasValidState()) {
|
|
15
|
-
fetchActions.push(fetchAllTenants(userId));
|
|
16
|
-
}
|
|
17
13
|
if (state.peopleState.fetchState != 'In-Progress' &&
|
|
18
14
|
!state.peopleState.hasValidState()) {
|
|
19
15
|
fetchActions.push(fetchPeople());
|
package/lib/index.d.ts
CHANGED
|
@@ -147,7 +147,7 @@ import { getTaskGroupById } from './entity/taskGroup/taskGroupSelector';
|
|
|
147
147
|
import { TaskGroupState } from './entity/taskGroup/taskGroupState';
|
|
148
148
|
import { TaskGroupTemplate } from './entity/taskGroupTemplate/taskGroupTemplateState';
|
|
149
149
|
import { DoSignInPayload, deleteConnection, doMagicLinkSignIn, doSignIn, doSignOut, fetchActiveTenant, fetchAllTenants, fetchExcludedResources as fetchExcludedResourcesForTenant, fetchExternalConnections as fetchExternalConnectionsForTenant, fetchSubscriptionSummaryForTenant, initEmailConnectOAuth, resendVerifyDeviceOTP, resetSignInState, saveAPIKeyConnection, saveConnectorCredentials, saveExternalConnection as saveExternalConnectionForTenant, saveOAuthConnection, sendEmailMagicLinkToUser, sendSessionHeartbeat, toExternalIntegrationType, updateCurrentTenant, updateSignInState, verifyDeviceWithTwoFA } from './entity/tenant/tenantReducer';
|
|
150
|
-
import { CurrentTenant, DEFAULT_RECEIPTS_EMAIL, TenantBaseView, TenantCoreDetailsView, TenantView, TenantsBaseOrdered,
|
|
150
|
+
import { CurrentTenant, DEFAULT_RECEIPTS_EMAIL, TenantBaseView, TenantCoreDetailsView, TenantView, TenantsBaseOrdered, getCurrentTenant, getCurrentTenantId, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getIsAutoReviewIncomeWithoutCustomerEnabled, getReceiptsEmail, getTenantBaseById, getTenantBaseViewForTenantId, getTenantBaseViewForTenantView, getTenantsBaseByCheckInDateSelector, getTenantsCoreDetailsByCheckInDateSelector, isCurrentTenantLoaded, isOtherProductsEnabledForTenant, isTenantBankingOnly, isTenantBookkeepingEnabled, isTenantCardsOnly, isTenantLoaded, isTenantUsingZeniCOA, isZeniDomainTenant, toTenantCoreDetailsView } from './entity/tenant/tenantSelector';
|
|
151
151
|
import { CapitalizableAccount, CapitalizableAccounts, CapitalizableClassificationType, CapitalizableMatchPattern, Connection, LoggedInUser, RoleResource, Tenant, TenantProductSettings } from './entity/tenant/tenantState';
|
|
152
152
|
import { toExternalSupportedTool } from './entity/tenant/tenantTypes';
|
|
153
153
|
import { ToastNotificationPayload } from './entity/toastNotification/toastNotificationPayload';
|
|
@@ -754,7 +754,7 @@ export { TimeframeTick, TimeframeTickWithMetaData, toTimeframeTick, mapTimePerio
|
|
|
754
754
|
export { VendorSpendTrendFilterTabType, toVendorSpendTrendFilterTabsTypeStrict, } from './entity/vendorExpense/vendorSpendTrendFilterTabType';
|
|
755
755
|
export { getNumberOfPeriods };
|
|
756
756
|
export { SCHEDULE_DAYS_OF_MONTH, ScheduleDaysOfMonth, toScheduleDaysOfMonth, Day, Month, toMonth, toMonthStrict, Quarter, toQuarter, toQuarterStrict, AbsoluteDay, TimePeriod, };
|
|
757
|
-
export { CapitalizableAccount, CapitalizableAccounts, CapitalizableClassificationType, CapitalizableMatchPattern, Tenant, TenantView, LoggedInUser, TenantBaseView, CurrentTenant, TenantProductSettings, getTenantBaseById, getCurrentTenant, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getIsAutoReviewIncomeWithoutCustomerEnabled,
|
|
757
|
+
export { CapitalizableAccount, CapitalizableAccounts, CapitalizableClassificationType, CapitalizableMatchPattern, Tenant, TenantView, LoggedInUser, TenantBaseView, CurrentTenant, TenantProductSettings, getTenantBaseById, getCurrentTenant, getCurrentTenantId, isCurrentTenantLoaded, isTenantLoaded, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getIsAutoReviewIncomeWithoutCustomerEnabled, getTenantsBaseByCheckInDateSelector, getTenantsCoreDetailsByCheckInDateSelector, toTenantCoreDetailsView, getTenantBaseViewForTenantView, getTenantBaseViewForTenantId, isZeniDomainTenant, isTenantUsingZeniCOA, TenantsBaseOrdered, TenantCoreDetailsView, fetchActiveTenant, fetchAllTenants, updateCurrentTenant, clearAll, doMagicLinkSignIn, verifyDeviceWithTwoFA, resendVerifyDeviceOTP, sendEmailMagicLinkToUser, doSignIn, updateSignInState, DoSignInPayload, doSignOut, sendSessionHeartbeat, resetSignInState, RoleResource, Connection, fetchExcludedResourcesForTenant, fetchExternalConnectionsForTenant, saveExternalConnectionForTenant, deleteConnection, saveAPIKeyConnection, saveConnectorCredentials, saveOAuthConnection, initEmailConnectOAuth, toExternalIntegrationType, toExternalSupportedTool, fetchSubscriptionSummaryForTenant, isTenantBankingOnly, isTenantCardsOnly, isTenantBookkeepingEnabled, isOtherProductsEnabledForTenant, DEFAULT_RECEIPTS_EMAIL, getReceiptsEmail, };
|
|
758
758
|
export { Account, AccountType, AccountBase, StatementCloseDay, toAccountType, AccountReport, NestedAccountReport, AccountWithBalanceReport, AccountGroup, AccountGroupReportV2, AccountGroupType, toAccountGroupType, AccountGroupReport, getAccountGroupKey, AccountGroupKey, RecommendedAccountBase, AccountFilterOption, getAllAccounts, getTransactionFilterAccountOptions, };
|
|
759
759
|
export { Class, ClassBase, RecommendedClassBase, } from './entity/class/classState';
|
|
760
760
|
export { ClassFilterOption, getAllClasses, getClassById, getClassFilterOptions, } from './entity/class/classSelector';
|