@zeniai/client-epic-state 5.1.82 → 5.1.83

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 (78) hide show
  1. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  2. package/lib/entity/snackbar/snackbarTypes.js +29 -0
  3. package/lib/esm/entity/snackbar/snackbarTypes.js +29 -0
  4. package/lib/esm/index.js +1 -1
  5. package/lib/esm/view/invoicing/createCreditNote/createCreditNoteEpic.js +24 -4
  6. package/lib/esm/view/invoicing/createInvoice/createInvoiceEpic.js +24 -4
  7. package/lib/esm/view/invoicing/createInvoice/createInvoiceSelector.js +37 -10
  8. package/lib/esm/view/invoicing/discountAction/discountActionEpic.js +33 -9
  9. package/lib/esm/view/invoicing/dunningAction/dunningActionEpic.js +35 -9
  10. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.js +5 -1
  11. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +32 -5
  12. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +23 -3
  13. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +24 -4
  14. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +24 -4
  15. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCouponsEpic.js +20 -3
  16. package/lib/esm/view/invoicing/invoiceAction/invoiceActionEpic.js +60 -17
  17. package/lib/esm/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.js +30 -0
  18. package/lib/esm/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.js +4 -29
  19. package/lib/esm/view/invoicing/invoicingQboView/epics/retryInvoicingQboSyncTaskEpic.js +29 -10
  20. package/lib/esm/view/invoicing/invoicingQboView/epics/startInvoicingQboBackfillEpic.js +28 -5
  21. package/lib/esm/view/invoicing/issueCreditNote/issueCreditNoteEpic.js +24 -4
  22. package/lib/esm/view/invoicing/issueCreditNote/issueCreditNoteSelector.js +22 -14
  23. package/lib/esm/view/invoicing/recordPayment/recordPaymentEpic.js +46 -7
  24. package/lib/esm/view/invoicing/settingsView/settingsViewEpics.js +26 -8
  25. package/lib/esm/view/invoicing/settingsView/settingsViewReducer.js +1 -1
  26. package/lib/esm/view/invoicing/settingsView/settingsViewSelector.js +4 -1
  27. package/lib/esm/view/invoicing/subscriptionAction/subscriptionActionEpic.js +41 -9
  28. package/lib/index.d.ts +4 -3
  29. package/lib/index.js +7 -8
  30. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  31. package/lib/view/invoicing/createCreditNote/createCreditNoteEpic.d.ts +2 -1
  32. package/lib/view/invoicing/createCreditNote/createCreditNoteEpic.js +23 -3
  33. package/lib/view/invoicing/createInvoice/createInvoiceEpic.d.ts +2 -1
  34. package/lib/view/invoicing/createInvoice/createInvoiceEpic.js +23 -3
  35. package/lib/view/invoicing/createInvoice/createInvoiceSelector.d.ts +22 -8
  36. package/lib/view/invoicing/createInvoice/createInvoiceSelector.js +37 -10
  37. package/lib/view/invoicing/discountAction/discountActionEpic.d.ts +2 -1
  38. package/lib/view/invoicing/discountAction/discountActionEpic.js +32 -8
  39. package/lib/view/invoicing/dunningAction/dunningActionEpic.d.ts +2 -1
  40. package/lib/view/invoicing/dunningAction/dunningActionEpic.js +34 -8
  41. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.d.ts +3 -1
  42. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.js +5 -1
  43. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.d.ts +2 -1
  44. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +31 -4
  45. package/lib/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.d.ts +2 -1
  46. package/lib/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +22 -2
  47. package/lib/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.d.ts +2 -1
  48. package/lib/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +23 -3
  49. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.d.ts +2 -1
  50. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +23 -3
  51. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCouponsEpic.d.ts +2 -1
  52. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCouponsEpic.js +19 -2
  53. package/lib/view/invoicing/invoiceAction/invoiceActionEpic.d.ts +2 -1
  54. package/lib/view/invoicing/invoiceAction/invoiceActionEpic.js +59 -16
  55. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewPayload.d.ts +13 -0
  56. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.d.ts +1 -1
  57. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.js +30 -0
  58. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.d.ts +11 -31
  59. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.js +4 -29
  60. package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewState.d.ts +15 -0
  61. package/lib/view/invoicing/invoicingQboView/epics/invoicingQboViewActionType.d.ts +2 -1
  62. package/lib/view/invoicing/invoicingQboView/epics/retryInvoicingQboSyncTaskEpic.js +28 -9
  63. package/lib/view/invoicing/invoicingQboView/epics/startInvoicingQboBackfillEpic.js +27 -4
  64. package/lib/view/invoicing/issueCreditNote/issueCreditNoteEpic.d.ts +2 -1
  65. package/lib/view/invoicing/issueCreditNote/issueCreditNoteEpic.js +23 -3
  66. package/lib/view/invoicing/issueCreditNote/issueCreditNoteSelector.d.ts +24 -7
  67. package/lib/view/invoicing/issueCreditNote/issueCreditNoteSelector.js +23 -16
  68. package/lib/view/invoicing/recordPayment/recordPaymentEpic.d.ts +2 -1
  69. package/lib/view/invoicing/recordPayment/recordPaymentEpic.js +45 -6
  70. package/lib/view/invoicing/settingsView/settingsViewEpics.d.ts +2 -1
  71. package/lib/view/invoicing/settingsView/settingsViewEpics.js +25 -7
  72. package/lib/view/invoicing/settingsView/settingsViewReducer.d.ts +10 -1
  73. package/lib/view/invoicing/settingsView/settingsViewReducer.js +1 -1
  74. package/lib/view/invoicing/settingsView/settingsViewSelector.d.ts +4 -2
  75. package/lib/view/invoicing/settingsView/settingsViewSelector.js +4 -1
  76. package/lib/view/invoicing/subscriptionAction/subscriptionActionEpic.d.ts +2 -1
  77. package/lib/view/invoicing/subscriptionAction/subscriptionActionEpic.js +40 -8
  78. package/package.json +1 -1
