@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.
- package/lib/entity/invoicing/invoicingCommonPayload.d.ts +29 -17
- package/lib/entity/invoicing/invoicingCommonPayload.js +47 -10
- package/lib/entity/invoicing/product/productPayload.d.ts +10 -0
- package/lib/entity/invoicing/product/productPayload.js +14 -2
- package/lib/esm/entity/invoicing/invoicingCommonPayload.js +44 -9
- package/lib/esm/entity/invoicing/product/productPayload.js +14 -2
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +24 -9
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +6 -1
- package/lib/esm/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +20 -0
- package/lib/esm/view/invoicing/settingsView/settingsViewEpics.js +17 -2
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.d.ts +12 -1
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewPayload.js +24 -9
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +6 -1
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.d.ts +10 -1
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +23 -1
- package/lib/view/invoicing/settingsView/settingsViewEpics.d.ts +1 -1
- package/lib/view/invoicing/settingsView/settingsViewEpics.js +17 -2
- package/package.json +1 -1
|
@@ -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
|
|
104
|
-
* (`deposit_account
|
|
105
|
-
*
|
|
106
|
-
*
|
|
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?:
|
|
106
|
+
default_income_account?: Partial<AccountBasePayload> | null;
|
|
110
107
|
default_income_account_id?: string | null;
|
|
111
|
-
deposit_account?:
|
|
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?:
|
|
111
|
+
discounts_account?: Partial<AccountBasePayload> | null;
|
|
115
112
|
discounts_account_id?: string | null;
|
|
116
|
-
rounding_account?:
|
|
113
|
+
rounding_account?: Partial<AccountBasePayload> | null;
|
|
117
114
|
rounding_account_id?: string | null;
|
|
118
|
-
write_off_account?:
|
|
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
|
-
*
|
|
128
|
-
*
|
|
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
|
-
//
|
|
163
|
-
//
|
|
164
|
-
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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
|
-
//
|
|
138
|
-
//
|
|
139
|
-
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
});
|
package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js
CHANGED
|
@@ -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
|
-
|
|
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$,
|
|
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, { ...
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
});
|
package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js
CHANGED
|
@@ -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
|
-
|
|
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>,
|
|
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$,
|
|
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, { ...
|
|
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.
|
|
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",
|