@zeniai/client-epic-state 5.2.12 → 5.2.13-betaAK0

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.
Files changed (37) hide show
  1. package/lib/esm/index.js +5 -4
  2. package/lib/esm/view/expenseAutomationView/epics/accountRecon/confirmStatementMappingEpic.js +23 -7
  3. package/lib/esm/view/expenseAutomationView/epics/accountRecon/updateCombinedStatementInfoEpic.js +1 -1
  4. package/lib/esm/view/expenseAutomationView/payload/reconciliationPayload.js +1 -1
  5. package/lib/esm/view/expenseAutomationView/reducers/reconciliationViewReducer.js +5 -0
  6. package/lib/esm/view/expenseAutomationView/selectors/reconciliationViewSelector.js +33 -2
  7. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.js +24 -7
  8. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +1 -1
  9. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +6 -8
  10. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogSubFamilyEpics.js +8 -2
  11. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +15 -10
  12. package/lib/esm/view/invoicing/invoicingQboView/epics/fetchInvoicingAccountingPickersEpic.js +0 -1
  13. package/lib/esm/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +15 -36
  14. package/lib/index.d.ts +7 -5
  15. package/lib/index.js +49 -46
  16. package/lib/view/expenseAutomationView/epics/accountRecon/confirmStatementMappingEpic.js +23 -7
  17. package/lib/view/expenseAutomationView/epics/accountRecon/updateCombinedStatementInfoEpic.js +1 -1
  18. package/lib/view/expenseAutomationView/payload/reconciliationPayload.d.ts +8 -3
  19. package/lib/view/expenseAutomationView/payload/reconciliationPayload.js +1 -1
  20. package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.js +5 -0
  21. package/lib/view/expenseAutomationView/selectors/reconciliationViewSelector.d.ts +3 -1
  22. package/lib/view/expenseAutomationView/selectors/reconciliationViewSelector.js +34 -2
  23. package/lib/view/expenseAutomationView/types/reconciliationViewState.d.ts +6 -0
  24. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.d.ts +11 -2
  25. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.js +24 -7
  26. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.d.ts +7 -0
  27. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +4 -3
  28. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.d.ts +19 -4
  29. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.d.ts +5 -4
  30. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +5 -7
  31. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogSubFamilyEpics.d.ts +1 -1
  32. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogSubFamilyEpics.js +8 -2
  33. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +14 -9
  34. package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingAccountingPickersEpic.js +0 -1
  35. package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.d.ts +12 -13
  36. package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +16 -38
  37. package/package.json +1 -1