@@ -1,8 +1,9 @@
1
- import { EMPTY, of } from 'rxjs';
1
+ import { EMPTY, from, of } from 'rxjs';
2
2
  import { catchError, filter, map, mergeMap } from 'rxjs/operators';
3
3
  import { updateAddresses } from '../../../entity/address/addressReducer';
4
4
  import { extractInvoicingSubscriptionAddresses, } from '../../../entity/invoicing/invoicingSubscription/invoicingSubscriptionPayload';
5
5
  import { updateInvoicingSubscriptions } from '../../../entity/invoicing/invoicingSubscription/invoicingSubscriptionReducer';
6
+ import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
6
7
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
7
8
  import { resetNewAddressDataInLocalStore } from '../../addressView/addressViewReducer';
8
9
  import { submitInvoicingSubscriptionFormFailure, submitInvoicingSubscriptionFormSuccess, syncInvoicingSubscriptionCoupons, } from './editInvoicingSubscriptionDetailViewReducer';
@@ -39,7 +40,14 @@ export const syncInvoicingSubscriptionCouponsEpic = (actions$, _state$, zeniAPI)
39
40
  : couponMutation$(zeniAPI, baseUrl, subscriptionId, 'remove-coupon', prev);
40
41
  return removeStep$.pipe(mergeMap((removeResult) => {
41
42
  if (!removeResult.success) {
42
- return of(submitInvoicingSubscriptionFormFailure(failureStatus(removeResult)));
43
+ return from([
44
+ submitInvoicingSubscriptionFormFailure(failureStatus(removeResult)),
45
+ openSnackbar({
46
+ messageSection: 'invoicing_subscription_saved',
47
+ messageText: 'failed',
48
+ type: 'error',
49
+ }),
50
+ ]);
43
51
  }
44
52
  const applyStep$ = next === ''
45
53
  ? of({
@@ -51,6 +59,11 @@ export const syncInvoicingSubscriptionCouponsEpic = (actions$, _state$, zeniAPI)
51
59
  if (!applyResult.success) {
52
60
  return [
53
61
  submitInvoicingSubscriptionFormFailure(failureStatus(applyResult)),
62
+ openSnackbar({
63
+ messageSection: 'invoicing_subscription_saved',
64
+ messageText: 'failed',
65
+ type: 'error',
66
+ }),
54
67
  ];
55
68
  }
56
69
  const couponUpdated = applyResult.payload ?? removeResult.payload;
@@ -58,7 +71,11 @@ export const syncInvoicingSubscriptionCouponsEpic = (actions$, _state$, zeniAPI)
58
71
  if (couponUpdated != null) {
59
72
  resultActions.push(updateInvoicingSubscriptions([couponUpdated], 'merge'), updateAddresses(extractInvoicingSubscriptionAddresses([couponUpdated])));
60
73
  }
61
- resultActions.push(submitInvoicingSubscriptionFormSuccess({ subscriptionId }), resetNewAddressDataInLocalStore(INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE));
74
+ resultActions.push(submitInvoicingSubscriptionFormSuccess({ subscriptionId }), resetNewAddressDataInLocalStore(INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE), openSnackbar({
75
+ messageSection: 'invoicing_subscription_saved',
76
+ messageText: 'success',
77
+ type: 'success',
78
+ }));
62
79
  return resultActions;
63
80
  }));
64
81
  }));
@@ -1,13 +1,30 @@
1
- import { of } from 'rxjs';
1
+ import { from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateAddresses } from '../../../entity/address/addressReducer';
4
4
  import { extractInvoicingInvoiceAddresses } from '../../../entity/invoicing/invoice/invoicePayload';
