@zeniai/client-epic-state 5.2.48 → 5.2.49-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 (53) hide show
  1. package/lib/esm/view/expenseAutomationView/reducers/reconciliationViewReducer.js +80 -53
  2. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.js +19 -11
  3. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +4 -1
  4. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +3 -2
  5. package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.js +18 -9
  6. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.js +19 -11
  7. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.js +3 -1
  8. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +1 -1
  9. package/lib/esm/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.js +16 -7
  10. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.js +19 -11
  11. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +4 -2
  12. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +1 -1
  13. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +21 -13
  14. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +3 -2
  15. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +4 -1
  16. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +1 -1
  17. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.js +18 -9
  18. package/lib/esm/view/invoicing/invoicingFormDraft.js +56 -0
  19. package/lib/view/expenseAutomationView/reducers/reconciliationViewReducer.js +80 -53
  20. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.d.ts +3 -1
  21. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.js +19 -11
  22. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.d.ts +10 -0
  23. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +4 -1
  24. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewState.d.ts +2 -1
  25. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +3 -2
  26. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.d.ts +10 -6
  27. package/lib/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.js +17 -8
  28. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.d.ts +3 -1
  29. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.js +19 -11
  30. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.d.ts +13 -0
  31. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.js +3 -1
  32. package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewState.d.ts +2 -1
  33. package/lib/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +1 -1
  34. package/lib/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.d.ts +10 -6
  35. package/lib/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.js +15 -6
  36. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.d.ts +3 -1
  37. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.js +19 -11
  38. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.d.ts +9 -0
  39. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +4 -2
  40. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewState.d.ts +2 -1
  41. package/lib/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +1 -1
  42. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.d.ts +3 -1
  43. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +21 -13
  44. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.d.ts +9 -0
  45. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +3 -2
  46. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewState.d.ts +2 -1
  47. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +4 -1
  48. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +1 -1
  49. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.d.ts +10 -6
  50. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.js +17 -8
  51. package/lib/view/invoicing/invoicingFormDraft.d.ts +50 -0
  52. package/lib/view/invoicing/invoicingFormDraft.js +63 -0
  53. package/package.json +1 -1
@@ -7,15 +7,24 @@ const planSelector_1 = require("../../../entity/invoicing/plan/planSelector");
7
7
  const editInvoicingCatalogItemDetailViewReducer_1 = require("./editInvoicingCatalogItemDetailViewReducer");
8
8
  const invoicingCatalogItemFormHelper_1 = require("./invoicingCatalogItemFormHelper");
9
9
  /**
10
- * Seeds the edit draft from the record the moment the detail fetch lands, so
11
- * the form has its values before it mounts. The reducer ignores the seed when a
12
- * draft already exists, which is what stops a background refetch from
13
- * overwriting edits in progress.
10
+ * Seeds the edit draft from the record, so the form has its values before it
11
+ * mounts. The reducer ignores a seed once the draft holds the record, which is
12
+ * what stops a background refetch from overwriting edits in progress.
13
+ *
14
+ * Opening the page counts as a trigger, not just the detail response: the page
15
+ * fetch is cache-guarded, so re-opening a form for a record already loaded this
16
+ * session issues no request at all — and a draft cleared in between (by a save
17
+ * or a cancel) would have nothing left to seed it.
14
18
  */
