@zeniai/client-epic-state 5.2.10-betaAK0 → 5.2.10-betaAK2

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 (43) hide show
  1. package/lib/entity/invoicing/invoicingCommonPayload.d.ts +15 -3
  2. package/lib/entity/invoicing/invoicingCommonPayload.js +18 -2
  3. package/lib/epic.d.ts +1 -1
  4. package/lib/epic.js +10 -5
  5. package/lib/esm/entity/invoicing/invoicingCommonPayload.js +16 -1
  6. package/lib/esm/epic.js +6 -1
  7. package/lib/esm/index.js +5 -3
  8. package/lib/esm/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +18 -4
  9. package/lib/esm/view/invoicing/settingsView/epics/connectInvoicingStripeEpic.js +27 -0
  10. package/lib/esm/view/invoicing/settingsView/epics/disconnectInvoicingStripeEpic.js +24 -0
  11. package/lib/esm/view/invoicing/settingsView/epics/fetchInvoicingSettingsEpic.js +19 -0
  12. package/lib/esm/view/invoicing/settingsView/epics/saveInvoicingSettingsEpic.js +54 -0
  13. package/lib/esm/view/invoicing/settingsView/epics/settingsViewActionType.js +1 -0
  14. package/lib/esm/view/invoicing/settingsView/epics/submitInvoicingQboSettingsEpic.js +22 -0
  15. package/lib/esm/view/invoicing/settingsView/invoicingQboSettingsFormConfig.js +52 -0
  16. package/lib/esm/view/invoicing/settingsView/settingsViewReducer.js +21 -1
  17. package/lib/esm/view/invoicing/settingsView/settingsViewSelector.js +48 -2
  18. package/lib/index.d.ts +8 -4
  19. package/lib/index.js +22 -11
  20. package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +18 -4
  21. package/lib/view/invoicing/settingsView/epics/connectInvoicingStripeEpic.d.ts +6 -0
  22. package/lib/view/invoicing/settingsView/epics/connectInvoicingStripeEpic.js +31 -0
  23. package/lib/view/invoicing/settingsView/epics/disconnectInvoicingStripeEpic.d.ts +11 -0
  24. package/lib/view/invoicing/settingsView/epics/disconnectInvoicingStripeEpic.js +28 -0
  25. package/lib/view/invoicing/settingsView/epics/fetchInvoicingSettingsEpic.d.ts +6 -0
  26. package/lib/view/invoicing/settingsView/epics/fetchInvoicingSettingsEpic.js +23 -0
  27. package/lib/view/invoicing/settingsView/epics/saveInvoicingSettingsEpic.d.ts +6 -0
  28. package/lib/view/invoicing/settingsView/epics/saveInvoicingSettingsEpic.js +58 -0
  29. package/lib/view/invoicing/settingsView/epics/settingsViewActionType.d.ts +5 -0
  30. package/lib/view/invoicing/settingsView/epics/settingsViewActionType.js +2 -0
  31. package/lib/view/invoicing/settingsView/epics/submitInvoicingQboSettingsEpic.d.ts +11 -0
  32. package/lib/view/invoicing/settingsView/epics/submitInvoicingQboSettingsEpic.js +26 -0
  33. package/lib/view/invoicing/settingsView/invoicingQboSettingsFormConfig.d.ts +28 -0
  34. package/lib/view/invoicing/settingsView/invoicingQboSettingsFormConfig.js +57 -0
  35. package/lib/view/invoicing/settingsView/settingsViewReducer.d.ts +4 -1
  36. package/lib/view/invoicing/settingsView/settingsViewReducer.js +22 -2
  37. package/lib/view/invoicing/settingsView/settingsViewSelector.d.ts +35 -0
  38. package/lib/view/invoicing/settingsView/settingsViewSelector.js +49 -2
  39. package/lib/view/invoicing/settingsView/settingsViewState.d.ts +8 -0
  40. package/package.json +1 -1
  41. package/lib/esm/view/invoicing/settingsView/settingsViewEpics.js +0 -107
  42. package/lib/view/invoicing/settingsView/settingsViewEpics.d.ts +0 -12
  43. package/lib/view/invoicing/settingsView/settingsViewEpics.js +0 -114
