@zeniai/client-epic-state 5.2.7 → 5.2.8
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/invoicing/invoicingCommonPayload.d.ts +22 -0
- package/lib/entity/invoicing/invoicingCommonPayload.js +51 -10
- package/lib/esm/entity/invoicing/invoicingCommonPayload.js +51 -10
- package/lib/esm/index.js +3 -2
- package/lib/esm/view/accountList/accountListReducer.js +0 -1
- package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/backgroundRefetchReviewTabEpic.js +1 -0
- package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +1 -0
- package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +56 -11
- package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +2 -1
- package/lib/esm/view/expenseAutomationView/types/transactionGroup.js +21 -0
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +2 -1
- package/lib/esm/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.js +2 -0
- package/lib/esm/view/invoicing/invoicingQboView/epics/fetchInvoicingAccountingPickersEpic.js +10 -5
- package/lib/esm/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +27 -3
- package/lib/esm/view/invoicing/settingsView/settingsViewSelector.js +7 -3
- package/lib/index.d.ts +3 -2
- package/lib/index.js +52 -47
- package/lib/view/accountList/accountListReducer.js +0 -1
- package/lib/view/accountList/accountListState.d.ts +1 -1
- package/lib/view/expenseAutomationView/epics/transactionCategorization/backgroundRefetchReviewTabEpic.js +1 -0
- package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +1 -0
- package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +11 -0
- package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +4 -1
- package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +57 -12
- package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +2 -0
- package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +2 -1
- package/lib/view/expenseAutomationView/types/transactionGroup.d.ts +21 -0
- package/lib/view/expenseAutomationView/types/transactionGroup.js +25 -0
- package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +11 -0
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.d.ts +5 -0
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +1 -0
- package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.d.ts +1 -0
- package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.js +2 -0
- package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingAccountingPickersEpic.js +10 -5
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.d.ts +9 -1
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +30 -4
- package/lib/view/invoicing/settingsView/settingsViewSelector.d.ts +11 -4
- package/lib/view/invoicing/settingsView/settingsViewSelector.js +7 -3
- package/package.json +1 -1
|
@@ -88,12 +88,34 @@ export interface InvoicingStripeAccountPayload {
|
|
|
88
88
|
export declare const INVOICING_QBO_CUSTOMER_MATCHING_MODES: readonly ["link_existing", "always_create"];
|
|
89
89
|
export type InvoicingQboCustomerMatchingMode = (typeof INVOICING_QBO_CUSTOMER_MATCHING_MODES)[number];
|
|
90
90
|
export declare function toInvoicingQboCustomerMatchingMode(value: string): InvoicingQboCustomerMatchingMode;
|
|
91
|
+
/**
|
|
92
|
+
* A mapped account as the newer settings payload returns it: the platform id,
|
|
93
|
+
* plus hydrated fields the client only displays.
|
|
94
|
+
*/
|
|
95
|
+
export interface InvoicingQboMappedAccountPayload {
|
|
96
|
+
account_id?: string | null;
|
|
97
|
+
account_name?: string | null;
|
|
98
|
+
fully_qualified_name?: string | null;
|
|
99
|
+
qbo_id?: string | null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Account mappings arrive in two shapes. The original sends a bare id per slot
|
|
103
|
+
* (`deposit_account_id`); the newer one sends an object holding the platform id
|
|
104
|
+
* (`deposit_account: {account_id}`). Both are declared because a tenant can be
|
|
105
|
+
* served either depending on when its backend rolled, and a page that understands
|
|
106
|
+
* only one renders an empty picker over data that exists.
|
|
107
|
+
*/
|
|
91
108
|
export interface InvoicingQboAccountMappingsPayload {
|
|
109
|
+
default_income_account?: InvoicingQboMappedAccountPayload | null;
|
|
92
110
|
default_income_account_id?: string | null;
|
|
111
|
+
deposit_account?: InvoicingQboMappedAccountPayload | null;
|
|
93
112
|
deposit_account_id?: string | null;
|
|
94
113
|
deposit_account_overrides?: Record<string, string> | null;
|
|
114
|
+
discounts_account?: InvoicingQboMappedAccountPayload | null;
|
|
95
115
|
discounts_account_id?: string | null;
|
|
116
|
+
rounding_account?: InvoicingQboMappedAccountPayload | null;
|
|
96
117
|
rounding_account_id?: string | null;
|
|
118
|
+
write_off_account?: InvoicingQboMappedAccountPayload | null;
|
|
97
119
|
write_off_account_id?: string | null;
|
|
98
120
|
}
|
|
99
121
|
/**
|
|
@@ -153,6 +153,23 @@ function toInvoicingQboCustomerMatchingMode(value) {
|
|
|
153
153
|
throw new Error(`Unknown InvoicingQboCustomerMatchingMode: ${value}`);
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* The id for one mapping slot, from whichever shape the backend sent. The object
|
|
158
|
+
* form wins when present; a blank id in either is treated as unset so the picker
|
|
159
|
+
* shows its placeholder rather than selecting nothing.
|
|
160
|
+
*/
|
|
161
|
+
function readInvoicingQboMappedAccountId(mappedAccount, legacyAccountId) {
|
|
162
|
+
// `qbo_id` first: the pickers key their options on the QuickBooks id, so an
|
|
163
|
+
// internal `account_id` would not select anything even when it is the same
|
|
164
|
+
// account. Both are read because the mapping arrives either way depending on
|
|
165
|
+
// how it was saved.
|
|
166
|
+
const candidates = [
|
|
167
|
+
mappedAccount?.qbo_id,
|
|
168
|
+
mappedAccount?.account_id,
|
|
169
|
+
legacyAccountId,
|
|
170
|
+
];
|
|
171
|
+
return candidates.find((candidate) => candidate != null && candidate !== '');
|
|
172
|
+
}
|
|
156
173
|
/**
|
|
157
174
|
* Read one account-mapping dropdown value by its config `code` (the codes in
|
|
158
175
|
* `qbo_account_mapping_kind_options` match the `account_mappings` field names).
|
|
@@ -160,15 +177,15 @@ function toInvoicingQboCustomerMatchingMode(value) {
|
|
|
160
177
|
function getInvoicingQboAccountMappingId(accountMappings, code) {
|
|
161
178
|
switch (code) {
|
|
162
179
|
case 'deposit_account_id':
|
|
163
|
-
return accountMappings?.deposit_account_id
|
|
180
|
+
return readInvoicingQboMappedAccountId(accountMappings?.deposit_account, accountMappings?.deposit_account_id);
|
|
164
181
|
case 'default_income_account_id':
|
|
165
|
-
return accountMappings?.default_income_account_id
|
|
182
|
+
return readInvoicingQboMappedAccountId(accountMappings?.default_income_account, accountMappings?.default_income_account_id);
|
|
166
183
|
case 'discounts_account_id':
|
|
167
|
-
return accountMappings?.discounts_account_id
|
|
184
|
+
return readInvoicingQboMappedAccountId(accountMappings?.discounts_account, accountMappings?.discounts_account_id);
|
|
168
185
|
case 'rounding_account_id':
|
|
169
|
-
return accountMappings?.rounding_account_id
|
|
186
|
+
return readInvoicingQboMappedAccountId(accountMappings?.rounding_account, accountMappings?.rounding_account_id);
|
|
170
187
|
case 'write_off_account_id':
|
|
171
|
-
return accountMappings?.write_off_account_id
|
|
188
|
+
return readInvoicingQboMappedAccountId(accountMappings?.write_off_account, accountMappings?.write_off_account_id);
|
|
172
189
|
default:
|
|
173
190
|
return undefined;
|
|
174
191
|
}
|
|
@@ -178,18 +195,42 @@ function getInvoicingQboAccountMappingId(accountMappings, code) {
|
|
|
178
195
|
* `code` set to `accountId`. Unknown codes leave the mappings unchanged.
|
|
179
196
|
*/
|
|
180
197
|
function withInvoicingQboAccountMappingId(accountMappings, code, accountId) {
|
|
198
|
+
// Both slots are written together. Setting only the bare id would leave the
|
|
199
|
+
// object shape behind, and the reader prefers it — so the selection just made
|
|
200
|
+
// would read back as whatever was mapped before. The object is dropped rather
|
|
201
|
+
// than rebuilt: only the id is known here, not the name that came with it.
|
|
181
202
|
const base = accountMappings ?? {};
|
|
182
203
|
switch (code) {
|
|
183
204
|
case 'deposit_account_id':
|
|
184
|
-
return {
|
|
205
|
+
return {
|
|
206
|
+
...base,
|
|
207
|
+
deposit_account: undefined,
|
|
208
|
+
deposit_account_id: accountId,
|
|
209
|
+
};
|
|
185
210
|
case 'default_income_account_id':
|
|
186
|
-
return {
|
|
211
|
+
return {
|
|
212
|
+
...base,
|
|
213
|
+
default_income_account: undefined,
|
|
214
|
+
default_income_account_id: accountId,
|
|
215
|
+
};
|
|
187
216
|
case 'discounts_account_id':
|
|
188
|
-
return {
|
|
217
|
+
return {
|
|
218
|
+
...base,
|
|
219
|
+
discounts_account: undefined,
|
|
220
|
+
discounts_account_id: accountId,
|
|
221
|
+
};
|
|
189
222
|
case 'rounding_account_id':
|
|
190
|
-
return {
|
|
223
|
+
return {
|
|
224
|
+
...base,
|
|
225
|
+
rounding_account: undefined,
|
|
226
|
+
rounding_account_id: accountId,
|
|
227
|
+
};
|
|
191
228
|
case 'write_off_account_id':
|
|
192
|
-
return {
|
|
229
|
+
return {
|
|
230
|
+
...base,
|
|
231
|
+
write_off_account: undefined,
|
|
232
|
+
write_off_account_id: accountId,
|
|
233
|
+
};
|
|
193
234
|
default:
|
|
194
235
|
return base;
|
|
195
236
|
}
|
|
@@ -128,6 +128,23 @@ export function toInvoicingQboCustomerMatchingMode(value) {
|
|
|
128
128
|
throw new Error(`Unknown InvoicingQboCustomerMatchingMode: ${value}`);
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* The id for one mapping slot, from whichever shape the backend sent. The object
|
|
133
|
+
* form wins when present; a blank id in either is treated as unset so the picker
|
|
134
|
+
* shows its placeholder rather than selecting nothing.
|
|
135
|
+
*/
|
|
136
|
+
function readInvoicingQboMappedAccountId(mappedAccount, legacyAccountId) {
|
|
137
|
+
// `qbo_id` first: the pickers key their options on the QuickBooks id, so an
|
|
138
|
+
// internal `account_id` would not select anything even when it is the same
|
|
139
|
+
// account. Both are read because the mapping arrives either way depending on
|
|
140
|
+
// how it was saved.
|
|
141
|
+
const candidates = [
|
|
142
|
+
mappedAccount?.qbo_id,
|
|
143
|
+
mappedAccount?.account_id,
|
|
144
|
+
legacyAccountId,
|
|
145
|
+
];
|
|
146
|
+
return candidates.find((candidate) => candidate != null && candidate !== '');
|
|
147
|
+
}
|
|
131
148
|
/**
|
|
132
149
|
* Read one account-mapping dropdown value by its config `code` (the codes in
|
|
133
150
|
* `qbo_account_mapping_kind_options` match the `account_mappings` field names).
|
|
@@ -135,15 +152,15 @@ export function toInvoicingQboCustomerMatchingMode(value) {
|
|
|
135
152
|
export function getInvoicingQboAccountMappingId(accountMappings, code) {
|
|
136
153
|
switch (code) {
|
|
137
154
|
case 'deposit_account_id':
|
|
138
|
-
return accountMappings?.deposit_account_id
|
|
155
|
+
return readInvoicingQboMappedAccountId(accountMappings?.deposit_account, accountMappings?.deposit_account_id);
|
|
139
156
|
case 'default_income_account_id':
|
|
140
|
-
return accountMappings?.default_income_account_id
|
|
157
|
+
return readInvoicingQboMappedAccountId(accountMappings?.default_income_account, accountMappings?.default_income_account_id);
|
|
141
158
|
case 'discounts_account_id':
|
|
142
|
-
return accountMappings?.discounts_account_id
|
|
159
|
+
return readInvoicingQboMappedAccountId(accountMappings?.discounts_account, accountMappings?.discounts_account_id);
|
|
143
160
|
case 'rounding_account_id':
|
|
144
|
-
return accountMappings?.rounding_account_id
|
|
161
|
+
return readInvoicingQboMappedAccountId(accountMappings?.rounding_account, accountMappings?.rounding_account_id);
|
|
145
162
|
case 'write_off_account_id':
|
|
146
|
-
return accountMappings?.write_off_account_id
|
|
163
|
+
return readInvoicingQboMappedAccountId(accountMappings?.write_off_account, accountMappings?.write_off_account_id);
|
|
147
164
|
default:
|
|
148
165
|
return undefined;
|
|
149
166
|
}
|
|
@@ -153,18 +170,42 @@ export function getInvoicingQboAccountMappingId(accountMappings, code) {
|
|
|
153
170
|
* `code` set to `accountId`. Unknown codes leave the mappings unchanged.
|
|
154
171
|
*/
|
|
155
172
|
export function withInvoicingQboAccountMappingId(accountMappings, code, accountId) {
|
|
173
|
+
// Both slots are written together. Setting only the bare id would leave the
|
|
174
|
+
// object shape behind, and the reader prefers it — so the selection just made
|
|
175
|
+
// would read back as whatever was mapped before. The object is dropped rather
|
|
176
|
+
// than rebuilt: only the id is known here, not the name that came with it.
|
|
156
177
|
const base = accountMappings ?? {};
|
|
157
178
|
switch (code) {
|
|
158
179
|
case 'deposit_account_id':
|
|
159
|
-
return {
|
|
180
|
+
return {
|
|
181
|
+
...base,
|
|
182
|
+
deposit_account: undefined,
|
|
183
|
+
deposit_account_id: accountId,
|
|
184
|
+
};
|
|
160
185
|
case 'default_income_account_id':
|
|
161
|
-
return {
|
|
186
|
+
return {
|
|
187
|
+
...base,
|
|
188
|
+
default_income_account: undefined,
|
|
189
|
+
default_income_account_id: accountId,
|
|
190
|
+
};
|
|
162
191
|
case 'discounts_account_id':
|
|
163
|
-
return {
|
|
192
|
+
return {
|
|
193
|
+
...base,
|
|
194
|
+
discounts_account: undefined,
|
|
195
|
+
discounts_account_id: accountId,
|
|
196
|
+
};
|
|
164
197
|
case 'rounding_account_id':
|
|
165
|
-
return {
|
|
198
|
+
return {
|
|
199
|
+
...base,
|
|
200
|
+
rounding_account: undefined,
|
|
201
|
+
rounding_account_id: accountId,
|
|
202
|
+
};
|
|
166
203
|
case 'write_off_account_id':
|
|
167
|
-
return {
|
|
204
|
+
return {
|
|
205
|
+
...base,
|
|
206
|
+
write_off_account: undefined,
|
|
207
|
+
write_off_account_id: accountId,
|
|
208
|
+
};
|
|
168
209
|
default:
|
|
169
210
|
return base;
|
|
170
211
|
}
|
package/lib/esm/index.js
CHANGED
|
@@ -187,13 +187,14 @@ import { clearExpenseAutomationFluxAnalysisView, fetchFluxAnalysisView, reviewFl
|
|
|
187
187
|
import { clearJeScheduleLocalData as clearExpenseAutomationJEScheduleLocalData, clearExpenseAutomationJESchedulesView, fetchJeSchedulesPage as fetchExpenseAutomationJESchedulesPage, ignoreRecommendedJeSchedule as ignoreExpenseAutomationJESchedule, initializeAccountSettingsView as initializeJeAccountSettingsView, initializeJeScheduleLocalData, removeJeScheduleTransactionKey, retryJeSchedule as retryExpenseAutomationJESchedule, saveAccountSettings as saveJeAccountSettings, saveAccountSettingsLocalData as saveJeAccountSettingsLocalData, updateJESchedulesUIState as updateExpenseAutomationJESchedulesUIState, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys, } from './view/expenseAutomationView/reducers/jeSchedulesViewReducer';
|
|
188
188
|
import { acknowledgeBulkUploadConfirmMatchComplete, bulkUploadAutomatchingTimedOut, bulkUploadReceipts, bulkUploadReceiptsFailure, bulkUploadReceiptsSuccess, clearBulkUpload, clearBulkUploadBatchDetailsForScopeChange, clearManualSearchResults, clearMissingReceiptsTabNavigation, confirmBulkUploadMatch, confirmBulkUploadMatchFailure, confirmBulkUploadMatchSuccess, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsFailure, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatches, fetchBulkUploadBatchesFailure, fetchBulkUploadBatchesSuccess, fetchCompletedTransactions, fetchCompletedTransactionsFailure, fetchCompletedTransactionsSuccess, fetchMissingReceipts as fetchExpenseAutomationMissingReceipts, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, markBatchDetailRefreshAttempted, markMissingReceiptAsDone as markExpenseAutomationMissingReceiptAsDone, pusherBatchStatusUpdate, refreshBatchDetailsForBatchId, requestMissingReceiptsTabNavigation, restoreBulkUploadAutomatchingOnMount, restoreBulkUploadMatchingState, searchTransactionsForManualMatch, searchTransactionsForManualMatchFailure, searchTransactionsForManualMatchSuccess, setBulkUploadCompletedSubTab, setBulkUploadResultsTab, setBulkUploadSortConfig, setBulkUploadUploadedFileCount, storeBatchDetails, updateBulkUploadProgress, updateMissingReceiptUploadState as updateExpenseAutomationMissingReceiptUploadState, updateMissingReceiptsUIState as updateExpenseAutomationMissingReceiptsUIState, uploadMissingReceiptSuccess as uploadExpenseAutomationMissingReceiptSuccess, } from './view/expenseAutomationView/reducers/missingReceiptsViewReducer';
|
|
189
189
|
import { clearStatementDateConflict, confirmStatementMapping, deleteAccountStatement, excludeAccountFromReconciliation, fetchReconciliation as fetchReconciliationView, includeAccountInReconciliation, parseCombinedStatement, parseStatement, parseStatementFailure, parseStatementSuccess, reparseStatement, reparseStatementFailure, reparseStatementSuccess, resetReparseStatementStatus, saveReconciliationDetail as saveExpenseAutomationReconciliationDetail, saveReconciliationReview as saveExpenseAutomationReconciliationReview, setConnectionInProgressForAccount as setConnectionInProgressForAccountReconciliation, setStatementParseInProgress, setStatementProcessingDismissedToBackground, submitCombinedStatementUpdate, submitStatementUpdate, updateAccountReconciliationLocalData as updateExpenseAutomationAccountReconciliationLocalData, updateSelectedAccountId as updateExpenseAutomationAccountReconciliationSelectedAccountId, updateSelectedTab as updateExpenseAutomationAccountReconciliationSelectedTab, updateReconListScrollPosition as updateExpenseAutomationReconListScrollPosition, updateReviewTabSortState as updateExpenseAutomationReconReviewTabListSortState, updateReviewTabLocalData as updateExpenseAutomationReconReviewTabLocalData, updateReconcileTabListScrollState as updateExpenseAutomationReconcileTabListScrollState, updateReconcileTabListSortState as updateExpenseAutomationReconcileTabListSortState, updateReconcileTabLocalData as updateExpenseAutomationReconcileTabLocalData, updateSelectedDrawerAccountId as updateExpenseAutomationSelectedDrawerAccountId, updateNodeCollapseState, updateParsedStatementData, updateStatementProcessingBackgroundCompletedSteps, updateStatementProcessingFailed, updateStatementUpdateLocalData, updateStatementUploadChosen, uploadAccountStatement, } from './view/expenseAutomationView/reducers/reconciliationViewReducer';
|
|
190
|
-
import { backgroundRefetchReviewTab, clearExpenseAutomationTransactionsView, fetchTransactionCategorization, fetchTransactionCategorizationFailure, fetchTransactionCategorizationView, initializeTransactionCategorizationViewLocalData, markCategoryClassRecommendationsFailureForCategorization, markTransactionAsNotMiscategorized, removeTransactionFromAllTabs, saveTransactionCategorization, saveTransactionCategorizationLocalData, setAllItemsToCategoryClassInLocalDataForCategorization, setEntityRecommendationForLineIdsForCategorization, syncTransactionCategorizationFromDetailSave, updateCurrentSelectedTransactionCategorizationTab, updateSelectedCheckboxTransactionIds, updateSelectedCustomerForTransaction, updateSelectedTransactionId, updateSelectedVendorForTransaction, updateTransactionCategorization, updateTransactionCategorizationCompletedSubTab, updateTransactionCategorizationSaveStatus, updateTransactionCategorizationUIState, updateTransactionCategorizationUploadReceiptState, updateTransactionFilters, uploadTransactionCategorizationReceiptSuccess, } from './view/expenseAutomationView/reducers/transactionsViewReducer';
|
|
190
|
+
import { backgroundRefetchReviewTab, clearExpenseAutomationTransactionsView, fetchTransactionCategorization, fetchTransactionCategorizationFailure, fetchTransactionCategorizationView, initializeTransactionCategorizationViewLocalData, markCategoryClassRecommendationsFailureForCategorization, markTransactionAsNotMiscategorized, removeTransactionFromAllTabs, saveTransactionCategorization, saveTransactionCategorizationLocalData, setAllItemsToCategoryClassInLocalDataForCategorization, setEntityRecommendationForLineIdsForCategorization, syncTransactionCategorizationFromDetailSave, updateCurrentSelectedTransactionCategorizationTab, updateSelectedCheckboxTransactionIds, updateSelectedCustomerForTransaction, updateSelectedTransactionId, updateSelectedVendorForTransaction, updateTransactionCategorization, updateTransactionCategorizationCompletedSubTab, updateTransactionCategorizationSaveStatus, updateTransactionCategorizationUIState, updateTransactionCategorizationUploadReceiptState, updateTransactionFilters, updateTransactionGroup, uploadTransactionCategorizationReceiptSuccess, } from './view/expenseAutomationView/reducers/transactionsViewReducer';
|
|
191
191
|
import { getExpenseAutomationFluxAnalysisView } from './view/expenseAutomationView/selectors/fluxAnalysisViewSelector';
|
|
192
192
|
import { getExpenseAutomationReconciliationView, getLedgerAccountsForCombinedStatementMapping, getReparseStatementStatusByAccountId, getUnavailableLedgerAccountIds, isAccountReconReport, } from './view/expenseAutomationView/selectors/reconciliationViewSelector';
|
|
193
193
|
import { getExpenseAutomationTransactionView, getLastTransferEntryReplacement, } from './view/expenseAutomationView/selectors/transactionCategorizationSelector';
|
|
194
194
|
import { applyTransactionFilters } from './view/expenseAutomationView/transactionFilterHelpers';
|
|
195
195
|
import { TRANSACTION_FILTER_CATEGORIES, } from './view/expenseAutomationView/transactionFilterTypes';
|
|
196
196
|
import { DEFAULT_COMPLETED_SUB_TAB, toCompletedSubTab, } from './view/expenseAutomationView/types/completedSubTab';
|
|
197
|
+
import { DEFAULT_TRANSACTION_GROUP, TRANSACTION_GROUPS, toTransactionGroup, } from './view/expenseAutomationView/types/transactionGroup';
|
|
197
198
|
import { toJEScheduleMainTab as toExpenseAutomationJEScheduleMainTab, toJEScheduleSortKey as toExpenseAutomationJEScheduleSortKey, } from './view/expenseAutomationView/types/jeSchedulesViewState';
|
|
198
199
|
import { BATCH_FILE_STATUSES, isUnmatchedTabFileStatus, toBatchFileStatus, toBatchStatusValue, toBulkUploadPhase, toBulkUploadResultsTab, toBulkUploadSortKey, toMatchSource, toMissingReceiptsTab, } from './view/expenseAutomationView/types/missingReceiptsViewState';
|
|
199
200
|
import { toMissingReceiptsSortKey as toExpenseAutomationMissingReceiptsSortKey, } from './view/expenseAutomationView/types/missingReceiptsViewState';
|
|
@@ -482,7 +483,7 @@ export { stringToUnion, stringToUnionStrict };
|
|
|
482
483
|
export { fetchMonthEndCloseChecks, fetchMonthClosePerformanceTrend, getMonthEndCloseChecksViewByTenantId, ALL_MONTH_END_CLOSE_CHECKS_FREQUENCY, };
|
|
483
484
|
export {
|
|
484
485
|
// Bulk Upload Types
|
|
485
|
-
BATCH_FILE_STATUSES, isUnmatchedTabFileStatus, DEFAULT_COMPLETED_SUB_TAB, toBatchFileStatus, toBatchStatusValue, toBulkUploadPhase, toBulkUploadResultsTab, toBulkUploadSortKey, toCompletedSubTab, toMatchSource, toMissingReceiptsTab, toExpenseAutomationJEScheduleMainTab, toExpenseAutomationJEScheduleSortKey, getExpenseAutomationView, toExpenseAutomationMissingReceiptsSortKey, toExpenseAutomationTransactionsTabKey, toExpenseAutomationViewType, uploadExpenseAutomationMissingReceiptSuccess, markExpenseAutomationMissingReceiptAsDone, fetchAllExpenseAutomationTabs, refreshExpenseAutomationCurrentTab, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, fetchExpenseAutomationMissingReceipts,
|
|
486
|
+
BATCH_FILE_STATUSES, isUnmatchedTabFileStatus, DEFAULT_COMPLETED_SUB_TAB, DEFAULT_TRANSACTION_GROUP, TRANSACTION_GROUPS, toTransactionGroup, toBatchFileStatus, toBatchStatusValue, toBulkUploadPhase, toBulkUploadResultsTab, toBulkUploadSortKey, toCompletedSubTab, toMatchSource, toMissingReceiptsTab, toExpenseAutomationJEScheduleMainTab, toExpenseAutomationJEScheduleSortKey, getExpenseAutomationView, toExpenseAutomationMissingReceiptsSortKey, toExpenseAutomationTransactionsTabKey, toExpenseAutomationViewType, uploadExpenseAutomationMissingReceiptSuccess, markExpenseAutomationMissingReceiptAsDone, fetchAllExpenseAutomationTabs, refreshExpenseAutomationCurrentTab, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, updateTransactionGroup, fetchExpenseAutomationMissingReceipts,
|
|
486
487
|
// Bulk Upload Actions
|
|
487
488
|
bulkUploadReceipts, bulkUploadAutomatchingTimedOut, bulkUploadReceiptsSuccess, bulkUploadReceiptsFailure, restoreBulkUploadAutomatchingOnMount, restoreBulkUploadMatchingState, updateBulkUploadProgress, pusherBatchStatusUpdate, requestMissingReceiptsTabNavigation, clearMissingReceiptsTabNavigation, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatchDetailsFailure, storeBatchDetails, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, fetchBulkUploadBatches, fetchBulkUploadBatchesSuccess, fetchBulkUploadBatchesFailure, clearBulkUploadBatchDetailsForScopeChange, markBatchDetailRefreshAttempted, refreshBatchDetailsForBatchId, confirmBulkUploadMatch, confirmBulkUploadMatchSuccess, confirmBulkUploadMatchFailure, setBulkUploadResultsTab, setBulkUploadCompletedSubTab, setBulkUploadSortConfig, setBulkUploadUploadedFileCount, clearBulkUpload, searchTransactionsForManualMatch, searchTransactionsForManualMatchSuccess, searchTransactionsForManualMatchFailure, clearManualSearchResults, acknowledgeBulkUploadConfirmMatchComplete, fetchCompletedTransactions, fetchCompletedTransactionsSuccess, fetchCompletedTransactionsFailure, fetchFluxAnalysisView, clearExpenseAutomationFluxAnalysisView, updateOperatingExpensesIdsForReview as updateFluxOperatingExpensesIdsForReview, updateSelectedSectionIdsForReview as updateFluxAnalysisSelectedSectionIdsForReview, reviewFluxAnalysisView, updateExpenseAutomationMissingReceiptUploadState, updateExpenseAutomationMissingReceiptsUIState, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, getExpenseAutomationFluxAnalysisView, updateCurrentSelectedView, updateCurrentSelectedPeriod, getExpenseAutomationTransactionView, getLastTransferEntryReplacement, updateFluxAnalysisViewUIState, updateFluxAnalysisViewPageMetaData, MAX_SELECTION_LIMIT, checkIfAllLineItemsAreCategoryClassFilled, getLineItemsByTransactionIdsFromLocalData, isAnyItemWithUncategorizedExpenseAccount, excludeAccountFromReconciliation, includeAccountInReconciliation, saveExpenseAutomationReconciliationDetail, updateExpenseAutomationReconcileTabListScrollState, updateExpenseAutomationReconReviewTabListSortState, updateExpenseAutomationReconcileTabListSortState, updateExpenseAutomationReconcileTabLocalData, updateExpenseAutomationAccountReconciliationSelectedTab, updateExpenseAutomationAccountReconciliationSelectedAccountId, getExpenseAutomationReconciliationView, getLedgerAccountsForCombinedStatementMapping, getReparseStatementStatusByAccountId, getUnavailableLedgerAccountIds, fetchReconciliationView, uploadAccountStatementIntoDocumentAI, updateExpenseAutomationReconListScrollPosition, setConnectionInProgressForAccountReconciliation, getAccountReconByAccountIdAndSelectedPeriod, toReconciliationTabsType, isAccountReconReport, updateExpenseAutomationReconReviewTabLocalData, updateExpenseAutomationSelectedDrawerAccountId, updateStatementProcessingFailed, saveExpenseAutomationReconciliationReview, updateExpenseAutomationAccountReconciliationLocalData, toReconciliationAccountSource, deleteAccountStatement, uploadAccountStatement, parseCombinedStatement, parseStatement, parseStatementSuccess, parseStatementFailure, reparseStatement, reparseStatementSuccess, reparseStatementFailure, resetReparseStatementStatus, updateParsedStatementData, updateNodeCollapseState, updateStatementUploadChosen, submitCombinedStatementUpdate, submitStatementUpdate, clearStatementDateConflict, confirmStatementMapping, updateStatementUpdateLocalData, isReviewTransactionBankTransferType, isReviewTransactionBillPaymentType, isReviewTransactionCreditCardPaymentType, isReviewTransactionDepositType, isReviewTransactionExpenseType, isReviewTransactionCreditCardCreditType, setStatementParseInProgress, setStatementProcessingDismissedToBackground, updateStatementProcessingBackgroundCompletedSteps, };
|
|
488
489
|
export { fetchTransactionCategorization, fetchTransactionCategorizationView, updateTransactionCategorizationUIState, updateTransactionFilters, updateSelectedCheckboxTransactionIds, markCategoryClassRecommendationsFailureForCategorization, setEntityRecommendationForLineIdsForCategorization, initializeTransactionCategorizationViewLocalData, setAllItemsToCategoryClassInLocalDataForCategorization, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, createTransferEntry, createTransferEntryFailure, createTransferEntryReplacedTransaction, createTransferEntrySuccess, resetCreateTransferEntryStatus, clearTransferEntryRouteReplacement, fetchAccountsForTransferFlow, removeTransactionFromAllTabs, clearExpenseAutomationTransactionsView, toTransactionsSortKey, };
|
|
@@ -27,6 +27,7 @@ export const backgroundRefetchReviewTabEpic = (actions$, state$, zeniAPI) => act
|
|
|
27
27
|
page_token: pageToken,
|
|
28
28
|
page_size: PAGE_SIZE,
|
|
29
29
|
search_text: reviewUiState.searchString,
|
|
30
|
+
transaction_group: state.expenseAutomationTransactionsViewState.selectedTransactionGroup,
|
|
30
31
|
};
|
|
31
32
|
return zeniAPI.getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expense-automation/transactions?query=${encodeURIComponent(JSON.stringify(queryParam))}`);
|
|
32
33
|
};
|
|
@@ -36,6 +36,7 @@ groupBy((action) => action.payload.selectedTab), mergeMap((group$) => group$.pip
|
|
|
36
36
|
sub_tab: selectedTab === 'autoCategorized'
|
|
37
37
|
? expenseAutomationTransactionsViewState.selectedTransactionCategorizationCompletedSubTab
|
|
38
38
|
: DEFAULT_COMPLETED_SUB_TAB,
|
|
39
|
+
transaction_group: expenseAutomationTransactionsViewState.selectedTransactionGroup,
|
|
39
40
|
};
|
|
40
41
|
return zeniAPI
|
|
41
42
|
.getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expense-automation/transactions?query=${encodeURIComponent(JSON.stringify(queryParam))}`)
|
|
@@ -6,7 +6,18 @@ import { isVendorTransaction } from '../../../entity/transaction/stateTypes/vend
|
|
|
6
6
|
import { clearAll } from '../../../rootActions';
|
|
7
7
|
import { filterAutoTabLineItems, mergeTabSpecificLineItems, removeTransactionFromTabView, setEntityRecommendationForLineIdInLocalData, shouldAutoSelectAndAdd, toSetAllLineItemsToCategoryClass, toTransactionDetailLocalData, } from '../helpers/transactionCategorizationLocalDataHelper';
|
|
8
8
|
import { DEFAULT_COMPLETED_SUB_TAB, } from '../types/completedSubTab';
|
|
9
|
+
import { DEFAULT_TRANSACTION_GROUP, } from '../types/transactionGroup';
|
|
9
10
|
import { TRANSACTIONS_TABS, } from '../types/transactionsViewState';
|
|
11
|
+
export const initialUIState = {
|
|
12
|
+
sortKey: 'date',
|
|
13
|
+
sortOrder: 'descending',
|
|
14
|
+
searchString: '',
|
|
15
|
+
nodeCollapseState: {},
|
|
16
|
+
scrollPosition: { scrollTop: 0, scrollLeft: undefined },
|
|
17
|
+
pageToken: null,
|
|
18
|
+
totalCount: {},
|
|
19
|
+
limit: 10,
|
|
20
|
+
};
|
|
10
21
|
export const initialTransactionTabViewState = {
|
|
11
22
|
saveStatus: {
|
|
12
23
|
fetchState: 'Not-Started',
|
|
@@ -20,16 +31,7 @@ export const initialTransactionTabViewState = {
|
|
|
20
31
|
fetchState: 'Not-Started',
|
|
21
32
|
error: undefined,
|
|
22
33
|
},
|
|
23
|
-
uiState: {
|
|
24
|
-
sortKey: 'date',
|
|
25
|
-
sortOrder: 'descending',
|
|
26
|
-
searchString: '',
|
|
27
|
-
nodeCollapseState: {},
|
|
28
|
-
scrollPosition: { scrollTop: 0, scrollLeft: undefined },
|
|
29
|
-
pageToken: null,
|
|
30
|
-
totalCount: {},
|
|
31
|
-
limit: 10,
|
|
32
|
-
},
|
|
34
|
+
uiState: { ...initialUIState },
|
|
33
35
|
filters: {
|
|
34
36
|
categoryCombinationOperator: 'AND',
|
|
35
37
|
categories: [],
|
|
@@ -55,6 +57,7 @@ export const initialState = {
|
|
|
55
57
|
},
|
|
56
58
|
selectedTransactionCategorizationTab: 'review',
|
|
57
59
|
selectedTransactionCategorizationCompletedSubTab: DEFAULT_COMPLETED_SUB_TAB,
|
|
60
|
+
selectedTransactionGroup: DEFAULT_TRANSACTION_GROUP,
|
|
58
61
|
transactionCategorizationView: {
|
|
59
62
|
review: { ...initialTransactionTabViewState },
|
|
60
63
|
autoCategorized: { ...initialTransactionTabViewState },
|
|
@@ -400,6 +403,48 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
400
403
|
return { payload: { newSubTab } };
|
|
401
404
|
},
|
|
402
405
|
},
|
|
406
|
+
updateTransactionGroup: {
|
|
407
|
+
reducer(draft, action) {
|
|
408
|
+
const { newGroup } = action.payload;
|
|
409
|
+
if (draft.selectedTransactionGroup === newGroup) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
draft.selectedTransactionGroup = newGroup;
|
|
413
|
+
// A group change re-scopes the entire dataset on the backend (by
|
|
414
|
+
// account_type), so every parent tab, sub-tab snapshot and count is now
|
|
415
|
+
// stale. Drop the sub-tab cache and parent counts, and reset each tab's
|
|
416
|
+
// paginated data slots so the view epic's cache check falls through to a
|
|
417
|
+
// fresh page-1 fetch. User preferences (search, sort, limit, collapse
|
|
418
|
+
// state) are intentionally preserved, mirroring the sub-tab switch.
|
|
419
|
+
draft.autoCategorizedSubTabCache = {};
|
|
420
|
+
draft.parentTotalCountByTab = { review: {}, autoCategorized: {} };
|
|
421
|
+
TRANSACTIONS_TABS.forEach((tab) => {
|
|
422
|
+
const view = draft.transactionCategorizationView[tab];
|
|
423
|
+
// Reset uiState to its initial values but keep the user prefs (search,
|
|
424
|
+
// sort, limit, collapse) in place -- mirroring the sub-tab switch. Then
|
|
425
|
+
// drop the loaded rows and any stale selection pointing at a now-absent
|
|
426
|
+
// row to avoid transient invalid state.
|
|
427
|
+
const { searchString, sortKey, sortOrder, nodeCollapseState, limit } = view.uiState;
|
|
428
|
+
view.uiState = {
|
|
429
|
+
...initialUIState,
|
|
430
|
+
searchString,
|
|
431
|
+
sortKey,
|
|
432
|
+
sortOrder,
|
|
433
|
+
nodeCollapseState,
|
|
434
|
+
limit,
|
|
435
|
+
};
|
|
436
|
+
view.transactionIdsBySelectedPeriod = {};
|
|
437
|
+
view.fetchState = 'Not-Started';
|
|
438
|
+
view.error = undefined;
|
|
439
|
+
view.selectedTransactionId = undefined;
|
|
440
|
+
view.selectedTransactionLineId = undefined;
|
|
441
|
+
view.selectedCheckBoxTransactionIds = [];
|
|
442
|
+
});
|
|
443
|
+
},
|
|
444
|
+
prepare(newGroup) {
|
|
445
|
+
return { payload: { newGroup } };
|
|
446
|
+
},
|
|
447
|
+
},
|
|
403
448
|
updateTransactionCategorization: {
|
|
404
449
|
reducer(draft, action) {
|
|
405
450
|
const selectedTab = action.payload.selectedTab;
|
|
@@ -1075,5 +1120,5 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
1075
1120
|
});
|
|
1076
1121
|
},
|
|
1077
1122
|
});
|
|
1078
|
-
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;
|
|
1123
|
+
export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, updateTransactionFilters, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, updateTransactionGroup, 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;
|
|
1079
1124
|
export default expenseAutomationTransactionsView.reducer;
|
|
@@ -86,7 +86,7 @@ export const toTransactionView = (transaction, transactionLocalData) => {
|
|
|
86
86
|
export { getLastTransferEntryReplacement } from '../../createTransferEntry/createTransferEntrySelector';
|
|
87
87
|
export function getExpenseAutomationTransactionView(state) {
|
|
88
88
|
const { accountState, classState, classListState, accountListState, createTransferEntryState, expenseAutomationTransactionsViewState, expenseAutomationViewState, projectState, projectListState, transactionState, monthEndCloseChecksState, } = state;
|
|
89
|
-
const { selectedTransactionCategorizationCompletedSubTab, selectedTransactionCategorizationTab, } = expenseAutomationTransactionsViewState;
|
|
89
|
+
const { selectedTransactionCategorizationCompletedSubTab, selectedTransactionCategorizationTab, selectedTransactionGroup, } = expenseAutomationTransactionsViewState;
|
|
90
90
|
const { uiState, refreshStatus, saveStatus, markAsNotMiscategorizedStatus, transactionIdsBySelectedPeriod, transactionReviewLocalDataById, selectedCheckBoxTransactionIds, transactionIdsWithUnsavedData, uploadReceiptStatusById, selectedTransactionId, selectedTransactionLineId, filters, } = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTransactionCategorizationTab];
|
|
91
91
|
const uncategorizedIncomeExpense = getUncategorizedAccounts(accountState, accountListState, 'accountList');
|
|
92
92
|
const accountList = getAccountList(accountState, accountListState, 'accountList');
|
|
@@ -240,5 +240,6 @@ export function getExpenseAutomationTransactionView(state) {
|
|
|
240
240
|
creditCardTransferAccountsList,
|
|
241
241
|
transferAccounts: transferAccountsList,
|
|
242
242
|
selectedTransactionCategorizationCompletedSubTab,
|
|
243
|
+
selectedTransactionGroup,
|
|
243
244
|
};
|
|
244
245
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { stringToUnion } from '../../../commonStateTypes/stringToUnion';
|
|
2
|
+
/**
|
|
3
|
+
* Statement-level grouping for the Transaction Categorization listing, forwarded
|
|
4
|
+
* verbatim to the listing API as `transaction_group`. When revenue automation is
|
|
5
|
+
* enabled for the tenant the backend scopes the whole view by the line
|
|
6
|
+
* account_type: `'expense'` -> expense accounts, `'income'` -> income accounts,
|
|
7
|
+
* `'all'` -> both. When off, every value falls back to the legacy expense scope
|
|
8
|
+
* on the backend, so the group is only sent when the gate is on.
|
|
9
|
+
*
|
|
10
|
+
* The wire value for expenses is the singular `'expense'` (matches the backend
|
|
11
|
+
* `TransactionGroup` enum) even though the picker labels it "Expenses".
|
|
12
|
+
* Balance-sheet groups (assets, liability) are reserved for later and are not
|
|
13
|
+
* part of this union yet.
|
|
14
|
+
*/
|
|
15
|
+
export const TRANSACTION_GROUPS = ['all', 'income', 'expense'];
|
|
16
|
+
export const toTransactionGroup = (v) => stringToUnion(v != null ? v.trim().toLowerCase() : '', TRANSACTION_GROUPS);
|
|
17
|
+
/**
|
|
18
|
+
* Default group for the listing — matches the picker's default selection ("All").
|
|
19
|
+
* Single source of truth for initial slice state and any fall-back call site.
|
|
20
|
+
*/
|
|
21
|
+
export const DEFAULT_TRANSACTION_GROUP = 'all';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createSelector } from '@reduxjs/toolkit';
|
|
2
2
|
import { getInvoicingPlanById, getInvoicingPlansByIds, } from '../../../entity/invoicing/plan/planSelector';
|
|
3
3
|
import { getInvoicingConfigView, invoicingFieldHelpForGroup, } from '../invoicingConfigView/invoicingConfigViewSelector';
|
|
4
|
-
import { INVOICING_INCOME_ACCOUNT_TYPES, getInvoicingQboView, invoicingAccountHierarchyForTypes, isInvoicingQboConnected, } from '../invoicingQboView/invoicingQboViewSelector';
|
|
4
|
+
import { INVOICING_INCOME_ACCOUNT_TYPES, getInvoicingQboView, invoicingAccountHierarchyForTypes, isInvoicingAccountingPickerDataLoading, isInvoicingQboConnected, } from '../invoicingQboView/invoicingQboViewSelector';
|
|
5
5
|
import { INVOICING_CATALOG_APPLICABILITY_VALUES, INVOICING_CATALOG_PRICING_MODEL_VALUES, INVOICING_CATALOG_PRORATION_VALUES, INVOICING_CATALOG_TIER_TYPE_VALUES, entityToInvoicingCatalogItemFormLocalData, } from './invoicingCatalogItemFormConfig';
|
|
6
6
|
export const getEditInvoicingCatalogItemDetailViewState = (state) => ({
|
|
7
7
|
fetchState: state.editInvoicingCatalogItemDetailViewState.fetchState,
|
|
@@ -53,6 +53,7 @@ export const getInvoicingCatalogItemFormView = (state, id) => {
|
|
|
53
53
|
const { billingPeriodUnitOptions, catalogItemTypeOptions, catalogTierTypeOptionsByPricingModel, catalogTrialEndActionOptions, catalogTrialUnitOptions, } = getInvoicingConfigView(state);
|
|
54
54
|
return {
|
|
55
55
|
fieldHelp: invoicingFieldHelpForGroup(state, 'catalog'),
|
|
56
|
+
isQboPickerDataLoading: isInvoicingAccountingPickerDataLoading(state),
|
|
56
57
|
qboClassHierarchy: isQboConnected
|
|
57
58
|
? getInvoicingQboView(state).nestedClassHierarchy
|
|
58
59
|
: [],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isInvoicingQboConnected } from '../invoicingQboView/invoicingQboViewSelector';
|
|
1
2
|
import { deriveInvoicingOnboardingStatus, } from './invoicingConfigViewPayload';
|
|
2
3
|
export const getInvoicingConfigView = (state) => {
|
|
3
4
|
const { acceptTermsFetchState, enableFetchState, ...configState } = state.invoicingConfigViewState;
|
|
@@ -14,6 +15,7 @@ export const getInvoicingOnboardingGateView = (state) => {
|
|
|
14
15
|
acceptTermsState: acceptTermsFetchState,
|
|
15
16
|
enableState: enableFetchState,
|
|
16
17
|
fetchState,
|
|
18
|
+
isQboConnected: isInvoicingQboConnected(state),
|
|
17
19
|
status: deriveInvoicingOnboardingStatus(state.invoicingConfigViewState.isInvoicingTosAccepted, state.invoicingConfigViewState.isInvoicingEnabled),
|
|
18
20
|
};
|
|
19
21
|
};
|
package/lib/esm/view/invoicing/invoicingQboView/epics/fetchInvoicingAccountingPickersEpic.js
CHANGED
|
@@ -9,12 +9,17 @@ import { INVOICING_ACCOUNT_LIST_KEY } from '../invoicingQboViewSelector';
|
|
|
9
9
|
export const fetchInvoicingAccountingPickersEpic = (actions$, state$) => actions$.pipe(filter(fetchInvoicingAccountingPickers.match), withLatestFrom(state$), mergeMap(([, state]) => {
|
|
10
10
|
const actions = [];
|
|
11
11
|
const accountList = state.accountListState.byReportId[INVOICING_ACCOUNT_LIST_KEY];
|
|
12
|
-
|
|
12
|
+
// `hasValidState` only reports that a fetch finished, not that it
|
|
13
|
+
// returned anything usable. The shared bucket can be Completed and empty
|
|
14
|
+
// because a screen that needs fewer accounts filled it first, which would
|
|
15
|
+
// leave the invoicing pickers empty with no request in flight to fix it.
|
|
16
|
+
const isAccountListUsable = accountList.hasValidState() && accountList.accountIds.length > 0;
|
|
17
|
+
if (isAccountListUsable === false &&
|
|
13
18
|
accountList.fetchState !== 'In-Progress') {
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
actions.push(fetchAccountList(INVOICING_ACCOUNT_LIST_KEY, true
|
|
19
|
+
// Same arguments the vendor and categorization screens pass, so this
|
|
20
|
+
// shares their cached list rather than replacing it with a narrower one.
|
|
21
|
+
// Invoicing narrows to income accounts when it builds its options.
|
|
22
|
+
actions.push(fetchAccountList(INVOICING_ACCOUNT_LIST_KEY, true));
|
|
18
23
|
}
|
|
19
24
|
const classList = state.classListState;
|
|
20
25
|
if (classList.hasValidState() === false &&
|
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
import { getAccountList, getNestedAccountListHierarchy, } from '../../accountList/accountListSelector';
|
|
2
2
|
import { getClassList, getNestedClassListHierarchy, } from '../../classList/classListSelector';
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
3
|
+
// The shared bucket, fetched with the same arguments as the other consumers so
|
|
4
|
+
// whichever screen loads first satisfies the rest. Invoicing narrows to income
|
|
5
|
+
// accounts when it builds its options, so it does not need its own copy.
|
|
6
|
+
export const INVOICING_ACCOUNT_LIST_KEY = 'accountList';
|
|
6
7
|
/** Whether QBO is connected, without building the option lists to find out. */
|
|
7
8
|
export const isInvoicingQboConnected = (state) => state.invoicingQboViewState.connection?.connected === true;
|
|
9
|
+
/**
|
|
10
|
+
* Whether the accounting pickers are still waiting on their data. Only true when
|
|
11
|
+
* QBO is connected: a tenant without it has no pickers to populate, so the form
|
|
12
|
+
* must not sit behind a skeleton waiting for lists it will never show.
|
|
13
|
+
*/
|
|
14
|
+
export const isInvoicingAccountingPickerDataLoading = (state) => {
|
|
15
|
+
const isPending = (fetchState) => fetchState === 'Not-Started' || fetchState === 'In-Progress';
|
|
16
|
+
// The connection settles first and decides whether the lists are fetched at
|
|
17
|
+
// all, so it counts as loading too. Reporting "not loading" while it is in
|
|
18
|
+
// flight renders the form, then takes it away again the moment the tenant
|
|
19
|
+
// turns out to be connected.
|
|
20
|
+
if (isPending(getInvoicingQboConnectionFetchState(state).fetchState)) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
if (!isInvoicingQboConnected(state)) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const accountList = state.accountListState.byReportId[INVOICING_ACCOUNT_LIST_KEY];
|
|
27
|
+
return (isPending(accountList.fetchState) ||
|
|
28
|
+
isPending(state.classListState.fetchState));
|
|
29
|
+
};
|
|
30
|
+
/** The QBO connection fetch on its own, without building the bundled view. */
|
|
31
|
+
export const getInvoicingQboConnectionFetchState = (state) => state.invoicingQboViewState.connectionFetchState;
|
|
8
32
|
/**
|
|
9
33
|
* Account types that can back invoicing revenue. Named here so screens ask for
|
|
10
34
|
* "income accounts" rather than repeating the accounting service's vocabulary.
|
|
@@ -63,9 +63,10 @@ export function deriveInvoicingEnablePrerequisites(settings, isQboConnected, qbo
|
|
|
63
63
|
* two integration prerequisites kept separate so the hub can name the step still
|
|
64
64
|
* outstanding instead of showing a dead button.
|
|
65
65
|
*
|
|
66
|
-
* `
|
|
67
|
-
*
|
|
68
|
-
*
|
|
66
|
+
* `isInvoicingEnabled` decides whether the footer renders; `canEnableInvoicing`
|
|
67
|
+
* whether its button is live, and it still requires the onboarding step so the
|
|
68
|
+
* terms cannot be skipped. A tenant partway through therefore sees the footer,
|
|
69
|
+
* disabled and naming what is left, rather than nothing at all.
|
|
69
70
|
*
|
|
70
71
|
* Deliberately not folded into `getInvoicingConfigView`: that selector gates
|
|
71
72
|
* every invoicing route and stays a pure read of the config slice. The settings
|
|
@@ -75,7 +76,10 @@ export const getInvoicingSettingsHubView = (state) => {
|
|
|
75
76
|
const { dunningScheduleActionOptions, enableState, error: configError, fetchState: configFetchState, qboAccountMappingKindOptions, qboCustomerMatchingOptions, qboSyncStatusOptions, status, } = getInvoicingConfigView(state);
|
|
76
77
|
const { canEnable, isQboConfigured, isStripeConnected } = deriveInvoicingEnablePrerequisites(state.invoicingSettingsViewState.settings, state.invoicingQboViewState.connection?.connected === true, qboAccountMappingKindOptions);
|
|
77
78
|
return {
|
|
79
|
+
// Keeps the onboarding step in the gate: a tenant that has not accepted the
|
|
80
|
+
// terms cannot enable invoicing, whatever the integrations say.
|
|
78
81
|
canEnableInvoicing: status === 'settings' && canEnable,
|
|
82
|
+
isInvoicingEnabled: state.invoicingConfigViewState.isInvoicingEnabled === true,
|
|
79
83
|
configError,
|
|
80
84
|
configFetchState,
|
|
81
85
|
dunningScheduleActionOptions,
|