5
5
  import { updateInvoices } from '../../../entity/invoicing/invoice/invoiceReducer';
6
+ import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
6
7
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
7
8
  import { runInvoicingInvoiceAction, runInvoicingInvoiceActionFailure, runInvoicingInvoiceActionSuccess, updateInvoicingInvoice, } from './invoiceActionReducer';
9
+ /** Snackbar copy per action kind; unlisted kinds run without one. */
10
+ const SNACKBAR_SECTION_BY_KIND = {
11
+ regenerate: 'invoicing_invoice_regenerated',
12
+ 'send-reminder': 'invoicing_invoice_reminder_sent',
13
+ void: 'invoicing_invoice_voided',
14
+ };
8
15
  export const runInvoicingInvoiceActionEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter((action) => runInvoicingInvoiceAction.match(action) &&
9
16
  action.payload.action !== 'issue-credit-note'), mergeMap((action) => {
10
17
  const { action: kind, body, invoiceId } = action.payload;
18
+ const snackbarSection = SNACKBAR_SECTION_BY_KIND[kind];
19
+ const snackbar = (messageText) => snackbarSection == null
20
+ ? []
21
+ : [
22
+ openSnackbar({
23
+ messageSection: snackbarSection,
24
+ messageText,
25
+ type: messageText === 'success' ? 'success' : 'error',
26
+ }),
27
+ ];
11
28
  const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/invoices/${encodeURIComponent(invoiceId)}/${kind}`;
12
29
  return zeniAPI
13
30
  .postAndGetJSON(apiUrl, (body ?? {}))
@@ -17,17 +34,24 @@ export const runInvoicingInvoiceActionEpic = (actions$, _state$, zeniAPI) => act
17
34
  updateInvoices([response.data], 'merge'),
18
35
  updateAddresses(extractInvoicingInvoiceAddresses([response.data])),
19
36
  runInvoicingInvoiceActionSuccess({ invoiceId }),
37
+ ...snackbar('success'),
20
38
  ];
21
39
  }
22
- return of(runInvoicingInvoiceActionFailure({
23
- error: response.status,
40
+ return from([
41
+ runInvoicingInvoiceActionFailure({
42
+ error: response.status,
43
+ invoiceId,
44
+ }),
45
+ ...snackbar('failed'),
46
+ ]);
47
+ }), catchError((error) => from([
48
+ runInvoicingInvoiceActionFailure({
49
+ error: createZeniAPIStatus('Unexpected Error', 'Invoice action REST API call errored out ' +
50
+ JSON.stringify(error)),
24
51
  invoiceId,
25
- }));
26
- }), catchError((error) => of(runInvoicingInvoiceActionFailure({
27
- error: createZeniAPIStatus('Unexpected Error', 'Invoice action REST API call errored out ' +
28
- JSON.stringify(error)),
29
- invoiceId,
30
- }))));
52
+ }),
53
+ ...snackbar('failed'),
54
+ ])));
31
55
  }));
32
56
  export const updateInvoicingInvoiceEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(updateInvoicingInvoice.match), mergeMap((action) => {
33
57
  const { data, invoiceId } = action.payload;
@@ -40,15 +64,34 @@ export const updateInvoicingInvoiceEpic = (actions$, _state$, zeniAPI) => action
40
64
  updateInvoices([response.data], 'merge'),
41
65
  updateAddresses(extractInvoicingInvoiceAddresses([response.data])),
42
66
  runInvoicingInvoiceActionSuccess({ invoiceId }),
67
+ openSnackbar({
68
+ messageSection: 'invoicing_invoice_saved',
69
+ messageText: 'success',
70
+ type: 'success',
71
+ }),
43
72
  ];
44
73
  }
45
- return of(runInvoicingInvoiceActionFailure({
46
- error: response.status,
74
+ return from([
75
+ runInvoicingInvoiceActionFailure({
76
+ error: response.status,
77
+ invoiceId,
78
+ }),
79
+ openSnackbar({
80
+ messageSection: 'invoicing_invoice_saved',
81
+ messageText: 'failed',
82
+ type: 'error',
83
+ }),
84
+ ]);
85
+ }), catchError((error) => from([
86
+ runInvoicingInvoiceActionFailure({
87
+ error: createZeniAPIStatus('Unexpected Error', 'Invoice update REST API call errored out ' +
88
+ JSON.stringify(error)),
47
89
  invoiceId,
48
- }));
49
- }), catchError((error) => of(runInvoicingInvoiceActionFailure({
50
- error: createZeniAPIStatus('Unexpected Error', 'Invoice update REST API call errored out ' +
51
- JSON.stringify(error)),
52
- invoiceId,
53
- }))));
90
+ }),
91
+ openSnackbar({
92
+ messageSection: 'invoicing_invoice_saved',
93
+ messageText: 'failed',
94
+ type: 'error',
95
+ }),
96
+ ])));
54
97
  }));
@@ -7,7 +7,20 @@ export const initialState = {
7
7
  auditLogEntityRouteOptions: [],
8
8
  auditLogListSortFieldOptions: [],
9
9
  auditLogSourceLabels: [],
10
+ autoCollectionOptions: [],
11
+ billingPeriodUnitOptions: [],
12
+ catalogTrialEndActionOptions: [],
13
+ catalogTrialUnitOptions: [],
14
+ customerTypeOptions: [],
15
+ dateFormatOptions: [],
16
+ discountApplyOnOptions: [],
17
+ discountDurationTypeOptions: [],
18
+ discountTypeOptions: [],
10
19
  dunningScheduleActionOptions: [],
20
+ manualPaymentMethodOptions: [],
21
+ netTermDayOptions: [],
22
+ paymentGatewayOptions: [],
23
+ taxabilityOptions: [],
11
24
  enableFetchState: { fetchState: 'Not-Started', error: undefined },
12
25
  subscriptionLifecycleActionOptions: [],
13
26
  subscriptionCommercialEditAllowedFromStatuses: [],
@@ -28,6 +41,8 @@ export const initialState = {
28
41
  invoicingTosAcceptanceUserAgent: undefined,
29
42
  invoicingTosAcceptanceUserId: undefined,
30
43
  isInvoicingSettingsSeeded: undefined,
44
+ createdAt: undefined,
45
+ updatedAt: undefined,
31
46
  hasValidState() {
32
47
  return (this.fetchState === 'Completed' && this.isInvoicingTosAccepted != null);
33
48
  },
@@ -43,6 +58,21 @@ export function mapInvoicingConfigPayloadToState(payload) {
43
58
  code: option.code,
44
59
  name: option.name,
45
60
  })),
61
+ autoCollectionOptions: (payload.auto_collection_options ?? []).map(toInvoicingNamedOption),
62
+ billingPeriodUnitOptions: (payload.billing_period_unit_options ?? []).map(toInvoicingNamedOption),
63
+ catalogTrialEndActionOptions: (payload.catalog_trial_end_action_options ?? []).map(toInvoicingNamedOption),
64
+ catalogTrialUnitOptions: (payload.catalog_trial_unit_options ?? []).map(toInvoicingNamedOption),
65
+ customerTypeOptions: (payload.customer_type_options ?? []).map(toInvoicingNamedOption),
66
+ dateFormatOptions: (payload.date_format_options ?? []).map(toInvoicingNamedOption),
67
+ discountApplyOnOptions: (payload.discount_apply_on_options ?? []).map(toInvoicingNamedOption),
68
+ discountDurationTypeOptions: (payload.discount_duration_type_options ?? []).map(toInvoicingNamedOption),
69
+ discountTypeOptions: (payload.discount_type_options ?? []).map(toInvoicingNamedOption),
70
+ manualPaymentMethodOptions: (payload.manual_payment_method_options ?? []).map(toInvoicingNamedOption),
71
+ netTermDayOptions: (payload.net_term_day_options ?? []).map(toInvoicingNamedOption),
72
+ paymentGatewayOptions: (payload.payment_gateway_options ?? []).map(toInvoicingNamedOption),
73
+ taxabilityOptions: (payload.taxability_options ?? []).map(toInvoicingNamedOption),
74
+ createdAt: payload.created_at ?? undefined,
75
+ updatedAt: payload.updated_at ?? undefined,
46
76
  dunningScheduleActionOptions: payload.dunning_schedule_action_options.map((option) => ({
47
77
  code: toInvoicingDunningAction(option.code),
48
78
  name: option.name,
@@ -1,35 +1,10 @@
1
1
  import { deriveInvoicingOnboardingStatus, } from './invoicingConfigViewPayload';
2
- /**
3
- * Single bundled view for the invoicing config — consumers read only this.
4
- * Carries the config flags, the derived onboarding `status` used for route
5
- * gating, the config fetch (`fetchState`/`error`), and the accept-terms /
6
- * enable action fetch states.
7
- */
8
2
  export const getInvoicingConfigView = (state) => {
9
- const configState = state.invoicingConfigViewState;
3
+ const { acceptTermsFetchState, enableFetchState, ...configState } = state.invoicingConfigViewState;
10
4
  return {
11
- acceptTermsState: configState.acceptTermsFetchState,
12
- auditLogEntityRouteOptions: configState.auditLogEntityRouteOptions,
13
- auditLogListSortFieldOptions: configState.auditLogListSortFieldOptions,
14
- auditLogSourceLabels: configState.auditLogSourceLabels,
15
- dunningScheduleActionOptions: configState.dunningScheduleActionOptions,
16
- enableState: configState.enableFetchState,
17
- subscriptionLifecycleActionOptions: configState.subscriptionLifecycleActionOptions,
18
- subscriptionCommercialEditAllowedFromStatuses: configState.subscriptionCommercialEditAllowedFromStatuses,
19
- subscriptionMetadataEditAllowedFromStatuses: configState.subscriptionMetadataEditAllowedFromStatuses,
20
- subscriptionEditFieldOptions: configState.subscriptionEditFieldOptions,
21
- subscriptionCancelTimingOptions: configState.subscriptionCancelTimingOptions,
22
- subscriptionChangeOptionOptions: configState.subscriptionChangeOptionOptions,
23
- catalogItemLifecycleActionOptions: configState.catalogItemLifecycleActionOptions,
24
- catalogProductLifecycleActionOptions: configState.catalogProductLifecycleActionOptions,
25
- discountLifecycleActionOptions: configState.discountLifecycleActionOptions,
26
- qboAccountMappingKindOptions: configState.qboAccountMappingKindOptions,
27
- qboCustomerMatchingOptions: configState.qboCustomerMatchingOptions,
28
- qboSyncStatusOptions: configState.qboSyncStatusOptions,
29
- error: configState.error,
30
- fetchState: configState.fetchState,
31
- isInvoicingEnabled: configState.isInvoicingEnabled,
32
- isInvoicingTosAccepted: configState.isInvoicingTosAccepted,
5
+ ...configState,
6
+ acceptTermsState: acceptTermsFetchState,
7
+ enableState: enableFetchState,
33
8
  status: deriveInvoicingOnboardingStatus(configState.isInvoicingTosAccepted, configState.isInvoicingEnabled),
34
9
  };
35
10
  };
@@ -1,5 +1,6 @@
1
- import { of } from 'rxjs';
1
+ import { from, of } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
+ import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
3
4
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
4
5
  import { fetchInvoicingQboSyncHealth, retryInvoicingQboSyncTask, retryInvoicingQboSyncTaskFailure, retryInvoicingQboSyncTaskSuccess, } from '../invoicingQboViewReducer';
5
6
  export const retryInvoicingQboSyncTaskEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(retryInvoicingQboSyncTask.match), mergeMap((action) => {
@@ -12,15 +13,33 @@ export const retryInvoicingQboSyncTaskEpic = (actions$, _state$, zeniAPI) => act
12
13
  return of(retryInvoicingQboSyncTaskSuccess({
13
14
  status: response.data?.status ?? '',
14
15
  taskId: response.data?.task_id ?? taskId,
15
- }), fetchInvoicingQboSyncHealth());
16
+ }), fetchInvoicingQboSyncHealth(), openSnackbar({
17
+ messageSection: 'invoicing_qbo_sync_health_retry',
18
+ messageText: 'success',
19
+ type: 'success',
20
+ }));
16
21
  }
17
- return of(retryInvoicingQboSyncTaskFailure({
18
- status: response.status,
22
+ return from([
23
+ retryInvoicingQboSyncTaskFailure({
24
+ status: response.status,
25
+ taskId,
26
+ }),
27
+ openSnackbar({
28
+ messageSection: 'invoicing_qbo_sync_health_retry',
29
+ messageText: 'failed',
30
+ type: 'error',
31
+ }),
32
+ ]);
33
+ }), catchError((error) => from([
34
+ retryInvoicingQboSyncTaskFailure({
35
+ status: createZeniAPIStatus('Unexpected Error', 'Retry QBO sync task REST API call errored out ' +
36
+ JSON.stringify(error)),
19
37
  taskId,
20
- }));
21
- }), catchError((error) => of(retryInvoicingQboSyncTaskFailure({
22
- status: createZeniAPIStatus('Unexpected Error', 'Retry QBO sync task REST API call errored out ' +
23
- JSON.stringify(error)),
24
- taskId,
25
- }))));
38
+ }),
39
+ openSnackbar({
40
+ messageSection: 'invoicing_qbo_sync_health_retry',
41
+ messageText: 'failed',
42
+ type: 'error',
43
+ }),
44
+ ])));
26
45
  }));
@@ -1,5 +1,6 @@
1
- import { of } from 'rxjs';
1
+ import { from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
+ import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
3
4
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
4
5
  import { fetchInvoicingQboSyncHealth, startInvoicingQboBackfill, startInvoicingQboBackfillFailure, startInvoicingQboBackfillSuccess, } from '../invoicingQboViewReducer';
5
6
  export const startInvoicingQboBackfillEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(startInvoicingQboBackfill.match), switchMap((action) => {
@@ -10,9 +11,31 @@ export const startInvoicingQboBackfillEpic = (actions$, _state$, zeniAPI) => act
10
11
  })
11
12
  .pipe(mergeMap((response) => {
12
13
  if (isSuccessResponse(response)) {
13
- return of(startInvoicingQboBackfillSuccess(), fetchInvoicingQboSyncHealth());
14
+ return from([
15
+ startInvoicingQboBackfillSuccess(),
16
+ fetchInvoicingQboSyncHealth(),
17
+ openSnackbar({
18
+ messageSection: 'invoicing_qbo_backfill',
19
+ messageText: 'success',
20
+ type: 'success',
21
+ }),
22
+ ]);
14
23
  }
15
- return of(startInvoicingQboBackfillFailure(response.status));
16
- }), catchError((error) => of(startInvoicingQboBackfillFailure(createZeniAPIStatus('Unexpected Error', 'Start QBO backfill REST API call errored out ' +
17
- JSON.stringify(error))))));
24
+ return from([
25
+ startInvoicingQboBackfillFailure(response.status),
26
+ openSnackbar({
27
+ messageSection: 'invoicing_qbo_backfill',
28
+ messageText: 'failed',
29
+ type: 'error',
30
+ }),
31
+ ]);
32
+ }), catchError((error) => from([
33
+ startInvoicingQboBackfillFailure(createZeniAPIStatus('Unexpected Error', 'Start QBO backfill REST API call errored out ' +
34
+ JSON.stringify(error))),
35
+ openSnackbar({
36
+ messageSection: 'invoicing_qbo_backfill',
37
+ messageText: 'failed',
38
+ type: 'error',
39
+ }),
40
+ ])));
18
41
  }));
@@ -1,6 +1,7 @@
1
- import { of } from 'rxjs';
1
+ import { from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateInvoicingCreditNotes } from '../../../entity/invoicing/creditNote/creditNoteReducer';
4
+ import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
4
5
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
5
6
  import { fetchInvoiceDetail } from '../invoiceDetail/invoiceDetailReducer';
6
7
  import { localDataToIssueCreditNotePayload, } from './issueCreditNotePayload';
@@ -18,9 +19,28 @@ export const issueCreditNoteEpic = (actions$, state$, zeniAPI) => actions$.pipe(
18
19
  updateInvoicingCreditNotes([response.data], 'merge'),
19
20
  fetchInvoiceDetail({ invoiceId }),
20
21
  issueCreditNoteSuccess({ creditNoteId: response.data.id }),
22
+ openSnackbar({
23
+ messageSection: 'invoicing_credit_note_saved',
24
+ messageText: 'success',
25
+ type: 'success',
26
+ }),
21
27
  ];
22
28
  }
23
- return of(issueCreditNoteFailure(response.status));
24
- }), catchError((error) => of(issueCreditNoteFailure(createZeniAPIStatus('Unexpected Error', 'Issue credit note REST API call errored out ' +
25
- JSON.stringify(error))))));
29
+ return from([
30
+ issueCreditNoteFailure(response.status),
31
+ openSnackbar({
32
+ messageSection: 'invoicing_credit_note_saved',
33
+ messageText: 'failed',
34
+ type: 'error',
35
+ }),
36
+ ]);
37
+ }), catchError((error) => from([
38
+ issueCreditNoteFailure(createZeniAPIStatus('Unexpected Error', 'Issue credit note REST API call errored out ' +
39
+ JSON.stringify(error))),
40
+ openSnackbar({
41
+ messageSection: 'invoicing_credit_note_saved',
42
+ messageText: 'failed',
43
+ type: 'error',
44
+ }),
45
+ ])));
26
46
  }));
@@ -1,20 +1,28 @@
1
- import { INVOICING_CREDIT_NOTE_REASON_CODES } from './issueCreditNoteFormConfig';
2
- export const getIssueCreditNoteView = (state) => {
3
- const { error, fetchState, issuedCreditNoteId } = state.invoicingIssueCreditNoteState;
1
+ import { getInvoiceDetail } from '../invoiceDetail/invoiceDetailSelector';
2
+ import { INVOICING_CREDIT_NOTE_REASON_CODES, initialIssueCreditNoteFormLocalData, } from './issueCreditNoteFormConfig';
3
+ /**
4
+ * The draft is a single global slice, so a draft seeded for a previously opened
5
+ * invoice can still be in state when another invoice's screen mounts. Anything
6
+ * that would render that stale draft next to this invoice is dropped until the
7
+ * screen re-seeds it for `invoiceId`.
8
+ */
9
+ export const getIssueCreditNoteView = (state, invoiceId) => {
10
+ const invoiceView = getInvoiceDetail(state, invoiceId);
11
+ const { draft, error, fetchState, issuedCreditNoteId } = state.invoicingIssueCreditNoteState;
12
+ const isDraftForInvoice = draft.invoiceId === invoiceId;
4
13
  return {
5
- error,
6
- fetchState,
14
+ invoice: invoiceView.invoice,
15
+ isPageReady: invoiceView.fetchState === 'Completed' &&
16
+ isDraftForInvoice &&
17
+ draft.lineItems.length > 0,
7
18
  issuedCreditNoteId,
8
- version: 1.0,
9
- };
10
- };
11
- export const getIssueCreditNoteFormView = (state) => {
12
- const { draft, error, fetchState } = state.invoicingIssueCreditNoteState;
13
- return {
14
- localData: draft,
19
+ localData: isDraftForInvoice
20
+ ? draft
21
+ : initialIssueCreditNoteFormLocalData(invoiceId),
15
22
  reasonCodeValues: [...INVOICING_CREDIT_NOTE_REASON_CODES],
16
- error,
17
- fetchState,
23
+ submitState: { error, fetchState },
24
+ error: invoiceView.error,
25
+ fetchState: invoiceView.fetchState,
18
26
  version: 1.0,
19
27
  };
20
28
  };
@@ -1,7 +1,8 @@
1
- import { of } from 'rxjs';
1
+ import { from } from 'rxjs';
2
2
  import { catchError, filter, mergeMap } from 'rxjs/operators';
3
3
  import { updateInvoices } from '../../../entity/invoicing/invoice/invoiceReducer';
4
4
  import { updateInvoicingTransactions } from '../../../entity/invoicing/invoicingTransaction/invoicingTransactionReducer';
5
+ import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
5
6
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
6
7
  import { fetchInvoiceDetail } from '../invoiceDetail/invoiceDetailReducer';
7
8
  import { addInvoicingTransactionToList } from '../invoicingTransactionListView/invoicingTransactionListViewReducer';
@@ -26,11 +27,30 @@ export const recordPaymentEpic = (actions$, state$, zeniAPI) => actions$.pipe(fi
26
27
  updateInvoices([response.data], 'merge'),
27
28
  fetchInvoiceDetail({ invoiceId }),
28
29
  recordPaymentSuccess({ invoiceId }),
30
+ openSnackbar({
31
+ messageSection: 'invoicing_payment_recorded',
32
+ messageText: 'success',
33
+ type: 'success',
34
+ }),
29
35
  ];
30
36
  }
31
- return of(recordPaymentFailure(response.status));
32
- }), catchError((error) => of(recordPaymentFailure(createZeniAPIStatus('Unexpected Error', 'Record payment REST API call errored out ' +
33
- JSON.stringify(error))))));
37
+ return from([
38
+ recordPaymentFailure(response.status),
39
+ openSnackbar({
40
+ messageSection: 'invoicing_payment_recorded',
41
+ messageText: 'failed',
42
+ type: 'error',
43
+ }),
44
+ ]);
45
+ }), catchError((error) => from([
46
+ recordPaymentFailure(createZeniAPIStatus('Unexpected Error', 'Record payment REST API call errored out ' +
47
+ JSON.stringify(error))),
48
+ openSnackbar({
49
+ messageSection: 'invoicing_payment_recorded',
50
+ messageText: 'failed',
51
+ type: 'error',
52
+ }),
53
+ ])));
34
54
  }
35
55
  const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/payments`;
36
56
  const data = localDataToRecordPaymentPayload(draft);
@@ -49,9 +69,28 @@ export const recordPaymentEpic = (actions$, state$, zeniAPI) => actions$.pipe(fi
49
69
  if (linkedInvoiceId != null) {
50
70
  successActions.push(fetchInvoiceDetail({ invoiceId: linkedInvoiceId }));
51
71
  }
72
+ successActions.push(openSnackbar({
73
+ messageSection: 'invoicing_payment_recorded',
74
+ messageText: 'success',
75
+ type: 'success',
76
+ }));
52
77
  return successActions;
53
78
  }
54
- return of(recordPaymentFailure(response.status));
55
- }), catchError((error) => of(recordPaymentFailure(createZeniAPIStatus('Unexpected Error', 'Record payment REST API call errored out ' +
56
- JSON.stringify(error))))));
79
+ return from([
80
+ recordPaymentFailure(response.status),
81
+ openSnackbar({
82
+ messageSection: 'invoicing_payment_recorded',
83
+ messageText: 'failed',
84
+ type: 'error',
85
+ }),
86
+ ]);
87
+ }), catchError((error) => from([
88
+ recordPaymentFailure(createZeniAPIStatus('Unexpected Error', 'Record payment REST API call errored out ' +
89
+ JSON.stringify(error))),
90
+ openSnackbar({
91
+ messageSection: 'invoicing_payment_recorded',
92
+ messageText: 'failed',
93
+ type: 'error',
94
+ }),
95
+ ])));
57
96
  }));
