@zeniai/client-epic-state 5.2.8 → 5.2.10-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.
@@ -1,3 +1,5 @@
1
+ import { AccountBasePayload } from '../account/accountPayload';
2
+ import { AccountBase } from '../account/accountState';
1
3
  import { AddressPayload } from '../address/addressPayload';
2
4
  export type InvoicingPriceType = 'tax_exclusive' | 'tax_inclusive';
3
5
  export declare const INVOICING_DUNNING_ACTIONS: readonly ["email_only", "email_and_retry", "do_nothing", "retry_only"];
@@ -92,41 +94,51 @@ export declare function toInvoicingQboCustomerMatchingMode(value: string): Invoi
92
94
  * A mapped account as the newer settings payload returns it: the platform id,
93
95
  * plus hydrated fields the client only displays.
94
96
  */
95
- export interface InvoicingQboMappedAccountPayload {
96
- account_id?: string | null;
97
- account_name?: string | null;
98
- fully_qualified_name?: string | null;
99
- qbo_id?: string | null;
100
- }
101
97
  /**
102
98
  * Account mappings arrive in two shapes. The original sends a bare id per slot
103
- * (`deposit_account_id`); the newer one sends an object holding the platform id
104
- * (`deposit_account: {account_id}`). Both are declared because a tenant can be
105
- * served either depending on when its backend rolled, and a page that understands
106
- * only one renders an empty picker over data that exists.
99
+ * (`deposit_account_id`); the newer one sends the account itself
100
+ * (`deposit_account`). Both are declared because a tenant can be served either
101
+ * depending on when its backend rolled, and a page that understands only one
102
+ * renders an empty picker over data that exists. `Partial` because a read may
103
+ * carry only the ids, while a write sends a whole `AccountBasePayload`.
107
104
  */
108
105
  export interface InvoicingQboAccountMappingsPayload {
109
- default_income_account?: InvoicingQboMappedAccountPayload | null;
106
+ default_income_account?: Partial<AccountBasePayload> | null;
110
107
  default_income_account_id?: string | null;
111
- deposit_account?: InvoicingQboMappedAccountPayload | null;
108
+ deposit_account?: Partial<AccountBasePayload> | null;
112
109
  deposit_account_id?: string | null;
113
110
  deposit_account_overrides?: Record<string, string> | null;
114
- discounts_account?: InvoicingQboMappedAccountPayload | null;
111
+ discounts_account?: Partial<AccountBasePayload> | null;
115
112
  discounts_account_id?: string | null;
116
- rounding_account?: InvoicingQboMappedAccountPayload | null;
113
+ rounding_account?: Partial<AccountBasePayload> | null;
117
114
  rounding_account_id?: string | null;
118
- write_off_account?: InvoicingQboMappedAccountPayload | null;
115
+ write_off_account?: Partial<AccountBasePayload> | null;
119
116
  write_off_account_id?: string | null;
120
117
  }
118
+ /**
119
+ * The id for one mapping slot, from whichever shape the backend sent. The object
120
+ * form wins when present; a blank id in either is treated as unset so the picker
121
+ * shows its placeholder rather than selecting nothing.
122
+ */
123
+ /**
124
+ * The first id a picker can actually select. Callers list the QuickBooks id
125
+ * first: the options are keyed on it, so an internal id selects nothing even
126
+ * when it names the same record. Blank counts as absent, so a cleared field
127
+ * falls through to the next candidate rather than selecting an empty option.
128
+ */
129
+ export declare const firstSelectableInvoicingId: (candidates: readonly (string | null | undefined)[]) => string | undefined;
121
130
  /**
122
131
  * Read one account-mapping dropdown value by its config `code` (the codes in
123
132
  * `qbo_account_mapping_kind_options` match the `account_mappings` field names).
124
133
  */
125
134
  export declare function getInvoicingQboAccountMappingId(accountMappings: InvoicingQboAccountMappingsPayload | undefined, code: string): string | undefined;
126
135
  /**
127
- * Return a copy of the account mappings with the dropdown slot named by
128
- * `code` set to `accountId`. Unknown codes leave the mappings unchanged.
136
+ * Expand each mapping from the id the picker submitted to the whole account, the
137
+ * shape the service stores. Written at the request boundary rather than in the
138
+ * page's draft so the UI keeps working in ids, and an id that no longer resolves
139
+ * is left out — a mapping the service cannot expand is worse than none.
129
140
  */
141
+ export declare const withInvoicingQboMappedAccounts: (accountMappings: InvoicingQboAccountMappingsPayload | undefined, accountByQboId: (qboId: string) => AccountBase | undefined) => InvoicingQboAccountMappingsPayload | undefined;
130
142
  export declare function withInvoicingQboAccountMappingId(accountMappings: InvoicingQboAccountMappingsPayload | undefined, code: string, accountId: string): InvoicingQboAccountMappingsPayload;