package/lib/esm/index.js CHANGED
@@ -189,7 +189,7 @@ import { acknowledgeBulkUploadConfirmMatchComplete, bulkUploadAutomatchingTimedO
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
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
- import { getExpenseAutomationReconciliationView, getLedgerAccountsForCombinedStatementMapping, getReparseStatementStatusByAccountId, getUnavailableLedgerAccountIds, isAccountReconReport, } from './view/expenseAutomationView/selectors/reconciliationViewSelector';
192
+ import { getExpenseAutomationReconciliationView, getLedgerAccountIdsWithStatementOverrideWarning, 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';
@@ -485,7 +485,7 @@ export {
485
485
  // Bulk Upload Types
486
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,
487
487
  // Bulk Upload Actions
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
+ 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, getLedgerAccountIdsWithStatementOverrideWarning, 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, };
489
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, };
490
490
  export { TOP_EX_TIME_PERIODS };
491
491
  export { toTimeframeTick, mapTimePeriodtoTimeframeTick, toTimePeriod, toAbsoluteDay, toMonthYearPeriodId, convertToPeriod, };
@@ -768,7 +768,7 @@ export { INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE, entityToInvoicingSubscrip
768
768
  export { getEditInvoicingSubscriptionDetailViewState, getInvoicingSubscriptionFormView, } from './view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector';
769
769
  export { archiveInvoicingCatalogSubFamily, createInvoicingCatalogSubFamily, resetEditInvoicingCatalogItemDetailView, saveInvoicingCatalogItem, submitInvoicingCatalogItemForm, updateInvoicingCatalogItemFormDraft, } from './view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer';
770
770
  export { catalogDetailEditValuesToSavePayload, createSubFamilyFormToPayload, planToCatalogDetailEditValues, productToCreateSubFamilyFormValues, } from './view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload';
771
- export { INVOICING_CATALOG_APPLICABILITY_VALUES, INVOICING_CATALOG_PRICING_MODEL_VALUES, INVOICING_CATALOG_PRORATION_VALUES, INVOICING_CATALOG_TIER_TYPE_VALUES, blankInvoicingCatalogSubFamily, blankInvoicingCatalogTier, entityToInvoicingCatalogItemFormLocalData, initialInvoicingCatalogItemFormLocalData, isTieredCatalogPricingModel, } from './view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormConfig';
771
+ export { INVOICING_CATALOG_APPLICABILITY_VALUES, INVOICING_CATALOG_PRICING_MODEL_VALUES, INVOICING_CATALOG_PRORATION_VALUES, INVOICING_CATALOG_TIER_TYPE_VALUES, blankInvoicingCatalogSubFamily, blankInvoicingCatalogTier, entityToInvoicingCatalogItemFormLocalData, invoicingProductToCatalogItemFormSubFamily, initialInvoicingCatalogItemFormLocalData, isTieredCatalogPricingModel, } from './view/invoicing/editInvoicingCatalogItemDetailView/invoicingCatalogItemFormConfig';
772
772
  export { getEditInvoicingCatalogItemDetailViewState, getInvoicingApplicableCatalogItemOptions, getInvoicingCatalogItemFormView, getSavedInvoicingCatalogItemId, getSavedInvoicingCatalogItemKind, } from './view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector';
773
773
  export { resetEditInvoicingCouponDetailView, saveInvoicingCoupon, submitInvoicingCouponForm, updateInvoicingCouponFormDraft, } from './view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer';
774
774
  export { entityToInvoicingCouponFormLocalData, initialInvoicingCouponFormLocalData, } from './view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewState';
@@ -856,8 +856,9 @@ export { INVOICING_BUSINESS_ADDRESS_TYPE, entityToInvoicingBrandingFormLocalData
856
856
  export { isStripeConnectedFromSettings } from './view/invoicing/settingsView/invoicingSettingsHelpers';
857
857
  // ── Invoicing: QBO integration ────────────────────────────────────────────────
858
858
  export { fetchInvoicingAccountingPickers, fetchInvoicingQboConnection, fetchInvoicingQboSyncHealth, retryInvoicingQboSyncTask, startInvoicingQboBackfill, } from './view/invoicing/invoicingQboView/invoicingQboViewReducer';
859
- export { INVOICING_INCOME_ACCOUNT_TYPES, invoicingAccountsForQboAccountTypes, } from './view/invoicing/invoicingQboView/invoicingQboViewSelector';
859
+ export { invoicingAccountsForQboAccountTypes } from './view/invoicing/invoicingQboView/invoicingQboViewSelector';
860
860
  export { getInvoicingQboView } from './view/invoicing/invoicingQboView/invoicingQboViewSelector';
861
+ export { getInvoicingCatalogAccountingLookup } from './view/invoicing/invoicingQboView/invoicingQboViewSelector';
861
862
  export { INVOICING_QBO_CUSTOMER_MATCHING_MODES, getInvoicingQboAccountMappingId, toInvoicingNamedOptionsByKey, toInvoicingQboCustomerMatchingMode, } from './entity/invoicing/invoicingCommonPayload';
862
863
  // ── Invoicing: config ─────────────────────────────────────────────────────────
863
864
  export { acceptInvoicingTerms, enableInvoicing, fetchInvoicingConfig, resetInvoicingConfigActionStates, updateInvoicingConfig, } from './view/invoicing/invoicingConfigView/invoicingConfigViewReducer';
@@ -4,11 +4,27 @@ import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
4
4
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
5
  import { isConfirmStatementMappingConflictResponse, transformConfirmStatementMappingConflictToState, transformConfirmStatementMappingPayloadToState, } from '../../payload/reconciliationPayload';
6
6
  import { confirmStatementMapping, confirmStatementMappingFailure, confirmStatementMappingSuccess, } from '../../reducers/reconciliationViewReducer';
7
- const confirmStatementMappingErrorSnackbar = openSnackbar({
8
- messageSection: 'confirm_statement_mapping',
9
- messageText: 'failed',
10
- type: 'error',
11
- });
7
+ function createConfirmStatementMappingErrorSnackbar(apiMessage) {
8
+ const trimmedApiMessage = apiMessage?.trim();
9
+ if (trimmedApiMessage != null && trimmedApiMessage !== '') {
10
+ return openSnackbar({
11
+ messageSection: 'confirm_statement_mapping',
12
+ messageText: 'failed',
13
+ type: 'error',
14
+ variables: [
15
+ {
16
+ variableName: '_api-error_',
17
+ variableValue: trimmedApiMessage,
18
+ },
19
+ ],
20
+ });
21
+ }
22
+ return openSnackbar({
23
+ messageSection: 'confirm_statement_mapping',
24
+ messageText: 'failed',
25
+ type: 'error',
26
+ });
27
+ }
12
28
  export const confirmStatementMappingEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(confirmStatementMapping.match), exhaustMap((action) => {
13
29
  const { accountId, selectedPeriod, statementUploadId, accountMappings } = action.payload;
14
30
  const confirmMappingApi$ = zeniAPI.postAndGetJSON(`${zeniAPI.apiEndPoints.reconciliationMicroServiceBaseUrl}/1.0/reconciliation/statements/${statementUploadId}/confirm-mapping`, {
@@ -33,11 +49,11 @@ export const confirmStatementMappingEpic = (actions$, _state$, zeniAPI) => actio
33
49
  selectedPeriod,
34
50
  error: response.status,
35
51
  mappingConflict,
36
- }), confirmStatementMappingErrorSnackbar);
52
+ }), createConfirmStatementMappingErrorSnackbar(response.status.message));
37
53
  }), catchError((error) => of(confirmStatementMappingFailure({
38
54
  accountId,
39
55
  selectedPeriod,
40
56
  error: createZeniAPIStatus('Unexpected Error', 'Confirm Statement Mapping REST API call errored out ' +
41
57
  (error instanceof Error ? error.message : String(error))),
42
- }), confirmStatementMappingErrorSnackbar)));
58
+ }), createConfirmStatementMappingErrorSnackbar())));
43
59
  }));