@@ -107,7 +107,6 @@ export interface InvoicingQboAccountMappingsPayload {
107
107
  default_income_account_id?: string | null;
108
108
  deposit_account?: Partial<AccountBasePayload> | null;
109
109
  deposit_account_id?: string | null;
110
- deposit_account_overrides?: Record<string, string> | null;
111
110
  discounts_account?: Partial<AccountBasePayload> | null;
112
111
  discounts_account_id?: string | null;
113
112
  rounding_account?: Partial<AccountBasePayload> | null;
@@ -131,7 +130,20 @@ export declare const firstSelectableInvoicingId: (candidates: readonly (string |
131
130
  * Read one account-mapping dropdown value by its config `code` (the codes in
132
131
  * `qbo_account_mapping_kind_options` match the `account_mappings` field names).
133
132
  */
134
- export declare function getInvoicingQboAccountMappingId(accountMappings: InvoicingQboAccountMappingsPayload | undefined, code: string): string | undefined;
133
+ export declare function getInvoicingQboAccountMappingId(accountMappings: InvoicingQboAccountMappingsPayload | undefined, code: InvoicingQboAccountMappingCode): string | undefined;
134
+ /**
135
+ * Return a copy of the account mappings with the dropdown slot named by
136
+ * `code` set to `accountId`. Unknown codes leave the mappings unchanged.
137
+ */
138
+ /** Every account-mapping slot the page can set, in the order it renders them. */
139
+ export declare const INVOICING_QBO_ACCOUNT_MAPPING_CODES: readonly ["deposit_account_id", "default_income_account_id", "discounts_account_id", "rounding_account_id", "write_off_account_id"];
140
+ export type InvoicingQboAccountMappingCode = (typeof INVOICING_QBO_ACCOUNT_MAPPING_CODES)[number];
141
+ /**
142
+ * Narrows a mapping code that arrived from the config API. Anything the client
143
+ * has no slot for returns undefined rather than being cast through: a code we
144
+ * cannot place is a mapping we cannot read or write.
145
+ */
146
+ export declare const toInvoicingQboAccountMappingCode: (code: string) => InvoicingQboAccountMappingCode | undefined;
135
147
  /**
136
148
  * Expand each mapping from the id the picker submitted to the whole account, the
137
149
  * shape the service stores. Written at the request boundary rather than in the
@@ -139,7 +151,7 @@ export declare function getInvoicingQboAccountMappingId(accountMappings: Invoici
139
151
  * is left out — a mapping the service cannot expand is worse than none.
140
152
  */
141
153
  export declare const withInvoicingQboMappedAccounts: (accountMappings: InvoicingQboAccountMappingsPayload | undefined, accountByQboId: (qboId: string) => AccountBase | undefined) => InvoicingQboAccountMappingsPayload | undefined;
142
- export declare function withInvoicingQboAccountMappingId(accountMappings: InvoicingQboAccountMappingsPayload | undefined, code: string, accountId: string): InvoicingQboAccountMappingsPayload;
154
+ export declare function withInvoicingQboAccountMappingId(accountMappings: InvoicingQboAccountMappingsPayload | undefined, code: InvoicingQboAccountMappingCode, accountId: string): InvoicingQboAccountMappingsPayload;
143
155
  export interface InvoicingQboSettingsPayload {
144
156
  account_mappings?: InvoicingQboAccountMappingsPayload | null;
145
157
  auto_create_items?: boolean;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ALL_INVOICING_FIELD_HELP_GROUPS = exports.withInvoicingQboMappedAccounts = exports.firstSelectableInvoicingId = exports.INVOICING_QBO_CUSTOMER_MATCHING_MODES = exports.INVOICING_DISCOUNT_LIFECYCLE_ACTIONS = exports.INVOICING_CATALOG_PRODUCT_LIFECYCLE_ACTIONS = exports.INVOICING_CATALOG_ITEM_LIFECYCLE_ACTIONS = exports.INVOICING_SUBSCRIPTION_LIFECYCLE_ACTIONS = exports.INVOICING_DUNNING_ACTIONS = void 0;
3
+ exports.ALL_INVOICING_FIELD_HELP_GROUPS = exports.withInvoicingQboMappedAccounts = exports.toInvoicingQboAccountMappingCode = exports.INVOICING_QBO_ACCOUNT_MAPPING_CODES = exports.firstSelectableInvoicingId = exports.INVOICING_QBO_CUSTOMER_MATCHING_MODES = exports.INVOICING_DISCOUNT_LIFECYCLE_ACTIONS = exports.INVOICING_CATALOG_PRODUCT_LIFECYCLE_ACTIONS = exports.INVOICING_CATALOG_ITEM_LIFECYCLE_ACTIONS = exports.INVOICING_SUBSCRIPTION_LIFECYCLE_ACTIONS = exports.INVOICING_DUNNING_ACTIONS = void 0;
4
4
  exports.toInvoicingDunningAction = toInvoicingDunningAction;
5
5
  exports.toInvoicingSubscriptionLifecycleAction = toInvoicingSubscriptionLifecycleAction;
6
6
  exports.toInvoicingSubscriptionLifecycleActionOption = toInvoicingSubscriptionLifecycleActionOption;
@@ -200,6 +200,21 @@ function getInvoicingQboAccountMappingId(accountMappings, code) {
200
200
  * Return a copy of the account mappings with the dropdown slot named by
201
201
  * `code` set to `accountId`. Unknown codes leave the mappings unchanged.
202
202
  */
203
+ /** Every account-mapping slot the page can set, in the order it renders them. */
204
+ exports.INVOICING_QBO_ACCOUNT_MAPPING_CODES = [
205
+ 'deposit_account_id',
206
+ 'default_income_account_id',
207
+ 'discounts_account_id',
208
+ 'rounding_account_id',
209
+ 'write_off_account_id',
210
+ ];
211
+ /**
212
+ * Narrows a mapping code that arrived from the config API. Anything the client
213
+ * has no slot for returns undefined rather than being cast through: a code we
214
+ * cannot place is a mapping we cannot read or write.
215
+ */
216
+ const toInvoicingQboAccountMappingCode = (code) => exports.INVOICING_QBO_ACCOUNT_MAPPING_CODES.find((knownCode) => knownCode === code);
217
+ exports.toInvoicingQboAccountMappingCode = toInvoicingQboAccountMappingCode;
203
218
  /** The object key holding the mapped account for each `*_account_id` code. */
204
219
  const INVOICING_QBO_MAPPED_ACCOUNT_KEY = {
205
220
  default_income_account_id: 'default_income_account',
@@ -219,7 +234,8 @@ const withInvoicingQboMappedAccounts = (accountMappings, accountByQboId) => {
219
234
  return undefined;
220
235
  }
221
236
  const expanded = { ...accountMappings };
222
- Object.entries(INVOICING_QBO_MAPPED_ACCOUNT_KEY).forEach(([code, objectKey]) => {
237
+ exports.INVOICING_QBO_ACCOUNT_MAPPING_CODES.forEach((code) => {
238
+ const objectKey = INVOICING_QBO_MAPPED_ACCOUNT_KEY[code];
223
239
  const accountId = getInvoicingQboAccountMappingId(accountMappings, code);
224
240
  if (accountId == null) {
225
241
  return;
package/lib/epic.d.ts CHANGED
@@ -265,7 +265,7 @@ import { ActionType as InvoicingTransactionListViewActionType } from './view/inv
265
265
  import { ActionType as InvoicingPaymentActionActionType } from './view/invoicing/paymentAction/paymentActionEpic';
266
266
  import { ActionType as InvoicingPromotionalCreditActionActionType } from './view/invoicing/promotionalCreditAction/promotionalCreditActionEpic';
267
267
  import { ActionType as RecordPaymentActionType } from './view/invoicing/recordPayment/recordPaymentEpic';
268
- import { ActionType as InvoicingSettingsViewActionType } from './view/invoicing/settingsView/settingsViewEpics';
268
+ import { InvoicingSettingsViewActionType } from './view/invoicing/settingsView/epics/settingsViewActionType';
269
269
  import { ActionType as InvoicingSubscriptionActionActionType } from './view/invoicing/subscriptionAction/subscriptionActionEpic';
270
270
  import { ActionType as FetchMonthClosePerformanceTrendActionType } from './view/monthEndCloseChecksView/epics/fetchMonthClosePerformanceTrendEpic';
271
271
  import { ActionType as FetchMonthEndCloseChecksActionType } from './view/monthEndCloseChecksView/epics/fetchMonthEndCloseChecksEpic';
package/lib/epic.js CHANGED
@@ -313,8 +313,12 @@ const issueCreditNoteEpic_1 = require("./view/invoicing/issueCreditNote/issueCre
313
313
  const paymentActionEpic_1 = require("./view/invoicing/paymentAction/paymentActionEpic");
314
314
  const promotionalCreditActionEpic_1 = require("./view/invoicing/promotionalCreditAction/promotionalCreditActionEpic");
315
315
  const recordPaymentEpic_1 = require("./view/invoicing/recordPayment/recordPaymentEpic");
316
+ const connectInvoicingStripeEpic_1 = require("./view/invoicing/settingsView/epics/connectInvoicingStripeEpic");
317
+ const disconnectInvoicingStripeEpic_1 = require("./view/invoicing/settingsView/epics/disconnectInvoicingStripeEpic");
318
+ const fetchInvoicingSettingsEpic_1 = require("./view/invoicing/settingsView/epics/fetchInvoicingSettingsEpic");
319
+ const saveInvoicingSettingsEpic_1 = require("./view/invoicing/settingsView/epics/saveInvoicingSettingsEpic");
320
+ const submitInvoicingQboSettingsEpic_1 = require("./view/invoicing/settingsView/epics/submitInvoicingQboSettingsEpic");
316
321
  const invoicingBrandingFormEpics_1 = require("./view/invoicing/settingsView/invoicingBrandingFormEpics");
317
- const settingsViewEpics_1 = require("./view/invoicing/settingsView/settingsViewEpics");
318
322
  const subscriptionActionEpic_1 = require("./view/invoicing/subscriptionAction/subscriptionActionEpic");
319
323
  const fetchMonthClosePerformanceTrendEpic_1 = require("./view/monthEndCloseChecksView/epics/fetchMonthClosePerformanceTrendEpic");
320
324
  const fetchMonthEndCloseChecksEpic_1 = require("./view/monthEndCloseChecksView/epics/fetchMonthEndCloseChecksEpic");
@@ -762,7 +766,7 @@ const ALL_EPICS = [
762
766
  confirmBulkUploadMatchEpic_1.confirmBulkUploadMatchEpic,
763
767
  confirmCardSetupIntentEpic_1.confirmCardSetupIntentEpic,
764
768
  confirmStatementMappingEpic_1.confirmStatementMappingEpic,
765
- settingsViewEpics_1.connectInvoicingStripeEpic,
769
+ connectInvoicingStripeEpic_1.connectInvoicingStripeEpic,
766
770
  convertAmountToHomeCurrencyEpic_1.convertAmountToHomeCurrencyEpic,
767
771
  createAddressEpic_1.createAddressEpic,
768
772
  createAutoTransferRuleEpic_1.createAutoTransferRuleEpic,
@@ -814,7 +818,7 @@ const ALL_EPICS = [
814
818
  deleteTransactionAttachmentEpic_1.deleteTransactionAttachmentEpic,
815
819
  deleteUserBankAccountEpic_1.deleteUserBankAccountEpic,
816
820
  disableAccountingProjectsEpic_1.disableAccountingProjectsEpic,
817
- settingsViewEpics_1.disconnectInvoicingStripeEpic,
821
+ disconnectInvoicingStripeEpic_1.disconnectInvoicingStripeEpic,
818
822
  dismissCapitalizationOnboardingEpic_1.dismissCapitalizationOnboardingEpic,
819
823
  doMagicLinkSignInEpic_1.doMagicLinkSignInEpic,
820
824
  signInUserEpic_1.doSignInEpic,
@@ -1023,7 +1027,7 @@ const ALL_EPICS = [
1023
1027
  customerPaymentMethodEpic_1.fetchInvoicingPlaidLinkTokenEpic,
1024
1028
  fetchInvoicingQboConnectionEpic_1.fetchInvoicingQboConnectionEpic,
1025
1029
  fetchInvoicingQboSyncHealthEpic_1.fetchInvoicingQboSyncHealthEpic,
1026
- settingsViewEpics_1.fetchInvoicingSettingsEpic,
1030
+ fetchInvoicingSettingsEpic_1.fetchInvoicingSettingsEpic,
1027
1031
  fetchInvoicingSubscriptionCountsEpic_1.fetchInvoicingSubscriptionCountsEpic,
1028
1032
  invoicingSubscriptionDetailViewEpics_1.fetchInvoicingSubscriptionDetailEpic,
1029
1033
  fetchInvoicingSubscriptionFormPageEpic_1.fetchInvoicingSubscriptionFormPageEpic,
@@ -1311,7 +1315,7 @@ const ALL_EPICS = [
1311
1315
  saveInvoicingCouponEpic_1.saveInvoicingCouponEpic,
1312
1316
  saveInvoicingCustomerEpic_1.saveInvoicingCustomerEpic,
1313
1317
  customerPaymentMethodEpic_1.saveInvoicingPaymentMethodEpic,
1314
- settingsViewEpics_1.saveInvoicingSettingsEpic,
1318
+ saveInvoicingSettingsEpic_1.saveInvoicingSettingsEpic,
1315
1319
  saveInvoicingSubscriptionEpic_1.saveInvoicingSubscriptionEpic,
1316
1320
  saveMagicLinkBankAccountEpic_1.saveMagicLinkBankAccountEpic,
1317
1321
  saveNewAddressEpic_1.saveNewAddressEpic,
@@ -1360,6 +1364,7 @@ const ALL_EPICS = [
1360
1364
  submitFeedbackEpic_1.submitFeedbackEpic,
1361
1365
  submitIntlVerificationEpic_1.submitIntlVerificationEpic,
1362
1366
  invoicingBrandingFormEpics_1.submitInvoicingBrandingFormEpic,
1367
+ submitInvoicingQboSettingsEpic_1.submitInvoicingQboSettingsEpic,
1363
1368
  submitQuestionEpic_1.submitQuestionEpic,
1364
1369
  syncInvoicingSubscriptionCouponsEpic_1.syncInvoicingSubscriptionCouponsEpic,
1365
1370
  syncTabsAfterAutomatchEpic_1.syncTabsAfterAutomatchEpic,
@@ -174,6 +174,20 @@ export function getInvoicingQboAccountMappingId(accountMappings, code) {
174
174
  * Return a copy of the account mappings with the dropdown slot named by
175
175
  * `code` set to `accountId`. Unknown codes leave the mappings unchanged.
176
176
  */
177
+ /** Every account-mapping slot the page can set, in the order it renders them. */
178
+ export const INVOICING_QBO_ACCOUNT_MAPPING_CODES = [
179
+ 'deposit_account_id',
180
+ 'default_income_account_id',
181
+ 'discounts_account_id',
182
+ 'rounding_account_id',
183
+ 'write_off_account_id',
184
+ ];
185
+ /**
186
+ * Narrows a mapping code that arrived from the config API. Anything the client
187
+ * has no slot for returns undefined rather than being cast through: a code we
188
+ * cannot place is a mapping we cannot read or write.
189
+ */
190
+ export const toInvoicingQboAccountMappingCode = (code) => INVOICING_QBO_ACCOUNT_MAPPING_CODES.find((knownCode) => knownCode === code);
177
191
  /** The object key holding the mapped account for each `*_account_id` code. */
178
192
  const INVOICING_QBO_MAPPED_ACCOUNT_KEY = {
179
193
  default_income_account_id: 'default_income_account',
@@ -193,7 +207,8 @@ export const withInvoicingQboMappedAccounts = (accountMappings, accountByQboId)
193
207
  return undefined;
194
208
  }
195
209
  const expanded = { ...accountMappings };
196
- Object.entries(INVOICING_QBO_MAPPED_ACCOUNT_KEY).forEach(([code, objectKey]) => {
210
+ INVOICING_QBO_ACCOUNT_MAPPING_CODES.forEach((code) => {
211
+ const objectKey = INVOICING_QBO_MAPPED_ACCOUNT_KEY[code];
197
212
  const accountId = getInvoicingQboAccountMappingId(accountMappings, code);
198
213
  if (accountId == null) {
199
214
  return;
package/lib/esm/epic.js CHANGED
@@ -310,8 +310,12 @@ import { issueCreditNoteEpic } from './view/invoicing/issueCreditNote/issueCredi
310
310
  import { runInvoicingPaymentActionEpic, } from './view/invoicing/paymentAction/paymentActionEpic';
311
311
  import { addPromotionalCreditsEpic, } from './view/invoicing/promotionalCreditAction/promotionalCreditActionEpic';
312
312
  import { recordPaymentEpic, } from './view/invoicing/recordPayment/recordPaymentEpic';
313
+ import { connectInvoicingStripeEpic } from './view/invoicing/settingsView/epics/connectInvoicingStripeEpic';
314
+ import { disconnectInvoicingStripeEpic } from './view/invoicing/settingsView/epics/disconnectInvoicingStripeEpic';
315
+ import { fetchInvoicingSettingsEpic } from './view/invoicing/settingsView/epics/fetchInvoicingSettingsEpic';
316
+ import { saveInvoicingSettingsEpic } from './view/invoicing/settingsView/epics/saveInvoicingSettingsEpic';
317
+ import { submitInvoicingQboSettingsEpic } from './view/invoicing/settingsView/epics/submitInvoicingQboSettingsEpic';
313
318
  import { initializeInvoicingBrandingAddressEpic, submitInvoicingBrandingFormEpic, } from './view/invoicing/settingsView/invoicingBrandingFormEpics';
314
- import { connectInvoicingStripeEpic, disconnectInvoicingStripeEpic, fetchInvoicingSettingsEpic, saveInvoicingSettingsEpic, } from './view/invoicing/settingsView/settingsViewEpics';
315
319
  import { runInvoicingSubscriptionActionEpic, } from './view/invoicing/subscriptionAction/subscriptionActionEpic';
316
320
  import { fetchMonthClosePerformanceTrendEpic, } from './view/monthEndCloseChecksView/epics/fetchMonthClosePerformanceTrendEpic';
317
321
  import { fetchMonthEndCloseChecksEpic, } from './view/monthEndCloseChecksView/epics/fetchMonthEndCloseChecksEpic';
@@ -1357,6 +1361,7 @@ const ALL_EPICS = [
1357
1361
  submitFeedbackEpic,
1358
1362
  submitIntlVerificationEpic,
1359
1363
  submitInvoicingBrandingFormEpic,
1364
+ submitInvoicingQboSettingsEpic,
1360
1365
  submitQuestionEpic,
1361
1366
  syncInvoicingSubscriptionCouponsEpic,
1362
1367
  syncTabsAfterAutomatchEpic,
package/lib/esm/index.js CHANGED
@@ -194,11 +194,11 @@ import { getExpenseAutomationTransactionView, getLastTransferEntryReplacement, }
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';
198
197
  import { toJEScheduleMainTab as toExpenseAutomationJEScheduleMainTab, toJEScheduleSortKey as toExpenseAutomationJEScheduleSortKey, } from './view/expenseAutomationView/types/jeSchedulesViewState';
199
198
  import { BATCH_FILE_STATUSES, isUnmatchedTabFileStatus, toBatchFileStatus, toBatchStatusValue, toBulkUploadPhase, toBulkUploadResultsTab, toBulkUploadSortKey, toMatchSource, toMissingReceiptsTab, } from './view/expenseAutomationView/types/missingReceiptsViewState';
200
199
  import { toMissingReceiptsSortKey as toExpenseAutomationMissingReceiptsSortKey, } from './view/expenseAutomationView/types/missingReceiptsViewState';
201
200
  import { toReconciliationTabsType, } from './view/expenseAutomationView/types/reconciliationViewState';
201
+ import { DEFAULT_TRANSACTION_GROUP, TRANSACTION_GROUPS, toTransactionGroup, } from './view/expenseAutomationView/types/transactionGroup';
202
202
  import { toTransactionsTabKey as toExpenseAutomationTransactionsTabKey, toTransactionsSortKey, } from './view/expenseAutomationView/types/transactionsViewState';
203
203
  import { fetchRegisteredInterests, notifyMeForFeature, } from './view/featureNotificationView/featureNotificationViewReducer';
204
204
  import { getFeatureNotificationView, getRegisteredInterests, getRegisteredInterestsByFeature, isFeatureInterestRegistered, } from './view/featureNotificationView/featureNotificationViewSelector';
@@ -849,8 +849,10 @@ export { getInvoicingCatalogItemDetail } from './view/invoicing/invoicingCatalog
849
849
  export { getInvoicingPlanById } from './entity/invoicing/plan/planSelector';
850
850
  export { getInvoicingProductById } from './entity/invoicing/product/productSelector';
851
851
  // ── Invoicing: settings ───────────────────────────────────────────────────────
852
- export { connectInvoicingStripe, disconnectInvoicingStripe, fetchInvoicingSettings, resetInvoicingBrandingFormDraft, resetSaveInvoicingSettings, saveInvoicingSettings, submitInvoicingBrandingForm, updateInvoicingBrandingFormDraft, updateInvoicingSettings, } from './view/invoicing/settingsView/settingsViewReducer';
853
- export { getInvoicingBrandingFormView, getInvoicingSettings, getInvoicingSettingsFetchState, getInvoicingSettingsHubView, getInvoicingSettingsView, } from './view/invoicing/settingsView/settingsViewSelector';
852
+ export { connectInvoicingStripe, disconnectInvoicingStripe, fetchInvoicingSettings, resetInvoicingBrandingFormDraft, resetInvoicingQboSettingsDraft, resetSaveInvoicingSettings, saveInvoicingSettings, submitInvoicingBrandingForm, submitInvoicingQboSettings, updateInvoicingBrandingFormDraft, updateInvoicingQboSettingsDraft, updateInvoicingSettings, } from './view/invoicing/settingsView/settingsViewReducer';
853
+ export { getInvoicingBrandingFormView, getInvoicingQboSettingsFormView, getInvoicingSettings, getInvoicingSettingsFetchState, getInvoicingSettingsHubView, getInvoicingSettingsView, } from './view/invoicing/settingsView/settingsViewSelector';
854
+ export { INVOICING_QBO_ACCOUNT_MAPPING_CODES, toInvoicingQboAccountMappingCode, } from './entity/invoicing/invoicingCommonPayload';
855
+ export { entityToInvoicingQboSettingsLocalData, initialInvoicingQboSettingsLocalData, invoicingQboSettingsLocalDataToPayload, } from './view/invoicing/settingsView/invoicingQboSettingsFormConfig';
854
856
  export { INVOICING_BUSINESS_ADDRESS_TYPE, entityToInvoicingBrandingFormLocalData, initialInvoicingBrandingFormLocalData, } from './view/invoicing/settingsView/invoicingBrandingFormConfig';
855
857
  export { isStripeConnectedFromSettings } from './view/invoicing/settingsView/invoicingSettingsHelpers';
856
858
  // ── Invoicing: QBO integration ────────────────────────────────────────────────
@@ -46,16 +46,30 @@ export const invoicingQboAccountByQboId = (state, qboId) => {
46
46
  if (qboId == null || qboId === '') {
47
47
  return undefined;
48
48
  }
49
- const { accounts } = getAccountList(state.accountState, state.accountListState, INVOICING_ACCOUNT_LIST_KEY);
50
- return accounts.find((account) => String(account.qboId) === qboId);
49
+ // `getAccountList` throws when a listed id is missing from the entity slice.
50
+ // That is survivable when looking one account up — the caller falls back to
51
+ // the id it already had — but not when it happens inside an epic, where the
52
+ // throw would take the whole request with it.
53
+ try {
54
+ const { accounts } = getAccountList(state.accountState, state.accountListState, INVOICING_ACCOUNT_LIST_KEY);
55
+ return accounts.find((account) => String(account.qboId) === qboId);
56
+ }
57
+ catch {
58
+ return undefined;
59
+ }
51
60
  };
52
61
  /** The class behind a picker value, for the same reason as the account above. */
53
62
  export const invoicingQboClassByQboId = (state, qboId) => {
54
63
  if (qboId == null || qboId === '') {
55
64
  return undefined;
56
65
  }
57
- const { classes } = getClassList(state.classState, state.classListState);
58
- return classes.find((accountingClass) => String(accountingClass.qboId) === qboId);
66
+ try {
67
+ const { classes } = getClassList(state.classState, state.classListState);
68
+ return classes.find((accountingClass) => String(accountingClass.qboId) === qboId);
69
+ }
70
+ catch {
71
+ return undefined;
72
+ }
59
73
  };
60
74
  /**
61
75
  * Accounts and classes as the invoicing pickers need them. Both come from the
@@ -0,0 +1,27 @@
1
+ import { of } from 'rxjs';
2
+ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
4
+ import { connectInvoicingStripe, fetchInvoicingSettings, invoicingStripeConnectionFailure, updateInvoicingStripeConnection, } from '../settingsViewReducer';
5
+ export const connectInvoicingStripeEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(connectInvoicingStripe.match), mergeMap((action) => {
6
+ const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/settings/stripe-connect/setup`;
7
+ const { api_key, publishable_key, webhook_secret } = action.payload;
8
+ const body = {
9
+ api_key,
10
+ publishable_key,
11
+ ...(webhook_secret != null && webhook_secret !== ''
12
+ ? { webhook_secret }
13
+ : {}),
14
+ };
15
+ return zeniAPI
16
+ .postAndGetJSON(apiUrl, body)
17
+ .pipe(switchMap((response) => {
18
+ if (isSuccessResponse(response) && response.data != null) {
19
+ return of(updateInvoicingStripeConnection({
20
+ stripe_connected: response.data.stripe_connected,
21
+ publishable_key: response.data.publishable_key,
22
+ }), fetchInvoicingSettings());
23
+ }
24
+ return of(invoicingStripeConnectionFailure(response.status));
25
+ }), catchError((error) => of(invoicingStripeConnectionFailure(createZeniAPIStatus('Unexpected Error', 'Stripe connect setup REST API call errored out ' +
26
+ JSON.stringify(error))))));
27
+ }));
@@ -0,0 +1,24 @@
1
+ import { of } from 'rxjs';
2
+ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
4
+ import { disconnectInvoicingStripe, fetchInvoicingSettings, invoicingStripeConnectionFailure, updateInvoicingStripeConnection, } from '../settingsViewReducer';
5
+ export const disconnectInvoicingStripeEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(disconnectInvoicingStripe.match), mergeMap(() => {
6
+ const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/settings/stripe-connect/callback`;
7
+ return zeniAPI
8
+ .deleteAndGetJSON(apiUrl)
9
+ .pipe(switchMap((response) => {
10
+ if (isSuccessResponse(response)) {
11
+ return of(updateInvoicingStripeConnection({
12
+ stripe_connected: false,
13
+ publishable_key: '',
14
+ }), fetchInvoicingSettings());
15
+ }
16
+ return of(invoicingStripeConnectionFailure(response.status));
17
+ }), catchError((error) => of(invoicingStripeConnectionFailure(createZeniAPIStatus('Unexpected Error', 'Stripe disconnect REST API call errored out ' +
18
+ JSON.stringify(error))))));
19
+ }));
20
+ /**
21
+ * Turns the QBO settings draft into a save. The draft lives in the store, so the
22
+ * screen fires one action and this reads what to send — the values do not have
23
+ * to travel back out through the page's props to get here.
24
+ */
@@ -0,0 +1,19 @@
1
+ import { of } from 'rxjs';
2
+ import { catchError, filter, switchMap } from 'rxjs/operators';
3
+ import { updateAddresses } from '../../../../entity/address/addressReducer';
4
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
+ import { fetchInvoicingSettings, updateInvoicingSettings, updateInvoicingSettingsFailure, } from '../settingsViewReducer';
6
+ export const fetchInvoicingSettingsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingSettings.match), switchMap(() => {
7
+ const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/settings`;
8
+ return zeniAPI.getJSON(apiUrl).pipe(switchMap((response) => {
9
+ if (isSuccessResponse(response) && response.data != null) {
10
+ const address = response.data.address;
11
+ return [
12
+ ...(address != null ? [updateAddresses([address])] : []),
13
+ updateInvoicingSettings(response.data),
14
+ ];
15
+ }
16
+ return of(updateInvoicingSettingsFailure(response.status));
17
+ }), catchError((error) => of(updateInvoicingSettingsFailure(createZeniAPIStatus('Unexpected Error', 'Fetch settings REST API call errored out ' +
18
+ JSON.stringify(error))))));
19
+ }));
@@ -0,0 +1,54 @@
1
+ import { from } from 'rxjs';
2
+ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
+ import { withInvoicingQboMappedAccounts, } from '../../../../entity/invoicing/invoicingCommonPayload';
4
+ import { apiErrorSnackbarVariables } from '../../../../entity/snackbar/snackbarApiError';
5
+ import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
6
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
7
+ import { invoicingQboAccountByQboId } from '../../invoicingQboView/invoicingQboViewSelector';
8
+ import { saveInvoicingSettings, saveInvoicingSettingsFailure, saveInvoicingSettingsSuccess, } from '../settingsViewReducer';
9
+ export const saveInvoicingSettingsEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(saveInvoicingSettings.match), mergeMap((action) => {
10
+ const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/settings`;
11
+ const { settings, snackbarSection } = action.payload;
12
+ // The QBO page edits mappings as picker ids; the service stores whole
13
+ // accounts. Expanded here, at the request boundary, so the page does not
14
+ // have to hold entities it only needs in order to save.
15
+ const qboSettings = settings.qbo;
16
+ const body = qboSettings?.account_mappings != null
17
+ ? {
18
+ ...settings,
19
+ qbo: {
20
+ ...qboSettings,
21
+ account_mappings: withInvoicingQboMappedAccounts(qboSettings.account_mappings, (qboId) => invoicingQboAccountByQboId(state$.value, qboId)),
22
+ },
23
+ }
24
+ : settings;
25
+ /** `status` surfaces the platform's error text; omit it to keep the generic copy. */
26
+ const snackbar = (messageText, status) => snackbarSection == null
27
+ ? []
28
+ : [
29
+ openSnackbar({
30
+ messageSection: snackbarSection,
31
+ messageText,
32
+ type: messageText === 'success' ? 'success' : 'error',
33
+ variables: apiErrorSnackbarVariables(status),
34
+ }),
35
+ ];
36
+ return zeniAPI
37
+ .putAndGetJSON(apiUrl, { ...body })
38
+ .pipe(switchMap((response) => {
39
+ if (isSuccessResponse(response) && response.data != null) {
40
+ return from([
41
+ saveInvoicingSettingsSuccess(response.data),
42
+ ...snackbar('success'),
43
+ ]);
44
+ }
45
+ return from([
46
+ saveInvoicingSettingsFailure(response.status),
47
+ ...snackbar('failed', response.status),
48
+ ]);
49
+ }), catchError((error) => from([
50
+ saveInvoicingSettingsFailure(createZeniAPIStatus('Unexpected Error', 'Save settings REST API call errored out ' +
51
+ JSON.stringify(error))),
52
+ ...snackbar('failed'),
53
+ ])));
54
+ }));
@@ -0,0 +1,22 @@
1
+ import { EMPTY, of } from 'rxjs';
2
+ import { filter, mergeMap, withLatestFrom } from 'rxjs/operators';
3
+ import { invoicingQboSettingsLocalDataToPayload } from '../invoicingQboSettingsFormConfig';
4
+ import { saveInvoicingSettings, submitInvoicingQboSettings, } from '../settingsViewReducer';
5
+ /**
6
+ * Saves the QBO local data. The screen fires this without a payload — the local
7
+ * data is already in the store, so sending it back through the action would make
8
+ * the page the carrier for state it does not own. Converting to the request body
9
+ * happens here too, keeping the wire shape out of the screen.
10
+ */
11
+ export const submitInvoicingQboSettingsEpic = (actions$, state$) => actions$.pipe(filter(submitInvoicingQboSettings.match), withLatestFrom(state$), mergeMap(([, state]) => {
12
+ const { qboDraft } = state.invoicingSettingsViewState;
13
+ // No local data means the settings response has not landed, so there is
14
+ // nothing the user could have edited yet.
15
+ if (qboDraft == null) {
16
+ return EMPTY;
17
+ }
18
+ return of(saveInvoicingSettings({
19
+ settings: { qbo: invoicingQboSettingsLocalDataToPayload(qboDraft) },
20
+ snackbarSection: 'invoicing_qbo_settings_save',
21
+ }));
22
+ }));
@@ -0,0 +1,52 @@
1
+ import { INVOICING_QBO_ACCOUNT_MAPPING_CODES, getInvoicingQboAccountMappingId, withInvoicingQboAccountMappingId, } from '../../../entity/invoicing/invoicingCommonPayload';
2
+ export const initialInvoicingQboSettingsLocalData = {
3
+ accountIdByMappingCode: {},
4
+ autoCreateItems: false,
5
+ closedPeriodVoidAsCreditMemo: false,
6
+ customerMatching: '',
7
+ isSyncEnabled: false,
8
+ syncStartDate: '',
9
+ trackingCategoriesEnabled: false,
10
+ };
11
+ /** The settings response as the page edits it. */
12
+ export const entityToInvoicingQboSettingsLocalData = (qbo) => {
13
+ const accountIdByMappingCode = {};
14
+ INVOICING_QBO_ACCOUNT_MAPPING_CODES.forEach((code) => {
15
+ const accountId = getInvoicingQboAccountMappingId(qbo?.account_mappings ?? undefined, code);
16
+ if (accountId != null) {
17
+ accountIdByMappingCode[code] = accountId;
18
+ }
19
+ });
20
+ return {
21
+ accountIdByMappingCode,
22
+ autoCreateItems: qbo?.auto_create_items === true,
23
+ closedPeriodVoidAsCreditMemo: qbo?.closed_period_void_as_credit_memo === true,
24
+ customerMatching: qbo?.customer_matching ?? '',
25
+ isSyncEnabled: qbo?.enabled === true,
26
+ syncStartDate: qbo?.sync_start_date ?? '',
27
+ trackingCategoriesEnabled: qbo?.tracking_categories_enabled === true,
28
+ };
29
+ };
30
+ /**
31
+ * The local data as the request body. Only the mappings the page holds are sent;
32
+ * an empty id means the slot was never set, so it is left out rather than sent
33
+ * blank.
34
+ */
35
+ export const invoicingQboSettingsLocalDataToPayload = (localData) => {
36
+ let accountMappings = {};
37
+ INVOICING_QBO_ACCOUNT_MAPPING_CODES.forEach((code) => {
38
+ const accountId = localData.accountIdByMappingCode[code] ?? '';
39
+ if (accountId !== '') {
40
+ accountMappings = withInvoicingQboAccountMappingId(accountMappings, code, accountId);
41
+ }
42
+ });
43
+ return {
44
+ account_mappings: accountMappings,
45
+ auto_create_items: localData.autoCreateItems,
46
+ closed_period_void_as_credit_memo: localData.closedPeriodVoidAsCreditMemo,
47
+ customer_matching: localData.customerMatching,
48
+ enabled: localData.isSyncEnabled,
49
+ sync_start_date: localData.syncStartDate !== '' ? localData.syncStartDate : null,
50
+ tracking_categories_enabled: localData.trackingCategoriesEnabled,
51
+ };
52
+ };
@@ -1,5 +1,6 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
2
  import { clearAll } from '../../../rootActions';
3
+ import { entityToInvoicingQboSettingsLocalData, } from './invoicingQboSettingsFormConfig';
3
4
  export const initialState = {
4
5
  fetchState: 'Not-Started',
5
6
  error: undefined,
@@ -22,6 +23,11 @@ const settingsView = createSlice({
22
23
  draft.settings = action.payload;
23
24
  draft.fetchState = 'Completed';
24
25
  draft.error = undefined;
26
+ // Seed the QBO page's local data from the response. An edit already in
27
+ // progress wins: a refetch behind the user must not discard it.
28
+ if (draft.qboDraft == null) {
29
+ draft.qboDraft = entityToInvoicingQboSettingsLocalData(action.payload.qbo);
30
+ }
25
31
  },
26
32
  updateInvoicingSettingsFailure(draft, action) {
27
33
  draft.fetchState = 'Error';
@@ -36,6 +42,9 @@ const settingsView = createSlice({
36
42
  saveInvoicingSettingsSuccess(draft, action) {
37
43
  draft.settings = action.payload;
38
44
  draft.saveFetchState = { fetchState: 'Completed', error: undefined };
45
+ // Re-seeded from what was just persisted, so the page keeps working off
46
+ // local data while showing the server's version of it.
47
+ draft.qboDraft = entityToInvoicingQboSettingsLocalData(action.payload.qbo);
39
48
  },
40
49
  saveInvoicingSettingsFailure(draft, action) {
41
50
  draft.saveFetchState = { fetchState: 'Error', error: action.payload };
@@ -54,6 +63,17 @@ const settingsView = createSlice({
54
63
  resetInvoicingBrandingFormDraft(draft) {
55
64
  draft.brandingDraft = undefined;
56
65
  },
66
+ updateInvoicingQboSettingsDraft(draft, action) {
67
+ draft.qboDraft = action.payload.localData;
68
+ },
69
+ // Trigger only — handled by submitInvoicingQboSettingsEpic, which reads the
70
+ // local data and converts it to the request body.
71
+ submitInvoicingQboSettings(draft) {
72
+ draft.saveFetchState = { fetchState: 'In-Progress', error: undefined };
73
+ },
74
+ resetInvoicingQboSettingsDraft(draft) {
75
+ draft.qboDraft = undefined;
76
+ },
57
77
  connectInvoicingStripe: {
58
78
  reducer(draft) {
59
79
  draft.stripeFetchState = { fetchState: 'In-Progress', error: undefined };
@@ -85,5 +105,5 @@ const settingsView = createSlice({
85
105
  });
86
106
  },
87
107
  });
88
- export const { fetchInvoicingSettings, updateInvoicingSettings, updateInvoicingSettingsFailure, saveInvoicingSettings, saveInvoicingSettingsSuccess, saveInvoicingSettingsFailure, resetSaveInvoicingSettings, updateInvoicingBrandingFormDraft, submitInvoicingBrandingForm, resetInvoicingBrandingFormDraft, connectInvoicingStripe, disconnectInvoicingStripe, updateInvoicingStripeConnection, invoicingStripeConnectionFailure, } = settingsView.actions;
108
+ export const { fetchInvoicingSettings, updateInvoicingSettings, updateInvoicingSettingsFailure, saveInvoicingSettings, saveInvoicingSettingsSuccess, saveInvoicingSettingsFailure, resetSaveInvoicingSettings, updateInvoicingBrandingFormDraft, submitInvoicingBrandingForm, resetInvoicingBrandingFormDraft, updateInvoicingQboSettingsDraft, submitInvoicingQboSettings, resetInvoicingQboSettingsDraft, connectInvoicingStripe, disconnectInvoicingStripe, updateInvoicingStripeConnection, invoicingStripeConnectionFailure, } = settingsView.actions;
89
109
  export default settingsView.reducer;
@@ -1,7 +1,9 @@
1
- import { getInvoicingQboAccountMappingId, } from '../../../entity/invoicing/invoicingCommonPayload';
1
+ import { getInvoicingQboAccountMappingId, toInvoicingQboAccountMappingCode, } from '../../../entity/invoicing/invoicingCommonPayload';
2
2
  import { getFormattedAddress } from '../../addressView/addressViewSelector';
3
3
  import { getInvoicingConfigView } from '../invoicingConfigView/invoicingConfigViewSelector';
4
+ import { getInvoicingQboView, isInvoicingAccountingPickerDataLoading, } from '../invoicingQboView/invoicingQboViewSelector';
4
5
  import { INVOICING_BUSINESS_ADDRESS_TYPE, entityToInvoicingBrandingFormLocalData, } from './invoicingBrandingFormConfig';
6
+ import { initialInvoicingQboSettingsLocalData, } from './invoicingQboSettingsFormConfig';
5
7
  /**
6
8
  * Shared primitive: the raw invoicing settings. Consumed internally by other
7
9
  * CES selectors (create-invoice, customer-detail, overview) and by non-settings
@@ -50,7 +52,12 @@ export function deriveInvoicingEnablePrerequisites(settings, isQboConnected, qbo
50
52
  // configured would let `every` pass vacuously and unlock the button early.
51
53
  const isQboConfigured = isQboConnected &&
52
54
  qboAccountMappingKindOptions.length > 0 &&
53
- qboAccountMappingKindOptions.every((option) => getInvoicingQboAccountMappingId(accountMappings, option.code) != null);
55
+ qboAccountMappingKindOptions.every((option) => {
56
+ // A kind the client has no slot for cannot count as configured.
57
+ const code = toInvoicingQboAccountMappingCode(option.code);
58
+ return (code != null &&
59
+ getInvoicingQboAccountMappingId(accountMappings, code) != null);
60
+ });
54
61
  return {
55
62
  canEnable: isStripeConnected && isQboConfigured,
56
63
  isQboConfigured,
@@ -92,6 +99,45 @@ export const getInvoicingSettingsHubView = (state) => {
92
99
  status,
93
100
  };
94
101
  };
102
+ /**
103
+ * Bundled view for the QBO configuration page: the draft it edits, the accounts
104
+ * and option lists its dropdowns render, the connection and sync health it
105
+ * reports, and the fetch states that decide between skeleton, form and error.
106
+ *
107
+ * `localData` is the draft once the user has edited, and the settings response
108
+ * before that — so the page has one field to read and CES owns when it becomes
109
+ * available, rather than the page copying settings into local state.
110
+ */
111
+ export const getInvoicingQboSettingsFormView = (state) => {
112
+ const { error, fetchState, qboDraft, saveFetchState, settings } = state.invoicingSettingsViewState;
113
+ const { fetchState: configFetchState, qboAccountMappingKindOptions, qboCustomerMatchingOptions, qboSyncStatusOptions, } = getInvoicingConfigView(state);
114
+ const qboView = getInvoicingQboView(state);
115
+ return {
116
+ accountMappingKindOptions: qboAccountMappingKindOptions,
117
+ accounts: qboView.accounts,
118
+ backfillState: qboView.backfillState,
119
+ connection: qboView.connection,
120
+ configFetchState,
121
+ customerMatchingOptions: qboCustomerMatchingOptions,
122
+ // The dropdowns cannot be built before both the settings and the accounts
123
+ // land, so the page waits on either rather than rendering empty pickers.
124
+ isLoading: fetchState === 'Not-Started' ||
125
+ fetchState === 'In-Progress' ||
126
+ isInvoicingAccountingPickerDataLoading(state),
127
+ isSyncEnabledSaved: settings?.qbo?.enabled === true,
128
+ // Seeded in the reducer when the settings response lands, so this is a plain
129
+ // read rather than a second place that decides what the page starts from.
130
+ localData: qboDraft ?? initialInvoicingQboSettingsLocalData,
131
+ nestedAccountHierarchy: qboView.nestedAccountHierarchy,
132
+ retryFetchStateByTaskId: qboView.retryFetchStateByTaskId,
133
+ saveState: saveFetchState.fetchState === 'Not-Started' && error != null
134
+ ? { fetchState, error }
135
+ : saveFetchState,
136
+ settingsFetchState: fetchState,
137
+ syncHealth: qboView.syncHealth,
138
+ syncStatusOptions: qboSyncStatusOptions,
139
+ };
140
+ };
95
141
  /**
96
142
  * Bundled view for the branding settings form: the draft `localData` (seeded
97
143
  * from the existing settings until the user edits), the business-address label