15
- const seedInvoicingCatalogItemFormDraftEpic = (actions$, state$) => actions$.pipe((0, operators_1.filter)(editInvoicingCatalogItemDetailViewReducer_1.updateInvoicingCatalogItemDetail.match), (0, operators_1.withLatestFrom)(state$), (0, operators_1.mergeMap)(([action, state]) => {
16
- const { id } = action.payload;
17
- const plan = (0, planSelector_1.getInvoicingPlanById)(state.invoicingPlanState, id);
18
- if (plan == null) {
19
+ const seedInvoicingCatalogItemFormDraftEpic = (actions$, state$) => actions$.pipe((0, operators_1.filter)((action) => editInvoicingCatalogItemDetailViewReducer_1.updateInvoicingCatalogItemDetail.match(action) ||
20
+ editInvoicingCatalogItemDetailViewReducer_1.fetchInvoicingCatalogItemDetailPage.match(action)), (0, operators_1.withLatestFrom)(state$), (0, operators_1.mergeMap)(([action, state]) => {
21
+ const id = action.payload.id;
22
+ // Nothing to seed from on a cold open; the detail response triggers this
23
+ // again once it lands.
24
+ const plan = id != null
25
+ ? (0, planSelector_1.getInvoicingPlanById)(state.invoicingPlanState, id)
26
+ : undefined;
27
+ if (id == null || plan == null) {
19
28
  return rxjs_1.EMPTY;
20
29
  }
21
30
  return (0, rxjs_1.of)((0, editInvoicingCatalogItemDetailViewReducer_1.seedInvoicingCatalogItemFormDraft)({
@@ -48,6 +48,8 @@ export declare const updateInvoicingCouponFormDraft: import("@reduxjs/toolkit").
48
48
  id: ID;
49
49
  }, "editInvoicingCouponDetailView/runInvoicingDiscountActionFailure">, submitInvoicingCouponForm: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: SubmitInvoicingCouponFormPayload], SubmitInvoicingCouponFormPayload, "editInvoicingCouponDetailView/submitInvoicingCouponForm", never, never>, saveInvoicingCoupon: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: SaveInvoicingCouponPayload], SaveInvoicingCouponPayload, "editInvoicingCouponDetailView/saveInvoicingCoupon", never, never>, submitInvoicingCouponFormSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
50
50
  couponId: ID;
51
- }, "editInvoicingCouponDetailView/submitInvoicingCouponFormSuccess">, submitInvoicingCouponFormFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "editInvoicingCouponDetailView/submitInvoicingCouponFormFailure">, resetEditInvoicingCouponDetailView: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"editInvoicingCouponDetailView/resetEditInvoicingCouponDetailView">;
51
+ }, "editInvoicingCouponDetailView/submitInvoicingCouponFormSuccess">, submitInvoicingCouponFormFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "editInvoicingCouponDetailView/submitInvoicingCouponFormFailure">, resetEditInvoicingCouponDetailView: import("@reduxjs/toolkit").ActionCreatorWithOptionalPayload<{
52
+ id?: ID;
53
+ } | undefined, "editInvoicingCouponDetailView/resetEditInvoicingCouponDetailView">;
52
54
  declare const _default: import("redux").Reducer<EditInvoicingCouponDetailViewState>;
53
55
  export default _default;
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.resetEditInvoicingCouponDetailView = exports.submitInvoicingCouponFormFailure = exports.submitInvoicingCouponFormSuccess = exports.saveInvoicingCoupon = exports.submitInvoicingCouponForm = exports.runInvoicingDiscountActionFailure = exports.runInvoicingDiscountActionSuccess = exports.runInvoicingDiscountAction = exports.updateInvoicingCouponDetailFailure = exports.updateInvoicingCouponDetail = exports.seedInvoicingCouponFormDraft = exports.fetchInvoicingCouponDetailPage = exports.fetchInvoicingCouponDetail = exports.updateInvoicingCouponFormDraft = exports.initialState = void 0;
5
5
  const toolkit_1 = require("@reduxjs/toolkit");
6
6
  const rootActions_1 = require("../../../rootActions");
7
+ const invoicingFormDraft_1 = require("../invoicingFormDraft");
7
8
  const editInvoicingCouponDetailViewState_1 = require("./editInvoicingCouponDetailViewState");
8
9
  exports.initialState = {
9
10
  fetchState: 'Not-Started',
@@ -24,7 +25,7 @@ const editInvoicingCouponDetailView = (0, toolkit_1.createSlice)({
24
25
  updateInvoicingCouponFormDraft(draft, action) {
25
26
  const { id, localData } = action.payload;
26
27
  if (id != null) {
27
- draft.editDraftById[id] = localData;
28
+ (0, invoicingFormDraft_1.applyInvoicingFormDraftEdit)(draft.editDraftById, id, localData);
28
29
  }
29
30
  else {
30
31
  draft.newCouponDraft = localData;
@@ -89,15 +90,12 @@ const editInvoicingCouponDetailView = (0, toolkit_1.createSlice)({
89
90
  fetchState: 'Error',
90
91
  error: action.payload.status,
91
92
  };
93
+ (0, invoicingFormDraft_1.applyInvoicingFormDraftFailure)(draft.editDraftById, action.payload.id, editInvoicingCouponDetailViewState_1.initialInvoicingCouponFormLocalData);
92
94
  },
93
- /**
94
- * Seed the draft from the loaded record, once. Skipped when a draft already
95
- * exists so a late refetch cannot overwrite in-progress edits.
96
- */
95
+ /** Fill the draft from the loaded record — see `applyInvoicingFormDraftSeed`. */
97
96
  seedInvoicingCouponFormDraft(draft, action) {
98
- if (draft.editDraftById[action.payload.id] == null) {
99
- draft.editDraftById[action.payload.id] = action.payload.localData;
100
- }
97
+ const { id, localData } = action.payload;
98
+ (0, invoicingFormDraft_1.applyInvoicingFormDraftSeed)(draft.editDraftById, id, localData);
101
99
  },
102
100
  runInvoicingDiscountAction(draft, action) {
103
101
  draft.actionStateById[action.payload.id] = {
@@ -117,12 +115,22 @@ const editInvoicingCouponDetailView = (0, toolkit_1.createSlice)({
117
115
  error: action.payload.error,
118
116
  };
119
117
  },
120
- resetEditInvoicingCouponDetailView(draft) {
118
+ /**
119
+ * Discard a form's draft — the coupon's own when `id` is given,
120
+ * the create draft otherwise. Scoped to the one form on purpose: another
121
+ * coupon's half-finished edit is not this form's to throw away.
122
+ * Clearing every draft at once is what `clearAll` is for.
123
+ */
124
+ resetEditInvoicingCouponDetailView(draft, action) {
121
125
  draft.fetchState = 'Not-Started';
122
126
  draft.error = undefined;
123
127
  draft.savedCouponId = undefined;
124
- draft.editDraftById = {};
125
- draft.newCouponDraft = { ...editInvoicingCouponDetailViewState_1.initialInvoicingCouponFormLocalData };
128
+ const id = action.payload?.id;
129
+ if (id == null) {
130
+ draft.newCouponDraft = { ...editInvoicingCouponDetailViewState_1.initialInvoicingCouponFormLocalData };
131
+ return;
132
+ }
133
+ delete draft.editDraftById[id];
126
134
  },
127
135
  },
128
136
  extraReducers: (builder) => {
@@ -19,6 +19,19 @@ export interface EditInvoicingCouponDetailView extends SelectorView {
19
19
  * Folded in here so the screen never reaches for the config view itself.
20
20
  */
21
21
  fieldHelp: InvoicingFieldHelpByCode;
22
+ /**
23
+ * True while the coupon being edited is still on its way to the form. The
24
+ * form holds a skeleton on this rather than mount: it seeds react-hook-form
25
+ * once, at first render, so fields mounted against the blank draft stay blank
26
+ * and the first change writes that blankness back over the coupon. A later
27
+ * refetch does not raise this again, so a refresh leaves the form on screen
28
+ * instead of blanking it mid-edit.
29
+ *
30
+ * Narrower than `pageFetchState`, which also waits on the accounting lists:
31
+ * the discounts-account picker carries its own loading state, so the form is
32
+ * useful before the lists land.
33
+ */
34
+ isFormLoading: boolean;
22
35
  /**
23
36
  * Every request the page renders from, reduced to one state: the record and
24
37
  * the config behind its dropdowns and field help. The page holds a skeleton
@@ -5,6 +5,7 @@ const reduceFetchState_1 = require("../../../commonStateTypes/reduceFetchState")
5
5
  const couponSelector_1 = require("../../../entity/invoicing/coupon/couponSelector");
6
6
  const invoicingCommonPayload_1 = require("../../../entity/invoicing/invoicingCommonPayload");
7
7
  const invoicingConfigViewSelector_1 = require("../invoicingConfigView/invoicingConfigViewSelector");
8
+ const invoicingFormDraft_1 = require("../invoicingFormDraft");
8
9
  const invoicingQboViewSelector_1 = require("../invoicingQboView/invoicingQboViewSelector");
9
10
  const settingsViewSelector_1 = require("../settingsView/settingsViewSelector");
10
11
  const editInvoicingCouponDetailViewState_1 = require("./editInvoicingCouponDetailViewState");
@@ -44,6 +45,7 @@ const getEditInvoicingCouponDetailViewState = (state, couponId) => {
44
45
  { error: undefined, fetchState: configFetchState },
45
46
  ]),
46
47
  recordState,
48
+ isFormLoading: (0, invoicingFormDraft_1.isInvoicingFormDraftPending)(state.editInvoicingCouponDetailViewState.editDraftById, couponId),
47
49
  coupon,
48
50
  discountsAccountName: (0, invoicingQboViewSelector_1.invoicingQboAccountByQboId)(state, coupon?.discountsAccountId)?.accountName,
49
51
  savedCouponId,
@@ -74,7 +76,7 @@ const getInvoicingCouponFormView = (state, couponId) => {
74
76
  // `seedInvoicingCouponFormDraftEpic` when the detail load lands. Until then
75
77
  // the page is showing its skeleton.
76
78
  const localData = couponId != null
77
- ? (editDraftById[couponId] ?? editInvoicingCouponDetailViewState_1.initialInvoicingCouponFormLocalData)
79
+ ? (0, invoicingFormDraft_1.getInvoicingFormDraftLocalData)(editDraftById, couponId, editInvoicingCouponDetailViewState_1.initialInvoicingCouponFormLocalData)
78
80
  : newCouponDraft;
79
81
  const { billingPeriodUnitOptions, discountApplyOnOptions, discountDurationTypeOptions, discountTypeOptions, qboAccountMappingKindOptions, } = (0, invoicingConfigViewSelector_1.getInvoicingConfigView)(state);
80
82
  const isQboConnected = (0, invoicingQboViewSelector_1.isInvoicingQboConnected)(state);
@@ -2,6 +2,7 @@ import { Amount } from '../../../commonStateTypes/amount';
2
2
  import { FetchStateAndError, FetchedState, ID } from '../../../commonStateTypes/common';
3
3
  import { InvoicingCoupon } from '../../../entity/invoicing/coupon/couponState';
4
4
  import { InvoicingQboAccountMappingsPayload } from '../../../entity/invoicing/invoicingCommonPayload';
5
+ import { InvoicingFormDraftById } from '../invoicingFormDraft';
5
6
  /**
6
7
  * Draft form state for the create/edit discount-coupon form. Held in CES so the
7
8
  * form flows its data from here (mirrors the customer form pattern). Dates are
@@ -37,7 +38,7 @@ export interface InvoicingCouponFormLocalData {
37
38
  export interface EditInvoicingCouponDetailViewState extends FetchedState {
38
39
  /** Lifecycle action (archive, activate, …) fetch state per coupon id. */
39
40
  actionStateById: Record<ID, FetchStateAndError>;
40
- editDraftById: Record<ID, InvoicingCouponFormLocalData>;
41
+ editDraftById: InvoicingFormDraftById<InvoicingCouponFormLocalData>;
41
42
  newCouponDraft: InvoicingCouponFormLocalData;
42
43
  /** Detail-load state per coupon id. */
43
44
  recordStateById: Record<ID, FetchStateAndError>;
@@ -23,7 +23,7 @@ const saveInvoicingCouponEpic = (actions$, state$, zeniAPI) => actions$.pipe((0,
23
23
  const state = state$.value;
24
24
  const viewState = state.editInvoicingCouponDetailViewState;
25
25
  const draft = (id != null
26
- ? viewState.editDraftById[id]
26
+ ? viewState.editDraftById[id]?.localData
27
27
  : viewState.newCouponDraft) ?? viewState.newCouponDraft;
28
28
  // The create default is resolved here as well as in the form view,
29
29
  // so a create that never touched the picker still persists the
@@ -1,12 +1,16 @@
1
1
  import { ActionsObservable, StateObservable } from 'redux-observable';
2
2
  import { Observable } from 'rxjs';
3
3
  import { RootState } from '../../../rootStateTypes';
4
- import { seedInvoicingCouponFormDraft, updateInvoicingCouponDetail } from './editInvoicingCouponDetailViewReducer';
5
- export type ActionType = ReturnType<typeof updateInvoicingCouponDetail> | ReturnType<typeof seedInvoicingCouponFormDraft>;
4
+ import { fetchInvoicingCouponDetailPage, seedInvoicingCouponFormDraft, updateInvoicingCouponDetail } from './editInvoicingCouponDetailViewReducer';
5
+ export type ActionType = ReturnType<typeof fetchInvoicingCouponDetailPage> | ReturnType<typeof updateInvoicingCouponDetail> | ReturnType<typeof seedInvoicingCouponFormDraft>;
6
6
  /**
7
- * Seeds the edit draft from the record the moment the detail fetch lands, so
8
- * the form has its values before it mounts. The reducer ignores the seed when a
9
- * draft already exists, which is what stops a background refetch from
10
- * overwriting edits in progress.
7
+ * Seeds the edit draft from the record, so the form has its values before it
8
+ * mounts. The reducer ignores a seed once the draft holds the record, which is
9
+ * what stops a background refetch from overwriting edits in progress.
10
+ *
11
+ * Opening the page counts as a trigger, not just the detail response: the page
12
+ * fetch is cache-guarded, so re-opening a form for a record already loaded this
13
+ * session issues no request at all — and a draft cleared in between (by a save
14
+ * or a cancel) would have nothing left to seed it.
11
15
  */
12
16
  export declare const seedInvoicingCouponFormDraftEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>) => Observable<ActionType>;
@@ -7,14 +7,23 @@ const couponSelector_1 = require("../../../entity/invoicing/coupon/couponSelecto
7
7
  const editInvoicingCouponDetailViewReducer_1 = require("./editInvoicingCouponDetailViewReducer");
8
8
  const editInvoicingCouponDetailViewState_1 = require("./editInvoicingCouponDetailViewState");
9
9
  /**
10
- * Seeds the edit draft from the record the moment the detail fetch lands, so
11
- * the form has its values before it mounts. The reducer ignores the seed when a
12
- * draft already exists, which is what stops a background refetch from
13
- * overwriting edits in progress.
10
+ * Seeds the edit draft from the record, so the form has its values before it
11
+ * mounts. The reducer ignores a seed once the draft holds the record, which is
12
+ * what stops a background refetch from overwriting edits in progress.
13
+ *
14
+ * Opening the page counts as a trigger, not just the detail response: the page
15
+ * fetch is cache-guarded, so re-opening a form for a record already loaded this
16
+ * session issues no request at all — and a draft cleared in between (by a save
17
+ * or a cancel) would have nothing left to seed it.
14
18
  */
15
- const seedInvoicingCouponFormDraftEpic = (actions$, state$) => actions$.pipe((0, operators_1.filter)(editInvoicingCouponDetailViewReducer_1.updateInvoicingCouponDetail.match), (0, operators_1.withLatestFrom)(state$), (0, operators_1.mergeMap)(([action, state]) => {
19
+ const seedInvoicingCouponFormDraftEpic = (actions$, state$) => actions$.pipe((0, operators_1.filter)((action) => editInvoicingCouponDetailViewReducer_1.updateInvoicingCouponDetail.match(action) ||
20
+ editInvoicingCouponDetailViewReducer_1.fetchInvoicingCouponDetailPage.match(action)), (0, operators_1.withLatestFrom)(state$), (0, operators_1.mergeMap)(([action, state]) => {
16
21
  const { id } = action.payload;
17
- const coupon = (0, couponSelector_1.getInvoicingCouponById)(state.invoicingCouponState, id);
22
+ // Nothing to seed from on a cold open; the detail response triggers this
23
+ // again once it lands.
24
+ const coupon = id != null
25
+ ? (0, couponSelector_1.getInvoicingCouponById)(state.invoicingCouponState, id)
26
+ : undefined;
18
27
  if (coupon == null) {
19
28
  return rxjs_1.EMPTY;
20
29
  }
@@ -54,6 +54,8 @@ export declare const fetchInvoicingPaymentLinkEmailPreview: import("@reduxjs/too
54
54
  status: ZeniAPIStatus;
55
55
  }, "editInvoicingCustomerDetailView/updateInvoicingCustomerDetailFailure">, updateInvoicingCustomerFormDraft: import("@reduxjs/toolkit").ActionCreatorWithPayload<UpdateInvoicingCustomerFormDraftPayload, "editInvoicingCustomerDetailView/updateInvoicingCustomerFormDraft">, submitInvoicingCustomerForm: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: SubmitInvoicingCustomerFormPayload], SubmitInvoicingCustomerFormPayload, "editInvoicingCustomerDetailView/submitInvoicingCustomerForm", never, never>, submitInvoicingCustomerFormSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
56
56
  customerId: ID;
57
- }, "editInvoicingCustomerDetailView/submitInvoicingCustomerFormSuccess">, submitInvoicingCustomerFormFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "editInvoicingCustomerDetailView/submitInvoicingCustomerFormFailure">, resetEditInvoicingCustomerDetailView: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"editInvoicingCustomerDetailView/resetEditInvoicingCustomerDetailView">, initializeInvoicingCustomerAddress: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: InitializeInvoicingCustomerAddressPayload], InitializeInvoicingCustomerAddressPayload, "editInvoicingCustomerDetailView/initializeInvoicingCustomerAddress", never, never>;
57
+ }, "editInvoicingCustomerDetailView/submitInvoicingCustomerFormSuccess">, submitInvoicingCustomerFormFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "editInvoicingCustomerDetailView/submitInvoicingCustomerFormFailure">, resetEditInvoicingCustomerDetailView: import("@reduxjs/toolkit").ActionCreatorWithOptionalPayload<{
58
+ id?: ID;
59
+ } | undefined, "editInvoicingCustomerDetailView/resetEditInvoicingCustomerDetailView">, initializeInvoicingCustomerAddress: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: InitializeInvoicingCustomerAddressPayload], InitializeInvoicingCustomerAddressPayload, "editInvoicingCustomerDetailView/initializeInvoicingCustomerAddress", never, never>;
58
60
  declare const _default: import("redux").Reducer<EditInvoicingCustomerDetailViewState>;
59
61
  export default _default;
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.initializeInvoicingCustomerAddress = exports.resetEditInvoicingCustomerDetailView = exports.submitInvoicingCustomerFormFailure = exports.submitInvoicingCustomerFormSuccess = exports.submitInvoicingCustomerForm = exports.updateInvoicingCustomerFormDraft = exports.updateInvoicingCustomerDetailFailure = exports.submitAddPromotionalCredits = exports.resetPromotionalCreditAction = exports.updateInvoicingCustomerDetail = exports.seedInvoicingCustomerFormDraft = exports.fetchInvoicingCustomerDetailPage = exports.fetchInvoicingCustomerDetail = exports.addPromotionalCreditsSuccess = exports.addPromotionalCreditsFailure = exports.updateInvoicingPaymentLinkEmailPreviewFailure = exports.updateInvoicingPaymentLinkEmailPreview = exports.fetchInvoicingPaymentLinkEmailPreviewPage = exports.fetchInvoicingPaymentLinkEmailPreview = exports.initialState = void 0;
5
5
  const toolkit_1 = require("@reduxjs/toolkit");
6
6
  const rootActions_1 = require("../../../rootActions");
7
+ const invoicingFormDraft_1 = require("../invoicingFormDraft");
7
8
  const invoicingCustomerFormConfig_1 = require("./invoicingCustomerFormConfig");
8
9
  exports.initialState = {
9
10
  emailPreviewFetchState: { fetchState: 'Not-Started', error: undefined },
@@ -127,20 +128,17 @@ const editInvoicingCustomerDetailView = (0, toolkit_1.createSlice)({
127
128
  fetchState: 'Error',
128
129
  error: action.payload.status,
129
130
  };
131
+ (0, invoicingFormDraft_1.applyInvoicingFormDraftFailure)(draft.editDraftById, action.payload.id, invoicingCustomerFormConfig_1.initialInvoicingCustomerFormLocalData);
130
132
  },
131
- /**
132
- * Seed the draft from the loaded record, once. Skipped when a draft already
133
- * exists so a late refetch cannot overwrite in-progress edits.
134
- */
133
+ /** Fill the draft from the loaded record — see `applyInvoicingFormDraftSeed`. */
135
134
  seedInvoicingCustomerFormDraft(draft, action) {
136
- if (draft.editDraftById[action.payload.id] == null) {
137
- draft.editDraftById[action.payload.id] = action.payload.localData;
138
- }
135
+ const { id, localData } = action.payload;
136
+ (0, invoicingFormDraft_1.applyInvoicingFormDraftSeed)(draft.editDraftById, id, localData);
139
137
  },
140
138
  updateInvoicingCustomerFormDraft(draft, action) {
141
139
  const { id, localData } = action.payload;
142
140
  if (id != null) {
143
- draft.editDraftById[id] = localData;
141
+ (0, invoicingFormDraft_1.applyInvoicingFormDraftEdit)(draft.editDraftById, id, localData);
144
142
  }
145
143
  else {
146
144
  draft.newCustomerDraft = localData;
@@ -165,12 +163,22 @@ const editInvoicingCustomerDetailView = (0, toolkit_1.createSlice)({
165
163
  draft.fetchState = 'Error';
166
164
  draft.error = action.payload;
167
165
  },
168
- resetEditInvoicingCustomerDetailView(draft) {
166
+ /**
167
+ * Discard a form's draft — the customer's own when `id` is given,
168
+ * the create draft otherwise. Scoped to the one form on purpose: another
169
+ * customer's half-finished edit is not this form's to throw away.
170
+ * Clearing every draft at once is what `clearAll` is for.
171
+ */
172
+ resetEditInvoicingCustomerDetailView(draft, action) {
169
173
  draft.fetchState = 'Not-Started';
170
174
  draft.error = undefined;
171
175
  draft.savedCustomerId = undefined;
172
- draft.editDraftById = {};
173
- draft.newCustomerDraft = { ...invoicingCustomerFormConfig_1.initialInvoicingCustomerFormLocalData };
176
+ const id = action.payload?.id;
177
+ if (id == null) {
178
+ draft.newCustomerDraft = { ...invoicingCustomerFormConfig_1.initialInvoicingCustomerFormLocalData };
179
+ return;
180
+ }
181
+ delete draft.editDraftById[id];
174
182
  },
175
183
  // Trigger only — handled by initializeInvoicingCustomerAddressEpic.
176
184
  initializeInvoicingCustomerAddress: {
@@ -53,6 +53,15 @@ export interface InvoicingCustomerFormView extends SelectorView {
53
53
  * Folded in here so the screen never reaches for the config view itself.
54
54
  */
55
55
  fieldHelp: InvoicingFieldHelpByCode;
56
+ /**
57
+ * True while the customer being edited is still on its way to the form. The
58
+ * form holds a skeleton on this rather than mount: it seeds react-hook-form
59
+ * once, at first render, so fields mounted against the blank draft stay blank
60
+ * and the first change writes that blankness back over the customer. A later
61
+ * refetch does not raise this again, so a refresh leaves the form on screen
62
+ * instead of blanking it mid-edit.
63
+ */
64
+ isFormLoading: boolean;
56
65
  localData: InvoicingCustomerFormLocalData;
57
66
  mode: 'create' | 'edit';
58
67
  netTermDayOptions: InvoicingNamedOption[];
@@ -8,6 +8,7 @@ const invoicingCustomerSelector_2 = require("../../../entity/invoicing/invoicing
8
8
  const addressViewSelector_1 = require("../../addressView/addressViewSelector");
9
9
  const customerPaymentMethodSelector_1 = require("../customerPaymentMethod/customerPaymentMethodSelector");
10
10
  const invoicingConfigViewSelector_1 = require("../invoicingConfigView/invoicingConfigViewSelector");
11
+ const invoicingFormDraft_1 = require("../invoicingFormDraft");
11
12
  const invoicingSettingsHelpers_1 = require("../settingsView/invoicingSettingsHelpers");
12
13
  const settingsViewSelector_1 = require("../settingsView/settingsViewSelector");
13
14
  const invoicingCustomerFormConfig_1 = require("./invoicingCustomerFormConfig");
@@ -63,8 +64,7 @@ const getInvoicingCustomerFormView = (state, invoicingCustomerID) => {
63
64
  // `seedInvoicingCustomerFormDraftEpic` when the detail load lands. Until then
64
65
  // the page is showing its skeleton.
65
66
  const localData = invoicingCustomerID != null
66
- ? (editDraftById[invoicingCustomerID] ??
67
- invoicingCustomerFormConfig_1.initialInvoicingCustomerFormLocalData)
67
+ ? (0, invoicingFormDraft_1.getInvoicingFormDraftLocalData)(editDraftById, invoicingCustomerID, invoicingCustomerFormConfig_1.initialInvoicingCustomerFormLocalData)
68
68
  : newCustomerDraft;
69
69
  // Billing address is captured via the shared Address screen and seeded there
70
70
  // (from the address entity) on detail load, so the formatted label comes from
@@ -82,6 +82,7 @@ const getInvoicingCustomerFormView = (state, invoicingCustomerID) => {
82
82
  ? { error: undefined, fetchState: 'Completed' }
83
83
  : (state.editInvoicingCustomerDetailViewState.recordStateById[invoicingCustomerID] ?? { error: undefined, fetchState: 'Not-Started' });
84
84
  const { emailPreview, emailPreviewCustomerId, emailPreviewFetchState } = state.editInvoicingCustomerDetailViewState;
85
+ const isFormLoading = (0, invoicingFormDraft_1.isInvoicingFormDraftPending)(editDraftById, invoicingCustomerID);
85
86
  return {
86
87
  // A preview cached for another customer is not this screen's.
87
88
  emailPreview: emailPreviewCustomerId === invoicingCustomerID ? emailPreview : undefined,
@@ -96,6 +97,7 @@ const getInvoicingCustomerFormView = (state, invoicingCustomerID) => {
96
97
  promotionalCreditSubmitState: state.editInvoicingCustomerDetailViewState.promotionalCreditSubmitState,
97
98
  record: (0, exports.getInvoicingCustomerRecord)(state, invoicingCustomerID),
98
99
  fieldHelp: (0, invoicingConfigViewSelector_1.invoicingFieldHelpForGroup)(state, 'customer'),
100
+ isFormLoading,
99
101
  customerTypeOptions,
100
102
  localData,
101
103
  mode,
@@ -1,4 +1,5 @@
1
1
  import { FetchStateAndError, FetchedState, ID } from '../../../commonStateTypes/common';
2
+ import { InvoicingFormDraftById } from '../invoicingFormDraft';
2
3
  /**
3
4
  * The payment-link email the composer opens with: the backend's prefill plus
4
5
  * the tokens the frontend splices the user's edits into. State, not wire shape
@@ -37,7 +38,7 @@ export interface InvoicingCustomerFormLocalData {
37
38
  vatNumber: string;
38
39
  }
39
40
  export interface EditInvoicingCustomerDetailViewState extends FetchedState {
40
- editDraftById: Record<ID, InvoicingCustomerFormLocalData>;
41
+ editDraftById: InvoicingFormDraftById<InvoicingCustomerFormLocalData>;
41
42
  /**
42
43
  * The payment-link email rendered by the backend, cached per customer so
43
44
  * reopening the composer costs nothing. `emailPreviewCustomerId` is the cache
@@ -19,7 +19,7 @@ const saveInvoicingCustomerEpic = (actions$, state$, zeniAPI) => actions$.pipe((
19
19
  const rootState = state$.value;
20
20
  const viewState = rootState.editInvoicingCustomerDetailViewState;
21
21
  const localData = (id != null
22
- ? viewState.editDraftById[id]
22
+ ? viewState.editDraftById[id]?.localData
23
23
  : viewState.newCustomerDraft) ?? viewState.newCustomerDraft;
24
24
  // Billing address captured via the shared Address screen is sent as an
25
25
  // object in the payload; the backend creates it and returns its id
@@ -55,6 +55,8 @@ export declare const runInvoicingSubscriptionAction: import("@reduxjs/toolkit").
55
55
  id: ID;
56
56
  }, "editInvoicingSubscriptionDetailView/updateInvoicingSubscriptionDetailFailure">, updateInvoicingSubscriptionFormDraft: import("@reduxjs/toolkit").ActionCreatorWithPayload<UpdateInvoicingSubscriptionFormDraftPayload, "editInvoicingSubscriptionDetailView/updateInvoicingSubscriptionFormDraft">, submitInvoicingSubscriptionForm: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: SubmitInvoicingSubscriptionFormPayload], SubmitInvoicingSubscriptionFormPayload, "editInvoicingSubscriptionDetailView/submitInvoicingSubscriptionForm", never, never>, submitInvoicingSubscriptionFormSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
57
57
  subscriptionId: ID;
58
- }, "editInvoicingSubscriptionDetailView/submitInvoicingSubscriptionFormSuccess">, submitInvoicingSubscriptionFormFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "editInvoicingSubscriptionDetailView/submitInvoicingSubscriptionFormFailure">, resetEditInvoicingSubscriptionDetailView: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"editInvoicingSubscriptionDetailView/resetEditInvoicingSubscriptionDetailView">, initializeInvoicingSubscriptionAddress: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: InitializeInvoicingSubscriptionAddressPayload], InitializeInvoicingSubscriptionAddressPayload, "editInvoicingSubscriptionDetailView/initializeInvoicingSubscriptionAddress", never, never>, syncInvoicingSubscriptionCoupons: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: SyncInvoicingSubscriptionCouponsPayload], SyncInvoicingSubscriptionCouponsPayload, "editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCoupons", never, never>;
58
+ }, "editInvoicingSubscriptionDetailView/submitInvoicingSubscriptionFormSuccess">, submitInvoicingSubscriptionFormFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "editInvoicingSubscriptionDetailView/submitInvoicingSubscriptionFormFailure">, resetEditInvoicingSubscriptionDetailView: import("@reduxjs/toolkit").ActionCreatorWithOptionalPayload<{
59
+ id?: ID;
60
+ } | undefined, "editInvoicingSubscriptionDetailView/resetEditInvoicingSubscriptionDetailView">, initializeInvoicingSubscriptionAddress: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: InitializeInvoicingSubscriptionAddressPayload], InitializeInvoicingSubscriptionAddressPayload, "editInvoicingSubscriptionDetailView/initializeInvoicingSubscriptionAddress", never, never>, syncInvoicingSubscriptionCoupons: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: SyncInvoicingSubscriptionCouponsPayload], SyncInvoicingSubscriptionCouponsPayload, "editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCoupons", never, never>;
59
61
  declare const _default: import("redux").Reducer<EditInvoicingSubscriptionDetailViewState>;
60
62
  export default _default;
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.syncInvoicingSubscriptionCoupons = exports.initializeInvoicingSubscriptionAddress = exports.resetEditInvoicingSubscriptionDetailView = exports.submitInvoicingSubscriptionFormFailure = exports.submitInvoicingSubscriptionFormSuccess = exports.submitInvoicingSubscriptionForm = exports.updateInvoicingSubscriptionFormDraft = exports.updateInvoicingSubscriptionDetailFailure = exports.updateInvoicingSubscriptionDetail = exports.seedInvoicingSubscriptionFormDraft = exports.fetchInvoicingSubscriptionFormPage = exports.fetchInvoicingSubscriptionDetailPage = exports.fetchInvoicingSubscriptionDetail = exports.runInvoicingSubscriptionActionSuccess = exports.runInvoicingSubscriptionActionFailure = exports.runInvoicingSubscriptionAction = exports.initialState = void 0;
5
5
  const toolkit_1 = require("@reduxjs/toolkit");
6
6
  const rootActions_1 = require("../../../rootActions");
7
+ const invoicingFormDraft_1 = require("../invoicingFormDraft");
7
8
  const invoicingSubscriptionFormConfig_1 = require("./invoicingSubscriptionFormConfig");
8
9
  exports.initialState = {
9
10
  fetchState: 'Not-Started',
@@ -69,15 +70,12 @@ const editInvoicingSubscriptionDetailView = (0, toolkit_1.createSlice)({
69
70
  fetchState: 'Error',
70
71
  error: action.payload.error,
71
72
  };
73
+ (0, invoicingFormDraft_1.applyInvoicingFormDraftFailure)(draft.editDraftById, action.payload.id, invoicingSubscriptionFormConfig_1.initialInvoicingSubscriptionFormLocalData);
72
74
  },
73
- /**
74
- * Seed the draft from the loaded record, once. Skipped when a draft already
75
- * exists so a late refetch cannot overwrite in-progress edits.
76
- */
75
+ /** Fill the draft from the loaded record — see `applyInvoicingFormDraftSeed`. */
77
76
  seedInvoicingSubscriptionFormDraft(draft, action) {
78
- if (draft.editDraftById[action.payload.id] == null) {
79
- draft.editDraftById[action.payload.id] = action.payload.localData;
80
- }
77
+ const { id, localData } = action.payload;
78
+ (0, invoicingFormDraft_1.applyInvoicingFormDraftSeed)(draft.editDraftById, id, localData);
81
79
  },
82
80
  fetchInvoicingSubscriptionFormPage: {
83
81
  reducer() {
@@ -91,7 +89,7 @@ const editInvoicingSubscriptionDetailView = (0, toolkit_1.createSlice)({
91
89
  updateInvoicingSubscriptionFormDraft(draft, action) {
92
90
  const { id, localData } = action.payload;
93
91
  if (id != null) {
94
- draft.editDraftById[id] = localData;
92
+ (0, invoicingFormDraft_1.applyInvoicingFormDraftEdit)(draft.editDraftById, id, localData);
95
93
  }
96
94
  else {
97
95
  draft.newSubscriptionDraft = localData;
@@ -116,14 +114,24 @@ const editInvoicingSubscriptionDetailView = (0, toolkit_1.createSlice)({
116
114
  draft.fetchState = 'Error';
117
115
  draft.error = action.payload;
118
116
  },
119
- resetEditInvoicingSubscriptionDetailView(draft) {
117
+ /**
118
+ * Discard a form's draft — the subscription's own when `id` is given,
119
+ * the create draft otherwise. Scoped to the one form on purpose: another
120
+ * subscription's half-finished edit is not this form's to throw away.
121
+ * Clearing every draft at once is what `clearAll` is for.
122
+ */
123
+ resetEditInvoicingSubscriptionDetailView(draft, action) {
120
124
  draft.fetchState = 'Not-Started';
121
125
  draft.error = undefined;
122
126
  draft.savedSubscriptionId = undefined;
123
- draft.editDraftById = {};
124
- draft.newSubscriptionDraft = {
125
- ...invoicingSubscriptionFormConfig_1.initialInvoicingSubscriptionFormLocalData,
126
- };
127
+ const id = action.payload?.id;
128
+ if (id == null) {
129
+ draft.newSubscriptionDraft = {
130
+ ...invoicingSubscriptionFormConfig_1.initialInvoicingSubscriptionFormLocalData,
131
+ };
132
+ return;
133
+ }
134
+ delete draft.editDraftById[id];
127
135
  },
128
136
  // Trigger only — handled by initializeInvoicingSubscriptionAddressEpic.
129
137
  initializeInvoicingSubscriptionAddress: {
@@ -41,6 +41,15 @@ export interface InvoicingSubscriptionFormView extends SelectorView {
41
41
  * Folded in here so the screen never reaches for the config view itself.
42
42
  */
43
43
  fieldHelp: InvoicingFieldHelpByCode;
44
+ /**
45
+ * True while the subscription being edited is still on its way to the form.
46
+ * The form holds a skeleton on this rather than mount: it seeds
47
+ * react-hook-form once, at first render, so fields mounted against the blank
48
+ * draft stay blank and the first change writes that blankness back over the
49
+ * subscription. A later refetch does not raise this again, so a refresh
50
+ * leaves the form on screen instead of blanking it mid-edit.
51
+ */
52
+ isFormLoading: boolean;
44
53
  localData: InvoicingSubscriptionFormLocalData;
45
54
  mode: 'create' | 'edit';
46
55
  /**
@@ -8,6 +8,7 @@ const couponViewSelector_1 = require("../couponView/couponViewSelector");
8
8
  const invoicingCatalogListViewSelector_1 = require("../invoicingCatalogListView/invoicingCatalogListViewSelector");
9
9
  const invoicingConfigViewSelector_1 = require("../invoicingConfigView/invoicingConfigViewSelector");
10
10
  const invoicingCustomerListViewSelector_1 = require("../invoicingCustomerListView/invoicingCustomerListViewSelector");
11
+ const invoicingFormDraft_1 = require("../invoicingFormDraft");
11
12
  const invoicingSubscriptionFormConfig_1 = require("./invoicingSubscriptionFormConfig");
12
13
  const invoicingSubscriptionFormConfig_2 = require("./invoicingSubscriptionFormConfig");
13
14
  const getEditInvoicingSubscriptionDetailViewState = (state, invoicingSubscriptionID) => {
@@ -37,8 +38,7 @@ const getInvoicingSubscriptionFormView = (state, invoicingSubscriptionID) => {
37
38
  // `seedInvoicingSubscriptionFormDraftEpic` when the detail load lands. Until
38
39
  // then the page is showing its skeleton.
39
40
  const localData = invoicingSubscriptionID != null
40
- ? (editDraftById[invoicingSubscriptionID] ??
41
- invoicingSubscriptionFormConfig_1.initialInvoicingSubscriptionFormLocalData)
41
+ ? (0, invoicingFormDraft_1.getInvoicingFormDraftLocalData)(editDraftById, invoicingSubscriptionID, invoicingSubscriptionFormConfig_1.initialInvoicingSubscriptionFormLocalData)
42
42
  : newSubscriptionDraft;
43
43
  // Shipping address is captured via the shared Address screen and seeded there
44
44
  // (looked up in the shared address store by `shippingAddressId`) on detail
@@ -69,6 +69,7 @@ const getInvoicingSubscriptionFormView = (state, invoicingSubscriptionID) => {
69
69
  subscriptionLifecycleActionOptions: configState.subscriptionLifecycleActionOptions,
70
70
  subscriptionMetadataEditAllowedFromStatuses: configState.subscriptionMetadataEditAllowedFromStatuses,
71
71
  recordState,
72
+ isFormLoading: (0, invoicingFormDraft_1.isInvoicingFormDraftPending)(editDraftById, invoicingSubscriptionID),
72
73
  fieldHelp: (0, invoicingConfigViewSelector_1.invoicingFieldHelpForGroup)(state, 'subscription'),
73
74
  coupons: (0, couponViewSelector_1.getInvoicingCouponListItems)(state),
74
75
  customers: (0, invoicingCustomerListViewSelector_1.getInvoicingCustomerListView)(state).customers,
@@ -1,5 +1,6 @@
1
1
  import { Amount } from '../../../commonStateTypes/amount';
2
2
  import { FetchStateAndError, FetchedState, ID } from '../../../commonStateTypes/common';
3
+ import { InvoicingFormDraftById } from '../invoicingFormDraft';
3
4
  /**
4
5
  * Draft form state for the create/edit subscription form. Held in CES so the
5
6
  * form flows its data from here (mirrors the customer form / bill-pay pattern).
@@ -41,7 +42,7 @@ export interface InvoicingSubscriptionFormLocalData {
41
42
  export interface EditInvoicingSubscriptionDetailViewState extends FetchedState {
42
43
  /** Lifecycle action fetch state per subscription id. */
43
44
  actionStateById: Record<ID, FetchStateAndError>;
44
- editDraftById: Record<ID, InvoicingSubscriptionFormLocalData>;
45
+ editDraftById: InvoicingFormDraftById<InvoicingSubscriptionFormLocalData>;
45
46
  newSubscriptionDraft: InvoicingSubscriptionFormLocalData;
46
47
  /** Detail-load state per subscription id. */
47
48
  recordStateById: Record<ID, FetchStateAndError>;
@@ -19,8 +19,11 @@ const fetchInvoicingSubscriptionFormPageEpic = (actions$) => actions$.pipe((0, o
19
19
  const { subscriptionId } = action.payload;
20
20
  const pageActions = [
21
21
  (0, invoicingCustomerListViewReducer_1.fetchInvoicingCustomerList)({ filters: {}, keepExistingListItems: false }),
22
+ // Plans only: the form's plan picker is the one consumer, and an
23
+ // unfiltered list would replace the store's plans with every catalog
24
+ // item.
22
25
  (0, invoicingCatalogListViewReducer_1.fetchInvoicingCatalogPlanList)({
23
- filters: {},
26
+ filters: { type: 'plan' },
24
27
  keepExistingListItems: false,
25
28
  }),
26
29
  (0, couponViewReducer_1.fetchInvoicingCouponList)({ filters: {}, keepExistingListItems: false }),
@@ -20,7 +20,7 @@ const saveInvoicingSubscriptionEpic = (actions$, state$, zeniAPI) => actions$.pi
20
20
  const rootState = state$.value;
21
21
  const viewState = rootState.editInvoicingSubscriptionDetailViewState;
22
22
  const localData = (id != null
23
- ? viewState.editDraftById[id]
23
+ ? viewState.editDraftById[id]?.localData
24
24
  : viewState.newSubscriptionDraft) ?? viewState.newSubscriptionDraft;
25
25
  // Shipping address captured via the shared Address screen is sent as an
26
26
  // object in the payload (same pattern as the customer save flow).
@@ -1,12 +1,16 @@
1
1
  import { ActionsObservable, StateObservable } from 'redux-observable';
2
2
  import { Observable } from 'rxjs';
3
3
  import { RootState } from '../../../rootStateTypes';
4
- import { seedInvoicingSubscriptionFormDraft, updateInvoicingSubscriptionDetail } from './editInvoicingSubscriptionDetailViewReducer';
5
- export type ActionType = ReturnType<typeof updateInvoicingSubscriptionDetail> | ReturnType<typeof seedInvoicingSubscriptionFormDraft>;
4
+ import { fetchInvoicingSubscriptionFormPage, seedInvoicingSubscriptionFormDraft, updateInvoicingSubscriptionDetail } from './editInvoicingSubscriptionDetailViewReducer';
5
+ export type ActionType = ReturnType<typeof fetchInvoicingSubscriptionFormPage> | ReturnType<typeof updateInvoicingSubscriptionDetail> | ReturnType<typeof seedInvoicingSubscriptionFormDraft>;
6
6
  /**
7
- * Seeds the edit draft from the record the moment the detail fetch lands, so
8
- * the form has its values before it mounts. The reducer ignores the seed when a
9
- * draft already exists, which is what stops a background refetch from
10
- * overwriting edits in progress.
7
+ * Seeds the edit draft from the record, so the form has its values before it
8
+ * mounts. The reducer ignores a seed once the draft holds the record, which is
9
+ * what stops a background refetch from overwriting edits in progress.
10
+ *
11
+ * Opening the page counts as a trigger, not just the detail response: the page
12
+ * fetch is cache-guarded, so re-opening a form for a record already loaded this
13
+ * session issues no request at all — and a draft cleared in between (by a save
14
+ * or a cancel) would have nothing left to seed it.
11
15
  */
12
16
  export declare const seedInvoicingSubscriptionFormDraftEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>) => Observable<ActionType>;