131
143
  export interface InvoicingQboSettingsPayload {
132
144
  account_mappings?: InvoicingQboAccountMappingsPayload | null;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ALL_INVOICING_FIELD_HELP_GROUPS = 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.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;
@@ -23,6 +23,7 @@ exports.toInvoicingSubscriptionEditFieldOption = toInvoicingSubscriptionEditFiel
23
23
  exports.toAuditLogEntityRouteOption = toAuditLogEntityRouteOption;
24
24
  exports.buildInvoicingEntityPath = buildInvoicingEntityPath;
25
25
  exports.isInvoicingEntityLinkable = isInvoicingEntityLinkable;
26
+ const accountPayload_1 = require("../account/accountPayload");
26
27
  exports.INVOICING_DUNNING_ACTIONS = [
27
28
  'email_only',
28
29
  'email_and_retry',
@@ -158,17 +159,22 @@ function toInvoicingQboCustomerMatchingMode(value) {
158
159
  * form wins when present; a blank id in either is treated as unset so the picker
159
160
  * shows its placeholder rather than selecting nothing.
160
161
  */
162
+ /**
163
+ * The first id a picker can actually select. Callers list the QuickBooks id
164
+ * first: the options are keyed on it, so an internal id selects nothing even
165
+ * when it names the same record. Blank counts as absent, so a cleared field
166
+ * falls through to the next candidate rather than selecting an empty option.
167
+ */
168
+ const firstSelectableInvoicingId = (candidates) => candidates.find((candidate) => candidate != null && candidate !== '');
169
+ exports.firstSelectableInvoicingId = firstSelectableInvoicingId;
161
170
  function readInvoicingQboMappedAccountId(mappedAccount, legacyAccountId) {
162
- // `qbo_id` first: the pickers key their options on the QuickBooks id, so an
163
- // internal `account_id` would not select anything even when it is the same
164
- // account. Both are read because the mapping arrives either way depending on
165
- // how it was saved.
166
- const candidates = [
171
+ // Both shapes are read: the mapping arrives either way depending on how it
172
+ // was saved.
173
+ return (0, exports.firstSelectableInvoicingId)([
167
174
  mappedAccount?.qbo_id,
168
175
  mappedAccount?.account_id,
169
176
  legacyAccountId,
170
- ];
171
- return candidates.find((candidate) => candidate != null && candidate !== '');
177
+ ]);
172
178
  }
173
179
  /**
174
180
  * Read one account-mapping dropdown value by its config `code` (the codes in
@@ -194,11 +200,42 @@ function getInvoicingQboAccountMappingId(accountMappings, code) {
194
200
  * Return a copy of the account mappings with the dropdown slot named by
195
201
  * `code` set to `accountId`. Unknown codes leave the mappings unchanged.
196
202
  */
203
+ /** The object key holding the mapped account for each `*_account_id` code. */
204
+ const INVOICING_QBO_MAPPED_ACCOUNT_KEY = {
205
+ default_income_account_id: 'default_income_account',
206
+ deposit_account_id: 'deposit_account',
207
+ discounts_account_id: 'discounts_account',
208
+ rounding_account_id: 'rounding_account',
209
+ write_off_account_id: 'write_off_account',
210
+ };
211
+ /**
212
+ * Expand each mapping from the id the picker submitted to the whole account, the
213
+ * shape the service stores. Written at the request boundary rather than in the
214
+ * page's draft so the UI keeps working in ids, and an id that no longer resolves
215
+ * is left out — a mapping the service cannot expand is worse than none.
216
+ */
217
+ const withInvoicingQboMappedAccounts = (accountMappings, accountByQboId) => {
218
+ if (accountMappings == null) {
219
+ return undefined;
220
+ }
221
+ const expanded = { ...accountMappings };
222
+ Object.entries(INVOICING_QBO_MAPPED_ACCOUNT_KEY).forEach(([code, objectKey]) => {
223
+ const accountId = getInvoicingQboAccountMappingId(accountMappings, code);
224
+ if (accountId == null) {
225
+ return;
226
+ }
227
+ const account = accountByQboId(accountId);
228
+ expanded[objectKey] =
229
+ account != null ? (0, accountPayload_1.toAccountPayload)(account) : undefined;
230
+ expanded[code] = undefined;
231
+ });
232
+ return expanded;
233
+ };
234
+ exports.withInvoicingQboMappedAccounts = withInvoicingQboMappedAccounts;
197
235
  function withInvoicingQboAccountMappingId(accountMappings, code, accountId) {
198
236
  // Both slots are written together. Setting only the bare id would leave the
199
237
  // object shape behind, and the reader prefers it — so the selection just made
200
- // would read back as whatever was mapped before. The object is dropped rather
201
- // than rebuilt: only the id is known here, not the name that came with it.
238
+ // would read back as whatever was mapped before.
202
239
  const base = accountMappings ?? {};
203
240
  switch (code) {
204
241
  case 'deposit_account_id':
@@ -1,3 +1,5 @@
1
+ import { AccountBasePayload } from '../../account/accountPayload';
2
+ import { ClassBasePayload } from '../../class/classPayload';
1
3
  import { InvoicingPlanTier, InvoicingProduct } from './productState';
2
4
  export interface InvoicingPlanTierPayload {
3
5
  price: number;
@@ -7,10 +9,18 @@ export interface InvoicingPlanTierPayload {
7
9
  pricing_type?: string;
8
10
  }
9
11
  export interface InvoicingAccountingDetailPayload {
12
+ /**
13
+ * `account` and `accounting_class` hold whole records — what the service stores
14
+ * and what the catalog payload builder sends. The bare `accounting_code` /
15
+ * `accounting_category_1` are the older shape, still read so a plan saved
16
+ * before the rollover prefills.
17
+ */
18
+ account?: Partial<AccountBasePayload> | null;
10
19
  accounting_category_1?: string;
11
20
  accounting_category_2?: string;
12
21
  accounting_category_3?: string;
13
22
  accounting_category_4?: string;
23
+ accounting_class?: Partial<ClassBasePayload> | null;
14
24
  accounting_code?: string;
15
25
  sku?: string;
16
26
  }
@@ -4,6 +4,7 @@ exports.mapInvoicingProductPayloadToState = exports.mapInvoicingPlanTierPayloadT
4
4
  const amount_1 = require("../../../commonStateTypes/amount");
5
5
  const currencyHelper_1 = require("../../../commonStateTypes/currencyHelper");
6
6
  const zeniDayJS_1 = require("../../../zeniDayJS");
7
+ const invoicingCommonPayload_1 = require("../invoicingCommonPayload");
7
8
  // ── Payload → State mappers (snake_case wire shape → camelCase domain shape) ──
8
9
  const mapInvoicingPlanTierPayloadToState = (payload, currencyCode, currencySymbol) => ({
9
10
  price: (0, amount_1.toAmount)(payload.price, currencyCode, currencySymbol),
@@ -14,11 +15,22 @@ const mapInvoicingPlanTierPayloadToState = (payload, currencyCode, currencySymbo
14
15
  });
15
16
  exports.mapInvoicingPlanTierPayloadToState = mapInvoicingPlanTierPayloadToState;
16
17
  const mapInvoicingAccountingDetailPayloadToState = (payload) => ({
17
- accountingCategory1: payload.accounting_category_1,
18
+ // The QuickBooks id first in both, for the reason `firstSelectableInvoicingId`
19
+ // documents: the form's pickers are keyed on it, so reading the internal id
20
+ // would leave the field blank over a mapping that exists.
21
+ accountingCategory1: (0, invoicingCommonPayload_1.firstSelectableInvoicingId)([
22
+ payload.accounting_class?.qbo_id,
23
+ payload.accounting_class?.accounting_class_id,
24
+ payload.accounting_category_1,
25
+ ]),
18
26
  accountingCategory2: payload.accounting_category_2,
19
27
  accountingCategory3: payload.accounting_category_3,
20
28
  accountingCategory4: payload.accounting_category_4,
21
- accountingCode: payload.accounting_code,
29
+ accountingCode: (0, invoicingCommonPayload_1.firstSelectableInvoicingId)([
30
+ payload.account?.qbo_id,
31
+ payload.account?.account_id,
32
+ payload.accounting_code,
33
+ ]),
22
34
  sku: payload.sku,
23
35
  });
24
36
  const mapInvoicingProductPayloadToState = (payload) => {
@@ -1,3 +1,4 @@
1
+ import { toAccountPayload } from '../account/accountPayload';
1
2
  export const INVOICING_DUNNING_ACTIONS = [
2
3
  'email_only',
3
4
  'email_and_retry',
@@ -133,17 +134,21 @@ export function toInvoicingQboCustomerMatchingMode(value) {
133
134
  * form wins when present; a blank id in either is treated as unset so the picker
134
135
  * shows its placeholder rather than selecting nothing.
135
136
  */
137
+ /**
138
+ * The first id a picker can actually select. Callers list the QuickBooks id
139
+ * first: the options are keyed on it, so an internal id selects nothing even
140
+ * when it names the same record. Blank counts as absent, so a cleared field
141
+ * falls through to the next candidate rather than selecting an empty option.
142
+ */
143
+ export const firstSelectableInvoicingId = (candidates) => candidates.find((candidate) => candidate != null && candidate !== '');
136
144
  function readInvoicingQboMappedAccountId(mappedAccount, legacyAccountId) {
137
- // `qbo_id` first: the pickers key their options on the QuickBooks id, so an
138
- // internal `account_id` would not select anything even when it is the same
139
- // account. Both are read because the mapping arrives either way depending on
140
- // how it was saved.
141
- const candidates = [
145
+ // Both shapes are read: the mapping arrives either way depending on how it
146
+ // was saved.
147
+ return firstSelectableInvoicingId([
142
148
  mappedAccount?.qbo_id,
143
149
  mappedAccount?.account_id,
144
150
  legacyAccountId,
145
- ];
146
- return candidates.find((candidate) => candidate != null && candidate !== '');
151
+ ]);
147
152
  }
148
153
  /**
149
154
  * Read one account-mapping dropdown value by its config `code` (the codes in
@@ -169,11 +174,41 @@ export function getInvoicingQboAccountMappingId(accountMappings, code) {
169
174
  * Return a copy of the account mappings with the dropdown slot named by
170
175
  * `code` set to `accountId`. Unknown codes leave the mappings unchanged.
171
176
  */
177
+ /** The object key holding the mapped account for each `*_account_id` code. */
178
+ const INVOICING_QBO_MAPPED_ACCOUNT_KEY = {
179
+ default_income_account_id: 'default_income_account',
180
+ deposit_account_id: 'deposit_account',
181
+ discounts_account_id: 'discounts_account',
182
+ rounding_account_id: 'rounding_account',
183
+ write_off_account_id: 'write_off_account',
184
+ };
185
+ /**
186
+ * Expand each mapping from the id the picker submitted to the whole account, the
187
+ * shape the service stores. Written at the request boundary rather than in the
188
+ * page's draft so the UI keeps working in ids, and an id that no longer resolves
189
+ * is left out — a mapping the service cannot expand is worse than none.
190
+ */
191
+ export const withInvoicingQboMappedAccounts = (accountMappings, accountByQboId) => {
192
+ if (accountMappings == null) {
193
+ return undefined;
194
+ }
195
+ const expanded = { ...accountMappings };
196
+ Object.entries(INVOICING_QBO_MAPPED_ACCOUNT_KEY).forEach(([code, objectKey]) => {
197
+ const accountId = getInvoicingQboAccountMappingId(accountMappings, code);
198
+ if (accountId == null) {
199
+ return;
200
+ }
201
+ const account = accountByQboId(accountId);
202
+ expanded[objectKey] =
203
+ account != null ? toAccountPayload(account) : undefined;
204
+ expanded[code] = undefined;
205
+ });
206
+ return expanded;
207
+ };
172
208
  export function withInvoicingQboAccountMappingId(accountMappings, code, accountId) {
173
209
  // Both slots are written together. Setting only the bare id would leave the
174
210
  // object shape behind, and the reader prefers it — so the selection just made
175
- // would read back as whatever was mapped before. The object is dropped rather
176
- // than rebuilt: only the id is known here, not the name that came with it.
211
+ // would read back as whatever was mapped before.
177
212
  const base = accountMappings ?? {};
178
213
  switch (code) {
179
214
  case 'deposit_account_id':
@@ -1,6 +1,7 @@
1
1
  import { toAmount } from '../../../commonStateTypes/amount';
2
2
  import { defaultCustomerCurrency } from '../../../commonStateTypes/currencyHelper';
3
3
  import { date } from '../../../zeniDayJS';
4
+ import { firstSelectableInvoicingId } from '../invoicingCommonPayload';
4
5
  // ── Payload → State mappers (snake_case wire shape → camelCase domain shape) ──
5
6
  export const mapInvoicingPlanTierPayloadToState = (payload, currencyCode, currencySymbol) => ({
6
7
  price: toAmount(payload.price, currencyCode, currencySymbol),
@@ -10,11 +11,22 @@ export const mapInvoicingPlanTierPayloadToState = (payload, currencyCode, curren
10
11
  pricingType: payload.pricing_type,
11
12
  });
12
13
  const mapInvoicingAccountingDetailPayloadToState = (payload) => ({
13
- accountingCategory1: payload.accounting_category_1,
14
+ // The QuickBooks id first in both, for the reason `firstSelectableInvoicingId`
15
+ // documents: the form's pickers are keyed on it, so reading the internal id
16
+ // would leave the field blank over a mapping that exists.
17
+ accountingCategory1: firstSelectableInvoicingId([
18
+ payload.accounting_class?.qbo_id,
19
+ payload.accounting_class?.accounting_class_id,
20
+ payload.accounting_category_1,
21
+ ]),
14
22
  accountingCategory2: payload.accounting_category_2,
15
23
  accountingCategory3: payload.accounting_category_3,
16
24
  accountingCategory4: payload.accounting_category_4,
17
- accountingCode: payload.accounting_code,
25
+ accountingCode: firstSelectableInvoicingId([
26
+ payload.account?.qbo_id,
27
+ payload.account?.account_id,
28
+ payload.accounting_code,
29
+ ]),
18
30
  sku: payload.sku,
19
31
  });
20
32
  export const mapInvoicingProductPayloadToState = (payload) => {
@@ -1,3 +1,5 @@
1
+ import { toAccountPayload } from '../../../entity/account/accountPayload';
2
+ import { toClassBasePayload } from '../../../entity/class/classPayload';
1
3
  import { blankInvoicingCatalogSubFamily, isTieredCatalogPricingModel, } from './invoicingCatalogItemFormConfig';
2
4
  const CURRENCY_CODE = 'USD';
3
5
  const trimOrUndefined = (value) => value.trim() !== '' ? value.trim() : undefined;
@@ -7,20 +9,26 @@ const toProductCode = (value) => value
7
9
  .toLowerCase()
8
10
  .replace(/[^a-z0-9]+/g, '-')
9
11
  .replace(/^-|-$/g, '');
10
- const buildAccountingDetail = (subFamily) => {
12
+ const buildAccountingDetail = (subFamily, lookup) => {
11
13
  const detail = {};
12
14
  if (subFamily.sku.trim() !== '') {
13
15
  detail.sku = subFamily.sku.trim();
14
16
  }
15
- if (subFamily.accountingCode.trim() !== '') {
16
- detail.accounting_code = subFamily.accountingCode.trim();
17
+ // The account and class travel as whole objects rather than bare codes, built
18
+ // by the same helpers the transaction and vendor payloads use. An id that no
19
+ // longer resolves is dropped: sending a code the service cannot expand is how
20
+ // a mapping silently lands on the wrong account.
21
+ const account = lookup.accountByQboId(subFamily.accountingCode.trim());
22
+ if (account != null) {
23
+ detail.account = toAccountPayload(account);
17
24
  }
18
- if (subFamily.accountingCategory.trim() !== '') {
19
- detail.accounting_category_1 = subFamily.accountingCategory.trim();
25
+ const accountingClass = lookup.classByQboId(subFamily.accountingCategory.trim());
26
+ if (accountingClass != null) {
27
+ detail.accounting_class = toClassBasePayload(accountingClass);
20
28
  }
21
29
  return Object.keys(detail).length > 0 ? detail : undefined;
22
30
  };
23
- const buildSubFamilyPayload = (localData, subFamily, clearEmptyValues) => {
31
+ const buildSubFamilyPayload = (localData, subFamily, clearEmptyValues, lookup) => {
24
32
  const isAddon = localData.catalogType === 'addon';
25
33
  const tiered = isTieredCatalogPricingModel(subFamily.pricingModel);
26
34
  const name = subFamily.name.trim();
@@ -74,7 +82,7 @@ const buildSubFamilyPayload = (localData, subFamily, clearEmptyValues) => {
74
82
  starting_unit: Number(tier.startingUnit) || 1,
75
83
  }));
76
84
  }
77
- const accountingDetail = buildAccountingDetail(subFamily);
85
+ const accountingDetail = buildAccountingDetail(subFamily, lookup);
78
86
  if (accountingDetail != null) {
79
87
  payload.accounting_detail = accountingDetail;
80
88
  }
@@ -86,7 +94,7 @@ const DEFAULT_CATALOG_ITEM_UNIT = 'USD';
86
94
  * Build the parent catalog-item request body from the form draft. Creates omit
87
95
  * both parent and sub-family IDs; edits retain returned sub-family IDs.
88
96
  */
89
- export const localDataToSaveInvoicingCatalogItemPayload = (localData, isCreate) => {
97
+ export const localDataToSaveInvoicingCatalogItemPayload = (localData, isCreate, lookup) => {
90
98
  const description = trimOrUndefined(localData.description);
91
99
  const productCode = trimOrUndefined(localData.productCode) ?? toProductCode(localData.name);
92
100
  const itemPayload = {
@@ -114,7 +122,7 @@ export const localDataToSaveInvoicingCatalogItemPayload = (localData, isCreate)
114
122
  ? localData.subFamilies
115
123
  : [blankInvoicingCatalogSubFamily()];
116
124
  itemPayload.sub_family_details = isCreate
117
- ? subFamilies.map((subFamily) => buildSubFamilyPayload(localData, subFamily, false))
125
+ ? subFamilies.map((subFamily) => buildSubFamilyPayload(localData, subFamily, false, lookup))
118
126
  : subFamilies.map((subFamily) => {
119
127
  const priceCode = trimOrUndefined(subFamily.priceCode) ?? toProductCode(subFamily.name);
120
128
  const detail = {
@@ -124,6 +132,13 @@ export const localDataToSaveInvoicingCatalogItemPayload = (localData, isCreate)
124
132
  price_code: priceCode,
125
133
  show_description_in_invoices: subFamily.showDescriptionInInvoices,
126
134
  };
135
+ // Edits send a narrower body than creates, but the accounting mapping
136
+ // is editable on this form, so leaving it out dropped the account and
137
+ // class the user had just picked.
138
+ const accountingDetail = buildAccountingDetail(subFamily, lookup);
139
+ if (accountingDetail != null) {
140
+ detail.accounting_detail = accountingDetail;
141
+ }
127
142
  const id = trimOrUndefined(subFamily.id);
128
143
  return id != null ? { ...detail, id } : detail;
129
144
  });
@@ -6,6 +6,7 @@ import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiE
6
6
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
7
7
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
8
8
  import { addInvoicingCatalogItem } from '../invoicingCatalogListView/invoicingCatalogListViewReducer';
9
+ import { invoicingQboAccountByQboId, invoicingQboClassByQboId, } from '../invoicingQboView/invoicingQboViewSelector';
9
10
  import { localDataToSaveInvoicingCatalogItemPayload, } from './editInvoicingCatalogItemDetailViewPayload';
10
11
  import { saveInvoicingCatalogItem, submitInvoicingCatalogItemForm, submitInvoicingCatalogItemFormFailure, submitInvoicingCatalogItemFormSuccess, } from './editInvoicingCatalogItemDetailViewReducer';
11
12
  export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter((action) => submitInvoicingCatalogItemForm.match(action) ||
@@ -20,7 +21,11 @@ export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actio
20
21
  const viewState = state$.value.editInvoicingCatalogItemDetailViewState;
21
22
  const localData = (id != null ? viewState.editDraftById[id] : viewState.newItemDraft) ??
22
23
  viewState.newItemDraft;
23
- data = localDataToSaveInvoicingCatalogItemPayload(localData, id == null);
24
+ const state = state$.value;
25
+ data = localDataToSaveInvoicingCatalogItemPayload(localData, id == null, {
26
+ accountByQboId: (qboId) => invoicingQboAccountByQboId(state, qboId),
27
+ classByQboId: (qboId) => invoicingQboClassByQboId(state, qboId),
28
+ });
24
29
  }
25
30
  const baseUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0`;
26
31
  const path = id != null
@@ -37,6 +37,26 @@ export const INVOICING_INCOME_ACCOUNT_TYPES = [
37
37
  'income',
38
38
  'other_income',
39
39
  ];
40
+ /**
41
+ * The account behind a picker value. The pickers submit the QuickBooks id, so
42
+ * that is what callers hold; the payload builders need the whole entity to send
43
+ * `toAccountPayload`, which is why the lookup lives here next to the options.
44
+ */
45
+ export const invoicingQboAccountByQboId = (state, qboId) => {
46
+ if (qboId == null || qboId === '') {
47
+ return undefined;
48
+ }
49
+ const { accounts } = getAccountList(state.accountState, state.accountListState, INVOICING_ACCOUNT_LIST_KEY);
50
+ return accounts.find((account) => String(account.qboId) === qboId);
51
+ };
52
+ /** The class behind a picker value, for the same reason as the account above. */
53
+ export const invoicingQboClassByQboId = (state, qboId) => {
54
+ if (qboId == null || qboId === '') {
55
+ return undefined;
56
+ }
57
+ const { classes } = getClassList(state.classState, state.classListState);
58
+ return classes.find((accountingClass) => String(accountingClass.qboId) === qboId);
59
+ };
40
60
  /**
41
61
  * Accounts and classes as the invoicing pickers need them. Both come from the
42
62
  * shared accounting-service lists, so whatever another screen already loaded
@@ -1,9 +1,11 @@
1
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 { withInvoicingQboMappedAccounts, } from '../../../entity/invoicing/invoicingCommonPayload';
4
5
  import { apiErrorSnackbarVariables } from '../../../entity/snackbar/snackbarApiError';
5
6
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
6
7
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
8
+ import { invoicingQboAccountByQboId } from '../invoicingQboView/invoicingQboViewSelector';
7
9
  import { connectInvoicingStripe, disconnectInvoicingStripe, fetchInvoicingSettings, invoicingStripeConnectionFailure, saveInvoicingSettings, saveInvoicingSettingsFailure, saveInvoicingSettingsSuccess, updateInvoicingSettings, updateInvoicingSettingsFailure, updateInvoicingStripeConnection, } from './settingsViewReducer';
8
10
  export const fetchInvoicingSettingsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingSettings.match), switchMap(() => {
9
11
  const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/settings`;
@@ -57,9 +59,22 @@ export const disconnectInvoicingStripeEpic = (actions$, _state$, zeniAPI) => act
57
59
  }), catchError((error) => of(invoicingStripeConnectionFailure(createZeniAPIStatus('Unexpected Error', 'Stripe disconnect REST API call errored out ' +
58
60
  JSON.stringify(error))))));
59
61
  }));
60
- export const saveInvoicingSettingsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(saveInvoicingSettings.match), mergeMap((action) => {
62
+ export const saveInvoicingSettingsEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(saveInvoicingSettings.match), mergeMap((action) => {
61
63
  const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/settings`;
62
64
  const { settings, snackbarSection } = action.payload;
65
+ // The QBO page edits mappings as picker ids; the service stores whole
66
+ // accounts. Expanded here, at the request boundary, so the page does not
67
+ // have to hold entities it only needs in order to save.
68
+ const qboSettings = settings.qbo;
69
+ const body = qboSettings?.account_mappings != null
70
+ ? {
71
+ ...settings,
72
+ qbo: {
73
+ ...qboSettings,
74
+ account_mappings: withInvoicingQboMappedAccounts(qboSettings.account_mappings, (qboId) => invoicingQboAccountByQboId(state$.value, qboId)),
75
+ },
76
+ }
77
+ : settings;
63
78
  /** `status` surfaces the platform's error text; omit it to keep the generic copy. */
64
79
  const snackbar = (messageText, status) => snackbarSection == null
65
80
  ? []
@@ -72,7 +87,7 @@ export const saveInvoicingSettingsEpic = (actions$, _state$, zeniAPI) => actions
72
87
  }),
73
88
  ];
74
89
  return zeniAPI
75
- .putAndGetJSON(apiUrl, { ...settings })
90
+ .putAndGetJSON(apiUrl, { ...body })
76
91
  .pipe(switchMap((response) => {
77
92
  if (isSuccessResponse(response) && response.data != null) {
78
93
  return from([
@@ -1,9 +1,20 @@
1
+ import { AccountBase } from '../../../entity/account/accountState';
2
+ import { ClassBase } from '../../../entity/class/classState';
1
3
  import { InvoicingPlanPayload } from '../../../entity/invoicing/plan/planPayload';
2
4
  import { ZeniAPIResponse } from '../../../responsePayload';
3
5
  import { InvoicingCatalogItemFormLocalData } from './editInvoicingCatalogItemDetailViewState';
4
6
  export type SaveInvoicingCatalogItemResponse = ZeniAPIResponse<InvoicingPlanPayload>;
7
+ /**
8
+ * Resolves the QuickBooks ids the pickers submit back to the entities they name.
9
+ * The payload carries the whole account and class, so the ids alone are not
10
+ * enough; `saveInvoicingCatalogItemEpic` builds this from the shared lists.
11
+ */
12
+ export interface InvoicingCatalogAccountingLookup {
13
+ accountByQboId: (qboId: string) => AccountBase | undefined;
14
+ classByQboId: (qboId: string) => ClassBase | undefined;
15
+ }
5
16
  /**
6
17
  * Build the parent catalog-item request body from the form draft. Creates omit
7
18
  * both parent and sub-family IDs; edits retain returned sub-family IDs.
8
19
  */
9
- export declare const localDataToSaveInvoicingCatalogItemPayload: (localData: InvoicingCatalogItemFormLocalData, isCreate: boolean) => Record<string, unknown>;
20
+ export declare const localDataToSaveInvoicingCatalogItemPayload: (localData: InvoicingCatalogItemFormLocalData, isCreate: boolean, lookup: InvoicingCatalogAccountingLookup) => Record<string, unknown>;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.localDataToSaveInvoicingCatalogItemPayload = void 0;
4
+ const accountPayload_1 = require("../../../entity/account/accountPayload");
5
+ const classPayload_1 = require("../../../entity/class/classPayload");
4
6
  const invoicingCatalogItemFormConfig_1 = require("./invoicingCatalogItemFormConfig");
5
7
  const CURRENCY_CODE = 'USD';
6
8
  const trimOrUndefined = (value) => value.trim() !== '' ? value.trim() : undefined;
@@ -10,20 +12,26 @@ const toProductCode = (value) => value
10
12
  .toLowerCase()
11
13
  .replace(/[^a-z0-9]+/g, '-')
12
14
  .replace(/^-|-$/g, '');
13
- const buildAccountingDetail = (subFamily) => {
15
+ const buildAccountingDetail = (subFamily, lookup) => {
14
16
  const detail = {};
15
17
  if (subFamily.sku.trim() !== '') {
16
18
  detail.sku = subFamily.sku.trim();
17
19
  }
18
- if (subFamily.accountingCode.trim() !== '') {
19
- detail.accounting_code = subFamily.accountingCode.trim();
20
+ // The account and class travel as whole objects rather than bare codes, built
21
+ // by the same helpers the transaction and vendor payloads use. An id that no
22
+ // longer resolves is dropped: sending a code the service cannot expand is how
23
+ // a mapping silently lands on the wrong account.
24
+ const account = lookup.accountByQboId(subFamily.accountingCode.trim());
25
+ if (account != null) {
26
+ detail.account = (0, accountPayload_1.toAccountPayload)(account);
20
27
  }
21
- if (subFamily.accountingCategory.trim() !== '') {
22
- detail.accounting_category_1 = subFamily.accountingCategory.trim();
28
+ const accountingClass = lookup.classByQboId(subFamily.accountingCategory.trim());
29
+ if (accountingClass != null) {
30
+ detail.accounting_class = (0, classPayload_1.toClassBasePayload)(accountingClass);
23
31
  }
24
32
  return Object.keys(detail).length > 0 ? detail : undefined;
25
33
  };
26
- const buildSubFamilyPayload = (localData, subFamily, clearEmptyValues) => {
34
+ const buildSubFamilyPayload = (localData, subFamily, clearEmptyValues, lookup) => {
27
35
  const isAddon = localData.catalogType === 'addon';
28
36
  const tiered = (0, invoicingCatalogItemFormConfig_1.isTieredCatalogPricingModel)(subFamily.pricingModel);
29
37
  const name = subFamily.name.trim();
@@ -77,7 +85,7 @@ const buildSubFamilyPayload = (localData, subFamily, clearEmptyValues) => {
77
85
  starting_unit: Number(tier.startingUnit) || 1,
78
86
  }));
79
87
  }
80
- const accountingDetail = buildAccountingDetail(subFamily);
88
+ const accountingDetail = buildAccountingDetail(subFamily, lookup);
81
89
  if (accountingDetail != null) {
82
90
  payload.accounting_detail = accountingDetail;
83
91
  }
@@ -89,7 +97,7 @@ const DEFAULT_CATALOG_ITEM_UNIT = 'USD';
89
97
  * Build the parent catalog-item request body from the form draft. Creates omit
90
98
  * both parent and sub-family IDs; edits retain returned sub-family IDs.
91
99
  */
92
- const localDataToSaveInvoicingCatalogItemPayload = (localData, isCreate) => {
100
+ const localDataToSaveInvoicingCatalogItemPayload = (localData, isCreate, lookup) => {
93
101
  const description = trimOrUndefined(localData.description);
94
102
  const productCode = trimOrUndefined(localData.productCode) ?? toProductCode(localData.name);
95
103
  const itemPayload = {
@@ -117,7 +125,7 @@ const localDataToSaveInvoicingCatalogItemPayload = (localData, isCreate) => {
117
125
  ? localData.subFamilies
118
126
  : [(0, invoicingCatalogItemFormConfig_1.blankInvoicingCatalogSubFamily)()];
119
127
  itemPayload.sub_family_details = isCreate
120
- ? subFamilies.map((subFamily) => buildSubFamilyPayload(localData, subFamily, false))
128
+ ? subFamilies.map((subFamily) => buildSubFamilyPayload(localData, subFamily, false, lookup))
121
129
  : subFamilies.map((subFamily) => {
122
130
  const priceCode = trimOrUndefined(subFamily.priceCode) ?? toProductCode(subFamily.name);
123
131
  const detail = {
@@ -127,6 +135,13 @@ const localDataToSaveInvoicingCatalogItemPayload = (localData, isCreate) => {
127
135
  price_code: priceCode,
128
136
  show_description_in_invoices: subFamily.showDescriptionInInvoices,
129
137
  };
138
+ // Edits send a narrower body than creates, but the accounting mapping
139
+ // is editable on this form, so leaving it out dropped the account and
140
+ // class the user had just picked.
141
+ const accountingDetail = buildAccountingDetail(subFamily, lookup);
142
+ if (accountingDetail != null) {
143
+ detail.accounting_detail = accountingDetail;
144
+ }
130
145
  const id = trimOrUndefined(subFamily.id);
131
146
  return id != null ? { ...detail, id } : detail;
132
147
  });
@@ -9,6 +9,7 @@ const snackbarApiError_1 = require("../../../entity/snackbar/snackbarApiError");
9
9
  const snackbarReducer_1 = require("../../../entity/snackbar/snackbarReducer");
10
10
  const responsePayload_1 = require("../../../responsePayload");
11
11
  const invoicingCatalogListViewReducer_1 = require("../invoicingCatalogListView/invoicingCatalogListViewReducer");
12
+ const invoicingQboViewSelector_1 = require("../invoicingQboView/invoicingQboViewSelector");
12
13
  const editInvoicingCatalogItemDetailViewPayload_1 = require("./editInvoicingCatalogItemDetailViewPayload");
13
14
  const editInvoicingCatalogItemDetailViewReducer_1 = require("./editInvoicingCatalogItemDetailViewReducer");
14
15
  const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)((action) => editInvoicingCatalogItemDetailViewReducer_1.submitInvoicingCatalogItemForm.match(action) ||
@@ -23,7 +24,11 @@ const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actions$.pip
23
24
  const viewState = state$.value.editInvoicingCatalogItemDetailViewState;
24
25
  const localData = (id != null ? viewState.editDraftById[id] : viewState.newItemDraft) ??
25
26
  viewState.newItemDraft;
26
- data = (0, editInvoicingCatalogItemDetailViewPayload_1.localDataToSaveInvoicingCatalogItemPayload)(localData, id == null);
27
+ const state = state$.value;
28
+ data = (0, editInvoicingCatalogItemDetailViewPayload_1.localDataToSaveInvoicingCatalogItemPayload)(localData, id == null, {
29
+ accountByQboId: (qboId) => (0, invoicingQboViewSelector_1.invoicingQboAccountByQboId)(state, qboId),
30
+ classByQboId: (qboId) => (0, invoicingQboViewSelector_1.invoicingQboClassByQboId)(state, qboId),
31
+ });
27
32
  }
28
33
  const baseUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0`;
29
34
  const path = id != null
@@ -1,7 +1,8 @@
1
1
  import { NestedAccountHierarchyForReport } from '../../../commonStateTypes/accountView/nestedAccountID';
2
2
  import { NestedClassHierarchyForReport } from '../../../commonStateTypes/classesView/nestedClassID';
3
3
  import { FetchStateAndError } from '../../../commonStateTypes/common';
4
- import { AccountType } from '../../../entity/account/accountState';
4
+ import { AccountBase, AccountType } from '../../../entity/account/accountState';
5
+ import { ClassBase } from '../../../entity/class/classState';
5
6
  import { RootState } from '../../../rootStateTypes';
6
7
  import { InvoicingQboConnection, InvoicingQboSyncHealth } from './invoicingQboViewPayload';
7
8
  export declare const INVOICING_ACCOUNT_LIST_KEY: "accountList";
@@ -20,6 +21,14 @@ export declare const getInvoicingQboConnectionFetchState: (state: RootState) =>
20
21
  * "income accounts" rather than repeating the accounting service's vocabulary.
21
22
  */
22
23
  export declare const INVOICING_INCOME_ACCOUNT_TYPES: readonly AccountType[];
24
+ /**
25
+ * The account behind a picker value. The pickers submit the QuickBooks id, so
26
+ * that is what callers hold; the payload builders need the whole entity to send
27
+ * `toAccountPayload`, which is why the lookup lives here next to the options.
28
+ */
29
+ export declare const invoicingQboAccountByQboId: (state: RootState, qboId: string | undefined) => AccountBase | undefined;
30
+ /** The class behind a picker value, for the same reason as the account above. */
31
+ export declare const invoicingQboClassByQboId: (state: RootState, qboId: string | undefined) => ClassBase | undefined;
23
32
  /** An account or class as the invoicing pickers render and submit it. */
24
33
  export interface InvoicingAccountingOption {
25
34
  id: string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getInvoicingQboView = exports.invoicingAccountHierarchyForTypes = exports.invoicingAccountsForQboAccountTypes = exports.INVOICING_INCOME_ACCOUNT_TYPES = exports.getInvoicingQboConnectionFetchState = exports.isInvoicingAccountingPickerDataLoading = exports.isInvoicingQboConnected = exports.INVOICING_ACCOUNT_LIST_KEY = void 0;
3
+ exports.getInvoicingQboView = exports.invoicingAccountHierarchyForTypes = exports.invoicingAccountsForQboAccountTypes = exports.invoicingQboClassByQboId = exports.invoicingQboAccountByQboId = exports.INVOICING_INCOME_ACCOUNT_TYPES = exports.getInvoicingQboConnectionFetchState = exports.isInvoicingAccountingPickerDataLoading = exports.isInvoicingQboConnected = exports.INVOICING_ACCOUNT_LIST_KEY = void 0;
4
4
  const accountListSelector_1 = require("../../accountList/accountListSelector");
5
5
  const classListSelector_1 = require("../../classList/classListSelector");
6
6
  // The shared bucket, fetched with the same arguments as the other consumers so
@@ -43,6 +43,28 @@ exports.INVOICING_INCOME_ACCOUNT_TYPES = [
43
43
  'income',
44
44
  'other_income',
45
45
  ];
46
+ /**
47
+ * The account behind a picker value. The pickers submit the QuickBooks id, so
48
+ * that is what callers hold; the payload builders need the whole entity to send
49
+ * `toAccountPayload`, which is why the lookup lives here next to the options.
50
+ */
51
+ const invoicingQboAccountByQboId = (state, qboId) => {
52
+ if (qboId == null || qboId === '') {
53
+ return undefined;
54
+ }
55
+ const { accounts } = (0, accountListSelector_1.getAccountList)(state.accountState, state.accountListState, exports.INVOICING_ACCOUNT_LIST_KEY);
56
+ return accounts.find((account) => String(account.qboId) === qboId);
57
+ };
58
+ exports.invoicingQboAccountByQboId = invoicingQboAccountByQboId;
59
+ /** The class behind a picker value, for the same reason as the account above. */
60
+ const invoicingQboClassByQboId = (state, qboId) => {
61
+ if (qboId == null || qboId === '') {
62
+ return undefined;
63
+ }
64
+ const { classes } = (0, classListSelector_1.getClassList)(state.classState, state.classListState);
65
+ return classes.find((accountingClass) => String(accountingClass.qboId) === qboId);
66
+ };
67
+ exports.invoicingQboClassByQboId = invoicingQboClassByQboId;
46
68
  /**
47
69
  * Accounts and classes as the invoicing pickers need them. Both come from the
48
70
  * shared accounting-service lists, so whatever another screen already loaded
@@ -9,4 +9,4 @@ export type ActionType = ReturnType<typeof fetchInvoicingSettings> | ReturnType<
9
9
  export declare const fetchInvoicingSettingsEpic: (actions$: ActionsObservable<ActionType>, _state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
10
10
  export declare const connectInvoicingStripeEpic: (actions$: ActionsObservable<ActionType>, _state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
11
11
  export declare const disconnectInvoicingStripeEpic: (actions$: ActionsObservable<ActionType>, _state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
12
- export declare const saveInvoicingSettingsEpic: (actions$: ActionsObservable<ActionType>, _state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
12
+ export declare const saveInvoicingSettingsEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
@@ -4,9 +4,11 @@ exports.saveInvoicingSettingsEpic = exports.disconnectInvoicingStripeEpic = expo
4
4
  const rxjs_1 = require("rxjs");
5
5
  const operators_1 = require("rxjs/operators");
6
6
  const addressReducer_1 = require("../../../entity/address/addressReducer");
7
+ const invoicingCommonPayload_1 = require("../../../entity/invoicing/invoicingCommonPayload");
7
8
  const snackbarApiError_1 = require("../../../entity/snackbar/snackbarApiError");
8
9
  const snackbarReducer_1 = require("../../../entity/snackbar/snackbarReducer");
9
10
  const responsePayload_1 = require("../../../responsePayload");
11
+ const invoicingQboViewSelector_1 = require("../invoicingQboView/invoicingQboViewSelector");
10
12
  const settingsViewReducer_1 = require("./settingsViewReducer");
11
13
  const fetchInvoicingSettingsEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(settingsViewReducer_1.fetchInvoicingSettings.match), (0, operators_1.switchMap)(() => {
12
14
  const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/settings`;
@@ -63,9 +65,22 @@ const disconnectInvoicingStripeEpic = (actions$, _state$, zeniAPI) => actions$.p
63
65
  JSON.stringify(error))))));
64
66
  }));
65
67
  exports.disconnectInvoicingStripeEpic = disconnectInvoicingStripeEpic;
66
- const saveInvoicingSettingsEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(settingsViewReducer_1.saveInvoicingSettings.match), (0, operators_1.mergeMap)((action) => {
68
+ const saveInvoicingSettingsEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(settingsViewReducer_1.saveInvoicingSettings.match), (0, operators_1.mergeMap)((action) => {
67
69
  const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/settings`;
68
70
  const { settings, snackbarSection } = action.payload;
71
+ // The QBO page edits mappings as picker ids; the service stores whole
72
+ // accounts. Expanded here, at the request boundary, so the page does not
73
+ // have to hold entities it only needs in order to save.
74
+ const qboSettings = settings.qbo;
75
+ const body = qboSettings?.account_mappings != null
76
+ ? {
77
+ ...settings,
78
+ qbo: {
79
+ ...qboSettings,
80
+ account_mappings: (0, invoicingCommonPayload_1.withInvoicingQboMappedAccounts)(qboSettings.account_mappings, (qboId) => (0, invoicingQboViewSelector_1.invoicingQboAccountByQboId)(state$.value, qboId)),
81
+ },
82
+ }
83
+ : settings;
69
84
  /** `status` surfaces the platform's error text; omit it to keep the generic copy. */
70
85
  const snackbar = (messageText, status) => snackbarSection == null
71
86
  ? []
@@ -78,7 +93,7 @@ const saveInvoicingSettingsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(
78
93
  }),
79
94
  ];
80
95
  return zeniAPI
81
- .putAndGetJSON(apiUrl, { ...settings })
96
+ .putAndGetJSON(apiUrl, { ...body })
82
97
  .pipe((0, operators_1.switchMap)((response) => {
83
98
  if ((0, responsePayload_1.isSuccessResponse)(response) && response.data != null) {
84
99
  return (0, rxjs_1.from)([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.2.8",
3
+ "version": "5.2.10-betaAK0",
4
4
  "description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/esm/index.js",