@@ -3,9 +3,9 @@ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
3
  import { toMonthYearPeriodId } from '../../../../commonStateTypes/timePeriod';
4
4
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
5
  import { mapCombinedStatementUpdateLocalDataToRequestBody } from '../../helpers/mapStatementUpdateLocalDataToRequestBody';
6
- import { getStatementUploadsFromParsedData } from '../../selectors/statementParseViewSelector';
7
6
  import { isStatementDateConflictResponse, transformStatementDateConflictToState, transformUpdateStatementInfoPayloadToState, } from '../../payload/reconciliationPayload';
8
7
  import { submitCombinedStatementUpdate, submitStatementUpdateFailure, submitStatementUpdateSuccess, updateParsedStatementData, } from '../../reducers/reconciliationViewReducer';
8
+ import { getStatementUploadsFromParsedData } from '../../selectors/statementParseViewSelector';
9
9
  export const updateCombinedStatementInfoEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(submitCombinedStatementUpdate.match), switchMap((action) => {
10
10
  const { accountId, selectedPeriod } = action.payload;
11
11
  const accountRecon = state$.value.expenseAutomationReconciliationViewState
@@ -254,7 +254,7 @@ export function transformConfirmStatementMappingConflictToState(response) {
254
254
  }
255
255
  export function transformUpdateStatementInfoPayloadToState(payload) {
256
256
  if (isUpdateStatementInfoCombinedResponsePayload(payload) === true) {
257
- const statementUploads = payload.statement_uploads.map(transformStatementUploadPayloadToState);
257
+ const statementUploads = payload.statement_uploads.map((item) => transformStatementUploadPayloadToState(item.statement_upload));
258
258
  const anchorUpload = statementUploads.find((upload) => upload.statementMeta.isCombinedStatementAnchor === true) ?? statementUploads[0];
259
259
  return {
260
260
  statementUpload: anchorUpload,
@@ -464,6 +464,7 @@ const expenseAutomationReconciliationView = createSlice({
464
464
  accountRecon.confirmStatementMappingConflict = null;
465
465
  if (accountRecon.localData != null) {
466
466
  accountRecon.localData.statementUpdateLocalDataByUploadId = undefined;
467
+ accountRecon.localData.combinedStatementAccountMappings = undefined;
467
468
  }
468
469
  if (statementAccountScope != null) {
469
470
  accountRecon.localData = {
@@ -542,6 +543,7 @@ const expenseAutomationReconciliationView = createSlice({
542
543
  accountRecon.confirmStatementMappingConflict = null;
543
544
  if (accountRecon.localData != null) {
544
545
  accountRecon.localData.statementUpdateLocalDataByUploadId = undefined;
546
+ accountRecon.localData.combinedStatementAccountMappings = undefined;
545
547
  accountRecon.localData.statementAccountScope = undefined;
546
548
  }
547
549
  }
@@ -772,6 +774,9 @@ const expenseAutomationReconciliationView = createSlice({
772
774
  };
773
775
  accountRecon.confirmStatementMappingConflict = null;
774
776
  accountRecon.confirmedCombinedStatementUploads = statementUploads;
777
+ if (accountRecon.localData != null) {
778
+ accountRecon.localData.combinedStatementAccountMappings = undefined;
779
+ }
775
780
  const combinedStatementDetails = accountRecon.parsedStatementData?.statementUpload
776
781
  .combinedStatementDetails;
777
782
  if (combinedStatementDetails != null) {
@@ -475,8 +475,18 @@ export const getExpenseAutomationReconciliationView = createSelector((state) =>
475
475
  });
476
476
  export const getReparseStatementStatusByAccountId = (state, accountId) => getExpenseAutomationReconciliationView(state)
477
477
  .accountReconciliationsByAccountID[accountId]?.reparseStatementStatus;
478
- export function getLedgerAccountsForCombinedStatementMapping(accountReconciliationsByAccountID) {
479
- return Object.values(accountReconciliationsByAccountID).map((accountReconciliation) => accountReconciliation.accountReconEntity.account);
478
+ export function getLedgerAccountsForCombinedStatementMapping(accountReconciliationsByAccountID, selectedAccountId) {
479
+ const selectedAccountType = accountReconciliationsByAccountID[selectedAccountId]?.accountReconEntity
480
+ .account.accountType;
481
+ const shouldFilterByPaymentAccountType = selectedAccountType === 'bank' || selectedAccountType === 'credit_card';
482
+ return Object.values(accountReconciliationsByAccountID)
483
+ .map((accountReconciliation) => accountReconciliation.accountReconEntity.account)
484
+ .filter((account) => {
485
+ if (shouldFilterByPaymentAccountType !== true) {
486
+ return true;
487
+ }
488
+ return account.accountType === selectedAccountType;
489
+ });
480
490
  }
481
491
  const UNAVAILABLE_LEDGER_ACCOUNT_RECON_STATUSES = new Set(['in_progress', 'reconciled', 'reconciled_on_qbo']);
482
492
  export function getUnavailableLedgerAccountIds(accountReconciliationsByAccountID) {
@@ -488,6 +498,27 @@ export function getUnavailableLedgerAccountIds(accountReconciliationsByAccountID
488
498
  })
489
499
  .map((accountReconciliation) => accountReconciliation.accountReconEntity.account.accountId);
490
500
  }
501
+ /** Selectable accounts that already have a statement — mapping will override that work. */
502
+ export function getLedgerAccountIdsWithStatementOverrideWarning(accountReconciliationsByAccountID, selectedAccountId) {
503
+ return Object.values(accountReconciliationsByAccountID)
504
+ .filter((accountReconciliation) => {
505
+ const entity = accountReconciliation.accountReconEntity;
506
+ const accountId = entity.account.accountId;
507
+ if (selectedAccountId != null && accountId === selectedAccountId) {
508
+ return false;
509
+ }
510
+ const hasUploadedStatement = entity.statementStatus?.code === 'uploaded' ||
511
+ entity.statementMetaData?.file != null ||
512
+ accountReconciliation.statementParseInProgress === true;
513
+ if (hasUploadedStatement !== true) {
514
+ return false;
515
+ }
516
+ const statusCode = entity.reconciliationStatus?.code;
517
+ return (statusCode == null ||
518
+ UNAVAILABLE_LEDGER_ACCOUNT_RECON_STATUSES.has(statusCode) !== true);
519
+ })
520
+ .map((accountReconciliation) => accountReconciliation.accountReconEntity.account.accountId);
521
+ }
491
522
  export const isAccountReconReport = (reportId) => {
492
523
  return reportId.endsWith('reconciliation');
493
524
  };
@@ -1,3 +1,4 @@
1
+ import { buildAccountingDetail, } from './editInvoicingCatalogItemDetailViewPayload';
1
2
  import { invoicingProductToCatalogItemFormSubFamily, isTieredCatalogPricingModel, } from './invoicingCatalogItemFormConfig';
2
3
  const trimOrUndefined = (value) => value.trim() !== '' ? value.trim() : undefined;
3
4
  const trimOrNull = (value) => value.trim() !== '' ? value.trim() : null;
@@ -35,7 +36,7 @@ const tierToPayload = (tier) => ({
35
36
  pricing_type: tier.pricingType,
36
37
  starting_unit: Number(tier.startingUnit) || 1,
37
38
  });
38
- const subFamilyToProductUpdate = (subFamily, itemType) => {
39
+ const subFamilyToProductUpdate = (subFamily, itemType, lookup) => {
39
40
  const tiered = isTieredCatalogPricingModel(subFamily.pricingModel);
40
41
  const data = {
41
42
  billing_cycles: hasValue(subFamily.billingCycles)
@@ -62,9 +63,12 @@ const subFamilyToProductUpdate = (subFamily, itemType) => {
62
63
  if (itemType === 'addon') {
63
64
  data.proration_type = subFamily.prorationType;
64
65
  }
66
+ // `null` rather than omitted when empty: an absent key on an update reads as
67
+ // "leave it alone", so a cleared account or class could never be saved.
68
+ data.accounting_detail = buildAccountingDetail(subFamily, lookup) ?? null;
65
69
  return { data, id: subFamily.id };
66
70
  };
67
- export const catalogDetailEditValuesToSavePayload = (values, itemType = 'plan', originalSubFamilies) => {
71
+ export const catalogDetailEditValuesToSavePayload = (values, lookup, itemType = 'plan', originalSubFamilies) => {
68
72
  const planData = {
69
73
  applicable_items: values.itemApplicability === 'restricted' ? values.applicableItems : [],
70
74
  enabled_for_checkout: values.enabledForCheckout,
@@ -96,12 +100,12 @@ export const catalogDetailEditValuesToSavePayload = (values, itemType = 'plan',
96
100
  planData.unit = unit;
97
101
  }
98
102
  const productUpdates = values.subFamilies.flatMap((subFamily) => {
99
- const update = subFamilyToProductUpdate(subFamily, itemType);
103
+ const update = subFamilyToProductUpdate(subFamily, itemType, lookup);
100
104
  const original = originalSubFamilies?.find((product) => product.id === subFamily.id);
101
105
  if (original == null) {
102
106
  return [update];
103
107
  }
104
- const originalUpdate = subFamilyToProductUpdate(subFamilyToEditValues(original), itemType);
108
+ const originalUpdate = subFamilyToProductUpdate(subFamilyToEditValues(original), itemType, lookup);
105
109
  const changedData = Object.fromEntries(Object.entries(update.data).filter(([key, value]) => JSON.stringify(value) !== JSON.stringify(originalUpdate.data[key])));
106
110
  return Object.keys(changedData).length > 0
107
111
  ? [{ data: changedData, id: update.id }]
@@ -113,21 +117,28 @@ export const catalogDetailEditValuesToSavePayload = (values, itemType = 'plan',
113
117
  };
114
118
  };
115
119
  /** Build POST /catalog/products body for Create sub-family. */
116
- export const createSubFamilyFormToPayload = (itemId, itemType, values) => {
120
+ export const createSubFamilyFormToPayload = (itemId, itemType, values, lookup) => {
117
121
  const name = values.name.trim();
118
122
  const priceCode = trimOrUndefined(values.priceCode) ?? toProductCode(name);
119
123
  const tiered = isTieredCatalogPricingModel(values.pricingModel);
120
124
  const payload = {
121
125
  currency_code: 'USD',
122
126
  free_quantity: Number(values.freeQuantity) || 0,
123
- is_taxable: true,
127
+ is_taxable: values.isTaxable,
124
128
  item_id: itemId,
125
129
  name,
126
130
  price: tiered ? 0 : (values.priceDollars?.amount ?? 0),
127
131
  price_code: priceCode,
128
132
  pricing_model: values.pricingModel || 'flat_fee',
129
- show_description_in_invoices: true,
133
+ show_description_in_invoices: values.showDescriptionInInvoices,
134
+ show_description_in_quotes: values.showDescriptionInQuotes,
130
135
  };
136
+ // The same accounting body the item payload sends, so a price point created
137
+ // from the dialog carries the account and class the form would have given it.
138
+ const accountingDetail = buildAccountingDetail(values, lookup);
139
+ if (accountingDetail != null) {
140
+ payload.accounting_detail = accountingDetail;
141
+ }
131
142
  if (tiered) {
132
143
  payload.tiers = (values.tiers ?? []).map(tierToPayload);
133
144
  }
@@ -157,10 +168,13 @@ export const createSubFamilyFormToPayload = (itemId, itemType, values) => {
157
168
  return payload;
158
169
  };
159
170
  export const productToCreateSubFamilyFormValues = (product) => ({
171
+ accountingCategory: product?.accountingDetail?.accountingCategory1 ?? '',
172
+ accountingCode: product?.accountingDetail?.accountingCode ?? '',
160
173
  billingCycles: product?.billingCycles != null ? String(product.billingCycles) : '',
161
174
  description: product?.description ?? '',
162
175
  freeQuantity: product?.freeQuantity != null ? String(product.freeQuantity) : '0',
163
176
  invoiceNotes: product?.invoiceNotes ?? '',
177
+ isTaxable: product?.isTaxable ?? true,
164
178
  name: product?.name != null ? `${product.name} (copy)` : '',
165
179
  period: product?.period != null ? String(product.period) : '1',
166
180
  periodUnit: product?.periodUnit ?? 'month',
@@ -168,6 +182,9 @@ export const productToCreateSubFamilyFormValues = (product) => ({
168
182
  priceDollars: product?.price,
169
183
  pricingModel: product?.pricingModel ?? 'flat_fee',
170
184
  prorationType: product?.prorationType ?? 'site_default',
185
+ showDescriptionInInvoices: product?.showDescriptionInInvoices ?? true,
186
+ showDescriptionInQuotes: product?.showDescriptionInQuotes ?? false,
187
+ sku: product?.accountingDetail?.sku ?? '',
171
188
  trialEndAction: product?.trialEndAction ?? 'site_default',
172
189
  trialPeriod: product?.trialPeriod != null ? String(product.trialPeriod) : '',
173
190
  trialPeriodUnit: product?.trialPeriodUnit ?? 'day',
@@ -9,7 +9,7 @@ const toProductCode = (value) => value
9
9
  .toLowerCase()
10
10
  .replace(/[^a-z0-9]+/g, '-')
11
11
  .replace(/^-|-$/g, '');
12
- const buildAccountingDetail = (subFamily, lookup) => {
12
+ export const buildAccountingDetail = (subFamily, lookup) => {
13
13
  const detail = {};
14
14
  if (subFamily.sku.trim() !== '') {
15
15
  detail.sku = subFamily.sku.trim();
@@ -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, isInvoicingAccountingPickerDataLoading, isInvoicingQboConnected, } from '../invoicingQboView/invoicingQboViewSelector';
4
+ import { getInvoicingQboView, 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,
@@ -49,17 +49,15 @@ export const getInvoicingCatalogItemFormView = (state, id) => {
49
49
  const applicableItemOptions = id != null
50
50
  ? allApplicableItemOptions.filter((option) => option.id !== id)
51
51
  : allApplicableItemOptions;
52
- const isQboConnected = isInvoicingQboConnected(state);
52
+ const qboView = isInvoicingQboConnected(state)
53
+ ? getInvoicingQboView(state)
54
+ : undefined;
53
55
  const { billingPeriodUnitOptions, catalogItemTypeOptions, catalogTierTypeOptionsByPricingModel, catalogTrialEndActionOptions, catalogTrialUnitOptions, } = getInvoicingConfigView(state);
54
56
  return {
55
57
  fieldHelp: invoicingFieldHelpForGroup(state, 'catalog'),
56
58
  isQboPickerDataLoading: isInvoicingAccountingPickerDataLoading(state),
57
- qboClassHierarchy: isQboConnected
58
- ? getInvoicingQboView(state).nestedClassHierarchy
59
- : [],
60
- qboIncomeAccountHierarchy: isQboConnected
61
- ? invoicingAccountHierarchyForTypes(state, INVOICING_INCOME_ACCOUNT_TYPES)
62
- : [],
59
+ qboAccountHierarchy: qboView?.nestedAccountHierarchy ?? [],
60
+ qboClassHierarchy: qboView?.nestedClassHierarchy ?? [],
63
61
  applicabilityValues: INVOICING_CATALOG_APPLICABILITY_VALUES,
64
62
  applicableItemOptions,
65
63
  localData,
@@ -2,6 +2,8 @@ import { of } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { mergeInvoicingPlanSubFamily } from '../../../entity/invoicing/plan/planReducer';
4
4
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
5
+ import { getInvoicingCatalogAccountingLookup } from '../invoicingQboView/invoicingQboViewSelector';
6
+ import { createSubFamilyFormToPayload } from './catalogDetailEditPayload';
5
7
  import { archiveInvoicingCatalogSubFamily, createInvoicingCatalogSubFamily, submitInvoicingCatalogItemFormFailure, submitInvoicingCatalogItemFormSuccess, } from './editInvoicingCatalogItemDetailViewReducer';
6
8
  const subFamilySuccessActions = (parentItemId, subFamilyPayload) => [
7
9
  mergeInvoicingPlanSubFamily(parentItemId, subFamilyPayload),
@@ -10,8 +12,12 @@ const subFamilySuccessActions = (parentItemId, subFamilyPayload) => [
10
12
  kind: 'plan',
11
13
  }),
12
14
  ];
13
- export const createInvoicingCatalogSubFamilyEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(createInvoicingCatalogSubFamily.match), mergeMap((action) => {
14
- const { data, parentItemId } = action.payload;
15
+ export const createInvoicingCatalogSubFamilyEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(createInvoicingCatalogSubFamily.match), mergeMap((action) => {
16
+ const { itemType, parentItemId, values } = action.payload;
17
+ const state = state$.value;
18
+ // Built here so the dialog can stay in picked ids: resolving one to the
19
+ // account record the payload carries needs the shared account list.
20
+ const data = createSubFamilyFormToPayload(parentItemId, itemType, values, getInvoicingCatalogAccountingLookup(state));
15
21
  const baseUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0`;
16
22
  return zeniAPI
17
23
  .postAndGetJSON(`${baseUrl}/catalog/products`, data)
@@ -6,26 +6,31 @@ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiE
6
6
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
7
7
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
8
8
  import { addInvoicingCatalogItem } from '../invoicingCatalogListView/invoicingCatalogListViewReducer';
9
- import { invoicingQboAccountByQboId, invoicingQboClassByQboId, } from '../invoicingQboView/invoicingQboViewSelector';
9
+ import { getInvoicingCatalogAccountingLookup } from '../invoicingQboView/invoicingQboViewSelector';
10
+ import { catalogDetailEditValuesToSavePayload, } from './catalogDetailEditPayload';
10
11
  import { localDataToSaveInvoicingCatalogItemPayload, } from './editInvoicingCatalogItemDetailViewPayload';
11
12
  import { saveInvoicingCatalogItem, submitInvoicingCatalogItemForm, submitInvoicingCatalogItemFormFailure, submitInvoicingCatalogItemFormSuccess, } from './editInvoicingCatalogItemDetailViewReducer';
12
13
  export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter((action) => submitInvoicingCatalogItemForm.match(action) ||
13
14
  saveInvoicingCatalogItem.match(action)), mergeMap((action) => {
14
15
  const { id } = action.payload;
15
- // Draft-based create/edit form vs. detail-page inline edit (raw data).
16
+ const state = state$.value;
17
+ const accountingLookup = getInvoicingCatalogAccountingLookup(state);
18
+ // Both sources are form values, turned into request bodies here so the
19
+ // picked account and class become the records the service stores: the
20
+ // detail page's edit form, or the create/edit form's draft.
16
21
  let data;
22
+ let editProductUpdates;
17
23
  if (saveInvoicingCatalogItem.match(action)) {
18
- data = action.payload.data;
24
+ const { editValues, itemType, originalSubFamilies } = action.payload;
25
+ const savePayload = catalogDetailEditValuesToSavePayload(editValues, accountingLookup, itemType, originalSubFamilies);
26
+ data = savePayload.planData;
27
+ editProductUpdates = savePayload.productUpdates;
19
28
  }
20
29
  else {
21
- const viewState = state$.value.editInvoicingCatalogItemDetailViewState;
30
+ const viewState = state.editInvoicingCatalogItemDetailViewState;
22
31
  const localData = (id != null ? viewState.editDraftById[id] : viewState.newItemDraft) ??
23
32
  viewState.newItemDraft;
24
- const state = state$.value;
25
- data = localDataToSaveInvoicingCatalogItemPayload(localData, id == null, {
26
- accountByQboId: (qboId) => invoicingQboAccountByQboId(state, qboId),
27
- classByQboId: (qboId) => invoicingQboClassByQboId(state, qboId),
28
- });
33
+ data = localDataToSaveInvoicingCatalogItemPayload(localData, id == null, accountingLookup);
29
34
  }
30
35
  const baseUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0`;
31
36
  const path = id != null
@@ -88,7 +93,7 @@ export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actio
88
93
  return resultActions;
89
94
  };
90
95
  const productUpdates = saveInvoicingCatalogItem.match(action)
91
- ? (action.payload.productUpdates ?? [])
96
+ ? (editProductUpdates ?? [])
92
97
  : [];
93
98
  if (productUpdates.length === 0) {
94
99
  return completeSave([]);
@@ -18,7 +18,6 @@ export const fetchInvoicingAccountingPickersEpic = (actions$, state$) => actions
18
18
  accountList.fetchState !== 'In-Progress') {
19
19
  // Same arguments the vendor and categorization screens pass, so this
20
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
21
  actions.push(fetchAccountList(INVOICING_ACCOUNT_LIST_KEY, true));
23
22
  }
24
23
  const classList = state.classListState;
@@ -1,8 +1,9 @@
1
+ import { createSelector } from '@reduxjs/toolkit';
1
2
  import { getAccountList, getNestedAccountListHierarchy, } from '../../accountList/accountListSelector';
2
3
  import { getClassList, getNestedClassListHierarchy, } from '../../classList/classListSelector';
3
4
  // 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.
5
+ // whichever screen loads first satisfies the rest. Invoicing renders the same
6
+ // accounts they do, so it does not need its own copy.
6
7
  export const INVOICING_ACCOUNT_LIST_KEY = 'accountList';
7
8
  /** Whether QBO is connected, without building the option lists to find out. */
8
9
  export const isInvoicingQboConnected = (state) => state.invoicingQboViewState.connection?.connected === true;
@@ -29,14 +30,6 @@ export const isInvoicingAccountingPickerDataLoading = (state) => {
29
30
  };
30
31
  /** The QBO connection fetch on its own, without building the bundled view. */
31
32
  export const getInvoicingQboConnectionFetchState = (state) => state.invoicingQboViewState.connectionFetchState;
32
- /**
33
- * Account types that can back invoicing revenue. Named here so screens ask for
34
- * "income accounts" rather than repeating the accounting service's vocabulary.
35
- */
36
- export const INVOICING_INCOME_ACCOUNT_TYPES = [
37
- 'income',
38
- 'other_income',
39
- ];
40
33
  /**
41
34
  * The account behind a picker value. The pickers submit the QuickBooks id, so
42
35
  * that is what callers hold; the payload builders need the whole entity to send
@@ -77,6 +70,17 @@ export const invoicingQboClassByQboId = (state, qboId) => {
77
70
  return undefined;
78
71
  }
79
72
  };
73
+ /**
74
+ * The resolver every catalog and settings payload builder needs: a picked
75
+ * QuickBooks id turned back into the account or class record the request body
76
+ * carries. Memoized so callers get one stable object rather than a fresh pair of
77
+ * closures per store change, which in `mapStateToProps` would re-render on every
78
+ * unrelated action.
79
+ */
80
+ export const getInvoicingCatalogAccountingLookup = createSelector((state) => state, (state) => ({
81
+ accountByQboId: (qboId) => invoicingQboAccountByQboId(state, qboId),
82
+ classByQboId: (qboId) => invoicingQboClassByQboId(state, qboId),
83
+ }));
80
84
  /**
81
85
  * Accounts and classes as the invoicing pickers need them. Both come from the
82
86
  * shared accounting-service lists, so whatever another screen already loaded
@@ -84,17 +88,12 @@ export const invoicingQboClassByQboId = (state, qboId) => {
84
88
  * API stores; an account without one cannot be posted, so it is dropped rather
85
89
  * than offered.
86
90
  */
87
- const invoicingAccountingOptions = (state, accountTypes) => {
91
+ const invoicingAccountingOptions = (state) => {
88
92
  const { accounts } = getAccountList(state.accountState, state.accountListState, INVOICING_ACCOUNT_LIST_KEY);
89
93
  return accounts.flatMap((account) => {
90
94
  if (account.qboId == null || account.isDeleted === true) {
91
95
  return [];
92
96
  }
93
- if (accountTypes != null &&
94
- (account.accountType == null ||
95
- !accountTypes.includes(account.accountType))) {
96
- return [];
97
- }
98
97
  return [
99
98
  {
100
99
  id: String(account.qboId),
@@ -151,26 +150,6 @@ const invoicingAccountingClassOptions = (state) => {
151
150
  },
152
151
  ]);
153
152
  };
154
- /**
155
- * The account tree narrowed to the given account types. The tree itself carries
156
- * no account type, so the flat account list supplies that and the matching
157
- * `qboId`s are kept — which is why callers that only need "income accounts, as a
158
- * tree" do not have to hold a second flat list to filter with.
159
- */
160
- export const invoicingAccountHierarchyForTypes = (state, accountTypes) => {
161
- const allowedQboIds = new Set(invoicingAccountingOptions(state, accountTypes).map((account) => account.id));
162
- const keepMatching = (nodes) => nodes.flatMap((node) => {
163
- const children = keepMatching(node.children);
164
- const isMatch = node.qboId != null && allowedQboIds.has(String(node.qboId));
165
- // A parent that is not itself a match is still kept when it holds one, so
166
- // the tree's shape survives the filter.
167
- if (!isMatch && children.length === 0) {
168
- return [];
169
- }
170
- return [{ ...node, children }];
171
- });
172
- return keepMatching(getNestedAccountListHierarchy(state.accountListState, INVOICING_ACCOUNT_LIST_KEY));
173
- };
174
153
  /**
175
154
  * Single bundled view for the QBO integration settings screen — the
176
155
  * connection, the account and class options, sync health, and the fetch/action