@@ -1,6 +1,7 @@
1
- import { of } from 'rxjs';
1
+ import { from, of } from 'rxjs';
2
2
  import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
3
  import { updateAddresses } from '../../../entity/address/addressReducer';
4
+ import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
4
5
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
5
6
  import { connectInvoicingStripe, disconnectInvoicingStripe, fetchInvoicingSettings, invoicingStripeConnectionFailure, saveInvoicingSettings, saveInvoicingSettingsFailure, saveInvoicingSettingsSuccess, updateInvoicingSettings, updateInvoicingSettingsFailure, updateInvoicingStripeConnection, } from './settingsViewReducer';
6
7
  export const fetchInvoicingSettingsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingSettings.match), switchMap(() => {
@@ -57,15 +58,32 @@ export const disconnectInvoicingStripeEpic = (actions$, _state$, zeniAPI) => act
57
58
  }));
58
59
  export const saveInvoicingSettingsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(saveInvoicingSettings.match), mergeMap((action) => {
59
60
  const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/settings`;
61
+ const { settings, snackbarSection } = action.payload;
62
+ const snackbar = (messageText) => snackbarSection == null
63
+ ? []
64
+ : [
65
+ openSnackbar({
66
+ messageSection: snackbarSection,
67
+ messageText,
68
+ type: messageText === 'success' ? 'success' : 'error',
69
+ }),
70
+ ];
60
71
  return zeniAPI
61
- .putAndGetJSON(apiUrl, {
62
- ...action.payload,
63
- })
72
+ .putAndGetJSON(apiUrl, { ...settings })
64
73
  .pipe(switchMap((response) => {
65
74
  if (isSuccessResponse(response) && response.data != null) {
66
- return of(saveInvoicingSettingsSuccess(response.data));
75
+ return from([
76
+ saveInvoicingSettingsSuccess(response.data),
77
+ ...snackbar('success'),
78
+ ]);
67
79
  }
68
- return of(saveInvoicingSettingsFailure(response.status));
69
- }), catchError((error) => of(saveInvoicingSettingsFailure(createZeniAPIStatus('Unexpected Error', 'Save settings REST API call errored out ' +
70
- JSON.stringify(error))))));
80
+ return from([
81
+ saveInvoicingSettingsFailure(response.status),
82
+ ...snackbar('failed'),
83
+ ]);
84
+ }), catchError((error) => from([
85
+ saveInvoicingSettingsFailure(createZeniAPIStatus('Unexpected Error', 'Save settings REST API call errored out ' +
86
+ JSON.stringify(error))),
87
+ ...snackbar('failed'),
88
+ ])));
71
89
  }));
@@ -29,7 +29,7 @@ const settingsView = createSlice({
29
29
  saveInvoicingSettings(draft, action) {
30
30
  draft.saveFetchState = { fetchState: 'In-Progress', error: undefined };
31
31
  if (draft.settings != null) {
32
- draft.settings = { ...draft.settings, ...action.payload };
32
+ draft.settings = { ...draft.settings, ...action.payload.settings };
33
33
  }
34
34
  },
35
35
  saveInvoicingSettingsSuccess(draft, action) {
@@ -1,4 +1,5 @@
1
1
  import { getFormattedAddress } from '../../addressView/addressViewSelector';
2
+ import { getInvoicingConfigView } from '../invoicingConfigView/invoicingConfigViewSelector';
2
3
  import { INVOICING_BUSINESS_ADDRESS_TYPE, entityToInvoicingBrandingFormLocalData, } from './invoicingBrandingFormConfig';
3
4
  /**
4
5
  * Shared primitive: the raw invoicing settings. Consumed internally by other
@@ -33,7 +34,8 @@ export const getInvoicingSettingsView = (state) => {
33
34
  * Bundled view for the branding settings form: the draft `localData` (seeded
34
35
  * from the existing settings until the user edits), the business-address label
35
36
  * (from the shared Address screen, seeded on load from `address_id`), the raw
36
- * settings (for the logo/preview) and the save fetch state.
37
+ * settings (for the logo/preview), the backend date-format choices and the
38
+ * save fetch state.
37
39
  */
38
40
  export const getInvoicingBrandingFormView = (state) => {
39
41
  const { brandingDraft, saveFetchState, settings } = state.invoicingSettingsViewState;
@@ -43,6 +45,7 @@ export const getInvoicingBrandingFormView = (state) => {
43
45
  : undefined;
44
46
  return {
45
47
  addressLabel,
48
+ dateFormatOptions: getInvoicingConfigView(state).dateFormatOptions,
46
49
  localData: brandingDraft ?? entityToInvoicingBrandingFormLocalData(settings),
47
50
  saveState: saveFetchState,
48
51
  settings,