@zeniai/client-epic-state 5.2.4 → 5.2.6
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/epic.js +14 -4
- package/lib/esm/epic.js +14 -4
- package/lib/esm/index.js +4 -3
- package/lib/esm/view/accountList/accountListReducer.js +1 -0
- package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +411 -9
- package/lib/esm/view/aiCfoView/aiCfoViewSelector.js +49 -0
- package/lib/esm/view/aiCfoView/epics/deleteSkillEpic.js +23 -0
- package/lib/esm/view/aiCfoView/epics/fetchSkillDetailEpic.js +35 -0
- package/lib/esm/view/aiCfoView/epics/fetchSkillVersionBodyEpic.js +45 -0
- package/lib/esm/view/aiCfoView/epics/fetchSkillVersionsEpic.js +37 -0
- package/lib/esm/view/aiCfoView/epics/fetchSkillsEpic.js +11 -6
- package/lib/esm/view/aiCfoView/epics/saveSkillEpic.js +72 -0
- package/lib/esm/view/aiCfoView/epics/setSkillVisibilityEpic.js +32 -0
- package/lib/esm/view/aiCfoView/epics/toSkillDetail.js +102 -0
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +8 -0
- package/lib/esm/view/invoicing/invoicingQboView/epics/fetchInvoicingAccountingPickersEpic.js +29 -0
- package/lib/esm/view/invoicing/invoicingQboView/invoicingQboViewPayload.js +0 -23
- package/lib/esm/view/invoicing/invoicingQboView/invoicingQboViewReducer.js +3 -40
- package/lib/esm/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +124 -6
- package/lib/index.d.ts +7 -5
- package/lib/index.js +31 -14
- package/lib/view/accountList/accountListReducer.js +1 -0
- package/lib/view/accountList/accountListState.d.ts +1 -1
- package/lib/view/aiCfoView/aiCfoViewPayload.d.ts +85 -0
- package/lib/view/aiCfoView/aiCfoViewReducer.d.ts +67 -2
- package/lib/view/aiCfoView/aiCfoViewReducer.js +413 -10
- package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +7 -1
- package/lib/view/aiCfoView/aiCfoViewSelector.js +54 -0
- package/lib/view/aiCfoView/aiCfoViewState.d.ts +143 -1
- package/lib/view/aiCfoView/epics/deleteSkillEpic.d.ts +8 -0
- package/lib/view/aiCfoView/epics/deleteSkillEpic.js +27 -0
- package/lib/view/aiCfoView/epics/fetchSkillDetailEpic.d.ts +8 -0
- package/lib/view/aiCfoView/epics/fetchSkillDetailEpic.js +39 -0
- package/lib/view/aiCfoView/epics/fetchSkillVersionBodyEpic.d.ts +13 -0
- package/lib/view/aiCfoView/epics/fetchSkillVersionBodyEpic.js +49 -0
- package/lib/view/aiCfoView/epics/fetchSkillVersionsEpic.d.ts +8 -0
- package/lib/view/aiCfoView/epics/fetchSkillVersionsEpic.js +41 -0
- package/lib/view/aiCfoView/epics/fetchSkillsEpic.js +11 -6
- package/lib/view/aiCfoView/epics/saveSkillEpic.d.ts +14 -0
- package/lib/view/aiCfoView/epics/saveSkillEpic.js +76 -0
- package/lib/view/aiCfoView/epics/setSkillVisibilityEpic.d.ts +14 -0
- package/lib/view/aiCfoView/epics/setSkillVisibilityEpic.js +36 -0
- package/lib/view/aiCfoView/epics/toSkillDetail.d.ts +88 -0
- package/lib/view/aiCfoView/epics/toSkillDetail.js +110 -0
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.d.ts +9 -0
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +8 -0
- package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingAccountingPickersEpic.d.ts +5 -0
- package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingAccountingPickersEpic.js +33 -0
- package/lib/view/invoicing/invoicingQboView/epics/invoicingQboViewActionType.d.ts +4 -2
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewPayload.d.ts +0 -26
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewPayload.js +0 -24
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewReducer.d.ts +2 -10
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewReducer.js +4 -41
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.d.ts +43 -6
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewSelector.js +128 -7
- package/lib/view/invoicing/invoicingQboView/invoicingQboViewState.d.ts +1 -5
- package/lib/view/taskManager/taskListView/taskList.d.ts +1 -1
- package/package.json +1 -1
- package/lib/esm/view/invoicing/invoicingQboView/epics/fetchInvoicingQboAccountsEpic.js +0 -25
- package/lib/esm/view/invoicing/invoicingQboView/epics/fetchInvoicingQboClassesEpic.js +0 -15
- package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingQboAccountsEpic.d.ts +0 -6
- package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingQboAccountsEpic.js +0 -29
- package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingQboClassesEpic.d.ts +0 -6
- package/lib/view/invoicing/invoicingQboView/epics/fetchInvoicingQboClassesEpic.js +0 -19
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { of } from 'rxjs';
|
|
2
|
+
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
3
|
+
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
4
|
+
import { fetchSkillVersions, fetchSkillVersionsFailure, fetchSkillVersionsSuccess, } from '../aiCfoViewReducer';
|
|
5
|
+
const toVersion = (payload) => ({
|
|
6
|
+
version: payload.version,
|
|
7
|
+
semver: payload.semver ?? null,
|
|
8
|
+
displayName: payload.display_name ?? null,
|
|
9
|
+
updatedAt: payload.updated_at ?? null,
|
|
10
|
+
// Defaulted defensively: these are booleans the server always sends, but a malformed
|
|
11
|
+
// 2xx should render a row as inactive-and-not-latest rather than as the live one.
|
|
12
|
+
isActive: payload.is_active === true,
|
|
13
|
+
isLatest: payload.is_latest === true,
|
|
14
|
+
instructionsCharCount: payload.instructions_char_count ?? 0,
|
|
15
|
+
});
|
|
16
|
+
/** GET /1.0/skills/{macroId}/versions — the history panel. */
|
|
17
|
+
export const fetchSkillVersionsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchSkillVersions.match),
|
|
18
|
+
// switchMap: a read, and only the skill currently open matters. The reducer also
|
|
19
|
+
// drops responses whose macroId no longer matches, so a cancelled-but-in-flight
|
|
20
|
+
// request cannot render one skill's history under another's title.
|
|
21
|
+
switchMap((action) => {
|
|
22
|
+
const { macroId } = action.payload;
|
|
23
|
+
const url = `${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/skills/${encodeURIComponent(macroId)}/versions`;
|
|
24
|
+
return zeniAPI.getJSON(url).pipe(mergeMap((response) => {
|
|
25
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
26
|
+
return of(fetchSkillVersionsSuccess({
|
|
27
|
+
macroId,
|
|
28
|
+
// Guard the array: a malformed 2xx without `versions` must not crash .map.
|
|
29
|
+
versions: (response.data.versions ?? []).map(toVersion),
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
return of(fetchSkillVersionsFailure({ macroId, error: response.status }));
|
|
33
|
+
}), catchError((error) => of(fetchSkillVersionsFailure({
|
|
34
|
+
macroId,
|
|
35
|
+
error: createZeniAPIStatus('Unexpected Error', 'Fetch skill versions REST API call failed', error instanceof Error ? { message: error.message } : undefined),
|
|
36
|
+
}))));
|
|
37
|
+
}));
|
|
@@ -2,6 +2,7 @@ import { of } from 'rxjs';
|
|
|
2
2
|
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
3
3
|
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
4
4
|
import { fetchSkills, fetchSkillsFailure, fetchSkillsSuccess, } from '../aiCfoViewReducer';
|
|
5
|
+
import { toBaseSkill } from './toSkillDetail';
|
|
5
6
|
const isNonBlankString = (value) => typeof value === 'string' && value.trim() !== '';
|
|
6
7
|
// parameter_hints is a free-form {param: exampleValue(s)} map; flatten its non-blank
|
|
7
8
|
// string values (a single string or an array of them) into display-ready "try: …"
|
|
@@ -27,14 +28,12 @@ const toExampleHints = (parameterHints) => {
|
|
|
27
28
|
// a broken duplicate to the user — dedupe across parameter keys and array entries.
|
|
28
29
|
return Array.from(new Set(hints));
|
|
29
30
|
};
|
|
31
|
+
// Only the LIST route returns parameter_hints, so this is the one call site that has
|
|
32
|
+
// hints to spread in. Everything else about a summary comes from the shared base mapper,
|
|
33
|
+
// so a new `Skill` field cannot land here and be missed on the detail path.
|
|
30
34
|
const toSkill = (skillSummaryPayload) => ({
|
|
31
|
-
|
|
32
|
-
name: skillSummaryPayload.name,
|
|
33
|
-
displayName: skillSummaryPayload.display_name,
|
|
34
|
-
description: skillSummaryPayload.description,
|
|
35
|
-
semver: skillSummaryPayload.semver,
|
|
35
|
+
...toBaseSkill(skillSummaryPayload),
|
|
36
36
|
exampleHints: toExampleHints(skillSummaryPayload.parameter_hints),
|
|
37
|
-
hasScripts: skillSummaryPayload.has_scripts,
|
|
38
37
|
});
|
|
39
38
|
/** GET {aiCfoMicroServiceBaseUrl}/1.0/skills — chat-visible skills for the `/` menu. */
|
|
40
39
|
export const fetchSkillsEpic = (actions$,
|
|
@@ -52,6 +51,12 @@ switchMap(() => {
|
|
|
52
51
|
// Guard the array: a malformed 2xx body without `skills` must not crash .map.
|
|
53
52
|
return of(fetchSkillsSuccess({
|
|
54
53
|
skills: (response.data.skills ?? []).map(toSkill),
|
|
54
|
+
// === true, not a truthy check: a body missing the field entirely
|
|
55
|
+
// must read as "off" so the Skills entry stays hidden by default.
|
|
56
|
+
skillsEnabled: response.data.skills_enabled === true,
|
|
57
|
+
// Same discipline: a body without the field must withhold the publish
|
|
58
|
+
// control, not grant it.
|
|
59
|
+
canPublish: response.data.can_publish === true,
|
|
55
60
|
}));
|
|
56
61
|
}
|
|
57
62
|
return of(fetchSkillsFailure(response.status));
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { of } from 'rxjs';
|
|
2
|
+
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
|
+
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
4
|
+
import { fetchSkillVersions, fetchSkills, saveSkill, saveSkillFailure, saveSkillSuccess, } from '../aiCfoViewReducer';
|
|
5
|
+
import { getSkillDetail } from '../aiCfoViewSelector';
|
|
6
|
+
import { isSkillDetailPayload, toSkillDetail } from './toSkillDetail';
|
|
7
|
+
/**
|
|
8
|
+
* POST /1.0/skills (create) or PUT /1.0/skills/{macroId} (new version).
|
|
9
|
+
*
|
|
10
|
+
* One epic for both because they are the same action to a user — "save what I wrote" —
|
|
11
|
+
* and the server answers both with the detail payload. Splitting them would duplicate
|
|
12
|
+
* the payload mapping and the error handling for a difference the form does not have.
|
|
13
|
+
*/
|
|
14
|
+
export const saveSkillEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(saveSkill.match),
|
|
15
|
+
// mergeMap, NOT switchMap: a save is a write. Cancelling an in-flight one because
|
|
16
|
+
// another started does not un-send it, it just hides the outcome — and for a
|
|
17
|
+
// create that means a skill exists that the user was never told about.
|
|
18
|
+
mergeMap((action) => {
|
|
19
|
+
const { draft, macroId } = action.payload;
|
|
20
|
+
const base = `${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/skills`;
|
|
21
|
+
const body = {
|
|
22
|
+
name: draft.name,
|
|
23
|
+
instructions: draft.instructions,
|
|
24
|
+
description: draft.description,
|
|
25
|
+
// Sent as the menu name too. The server derives the unique `/` slug from it, so
|
|
26
|
+
// letting the two drift would make the command stop matching the row's title.
|
|
27
|
+
display_name: draft.name,
|
|
28
|
+
// Spread, not `visibility: draft.visibility`: the server distinguishes an ABSENT
|
|
29
|
+
// visibility ("leave it alone") from an explicit null or 'private' ("make it
|
|
30
|
+
// private"). Sending the key with a null value would unpublish a tenant-wide
|
|
31
|
+
// skill on any edit by someone who cannot publish.
|
|
32
|
+
//
|
|
33
|
+
// `!= null`, not `!== null`: the type says this is always present, but the value
|
|
34
|
+
// arrives from a component, and an untyped caller omitting it would spread
|
|
35
|
+
// `{visibility: undefined}` — a key present with no value, which is the one shape
|
|
36
|
+
// the rule above is about. The looser check costs nothing and closes it.
|
|
37
|
+
...(draft.visibility != null && { visibility: draft.visibility }),
|
|
38
|
+
};
|
|
39
|
+
const request$ = macroId != null && macroId !== ''
|
|
40
|
+
? zeniAPI.putAndGetJSON(`${base}/${encodeURIComponent(macroId)}`, body)
|
|
41
|
+
: zeniAPI.postAndGetJSON(base, body);
|
|
42
|
+
return request$.pipe(mergeMap((response) => {
|
|
43
|
+
if (isSuccessResponse(response) &&
|
|
44
|
+
isSkillDetailPayload(response.data)) {
|
|
45
|
+
const saved = response.data;
|
|
46
|
+
const savedMacroId = saved.skill.macro_id;
|
|
47
|
+
const emitted = [
|
|
48
|
+
saveSkillSuccess({
|
|
49
|
+
macroId: savedMacroId,
|
|
50
|
+
// The response is a full detail read — carried through so an editor left
|
|
51
|
+
// on this skill shows what it just saved rather than the previous prompt.
|
|
52
|
+
detail: toSkillDetail(saved),
|
|
53
|
+
}),
|
|
54
|
+
// Refetch rather than splice the response in: a create changes the list's
|
|
55
|
+
// ordering and an update can change the name it is sorted by, so a local
|
|
56
|
+
// insert would put the row somewhere the next load moves it away from.
|
|
57
|
+
fetchSkills(),
|
|
58
|
+
];
|
|
59
|
+
// A save adds a version, so the history panel is one behind — but only
|
|
60
|
+
// refetch when this skill is still the one on screen. `fetchSkillVersions`
|
|
61
|
+
// takes ownership of the versions slice, so firing it for a skill the user
|
|
62
|
+
// has already left would cancel the history of whatever they opened next.
|
|
63
|
+
if (getSkillDetail(state$.value).macroId === savedMacroId) {
|
|
64
|
+
emitted.push(fetchSkillVersions({ macroId: savedMacroId }));
|
|
65
|
+
}
|
|
66
|
+
return of(...emitted);
|
|
67
|
+
}
|
|
68
|
+
// A malformed 2xx is a failure, not a silent success: reporting "saved" when
|
|
69
|
+
// no skill came back leaves the user believing work was persisted.
|
|
70
|
+
return of(saveSkillFailure(response.status));
|
|
71
|
+
}), catchError((error) => of(saveSkillFailure(createZeniAPIStatus('Unexpected Error', 'Save skill REST API call failed', error instanceof Error ? { message: error.message } : undefined)))));
|
|
72
|
+
}));
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { of } from 'rxjs';
|
|
2
|
+
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
|
+
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
4
|
+
import { fetchSkills, setSkillVisibility, setSkillVisibilityFailure, setSkillVisibilitySuccess, } from '../aiCfoViewReducer';
|
|
5
|
+
import { isSkillDetailPayload, toSkillDetail } from './toSkillDetail';
|
|
6
|
+
/**
|
|
7
|
+
* PUT /1.0/skills/{macroId}/visibility — publish or unpublish, without a new version.
|
|
8
|
+
*
|
|
9
|
+
* Its own route rather than the prompt PUT: that one requires name and instructions, so a
|
|
10
|
+
* visibility-only change cannot be expressed through it, and sending the whole prompt back
|
|
11
|
+
* to flip one field would create a version whose prompt is identical to its parent's.
|
|
12
|
+
*/
|
|
13
|
+
export const setSkillVisibilityEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(setSkillVisibility.match),
|
|
14
|
+
// mergeMap, like the other writes: cancelling a write in flight does not un-send it,
|
|
15
|
+
// it only hides the outcome — and for a publish that means a skill sitting in every
|
|
16
|
+
// colleague's menu with nobody told it got there.
|
|
17
|
+
mergeMap((action) => {
|
|
18
|
+
const { macroId, visibility } = action.payload;
|
|
19
|
+
const url = `${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/skills/${encodeURIComponent(macroId)}/visibility`;
|
|
20
|
+
return zeniAPI.putAndGetJSON(url, { visibility }).pipe(mergeMap((response) => {
|
|
21
|
+
if (isSuccessResponse(response) &&
|
|
22
|
+
isSkillDetailPayload(response.data)) {
|
|
23
|
+
return of(setSkillVisibilitySuccess({
|
|
24
|
+
macroId,
|
|
25
|
+
detail: toSkillDetail(response.data),
|
|
26
|
+
}),
|
|
27
|
+
// The list carries the badge that shows the audience, so it is now stale.
|
|
28
|
+
fetchSkills());
|
|
29
|
+
}
|
|
30
|
+
return of(setSkillVisibilityFailure(response.status));
|
|
31
|
+
}), catchError((error) => of(setSkillVisibilityFailure(createZeniAPIStatus('Unexpected Error', 'Set skill visibility REST API call failed', error instanceof Error ? { message: error.message } : undefined)))));
|
|
32
|
+
}));
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every value `SkillVisibility` admits, as data so the converter below cannot drift from
|
|
3
|
+
* the type. Adding a tier to the union without adding it here is a compile error.
|
|
4
|
+
*/
|
|
5
|
+
const SKILL_VISIBILITIES = ['private', 'tenant'];
|
|
6
|
+
/**
|
|
7
|
+
* A wire string narrowed to {@link SkillVisibility}, or null.
|
|
8
|
+
*
|
|
9
|
+
* A converter rather than passing the raw string through, because `Skill.visibility` is a
|
|
10
|
+
* union on the state type and this is the boundary where an unvalidated payload becomes
|
|
11
|
+
* it — the repo's rule about explicit converters at scalar boundaries instead of a bare
|
|
12
|
+
* cast.
|
|
13
|
+
*
|
|
14
|
+
* Unrecognised maps to NULL, not to `'private'`. Null already means "does not apply" for a
|
|
15
|
+
* Zeni default, and consumers show no audience badge for it — so an unknown tier renders
|
|
16
|
+
* as no badge. Defaulting to `'private'` would instead label a skill the whole tenant can
|
|
17
|
+
* run as author-only, which is a confident lie rather than a visible gap.
|
|
18
|
+
*/
|
|
19
|
+
export const toSkillVisibility = (raw) => SKILL_VISIBILITIES.find((known) => known === raw) ?? null;
|
|
20
|
+
/**
|
|
21
|
+
* Everything about a skill summary EXCEPT `exampleHints`, which no shared mapper can
|
|
22
|
+
* honestly supply.
|
|
23
|
+
*
|
|
24
|
+
* Only the LIST route returns `parameter_hints`; the detail and write routes do not. A
|
|
25
|
+
* shared mapper hardcoding `exampleHints: []` would put a "try: …" line under a skill
|
|
26
|
+
* that has hints, or drop them from the menu — so the field is `Omit`ted and each caller
|
|
27
|
+
* spreads in what its own route actually knows.
|
|
28
|
+
*
|
|
29
|
+
* Returning `Omit<Skill, 'exampleHints'>` rather than duplicating the mapper is what
|
|
30
|
+
* makes a new `Skill` field land in one place: this function stops compiling until the
|
|
31
|
+
* field is mapped, and both callers inherit it. The previous shape — two full copies —
|
|
32
|
+
* is how `isMine` was nearly added to only one of them, which would have silently
|
|
33
|
+
* withheld the edit and delete controls on whichever surface was missed.
|
|
34
|
+
*/
|
|
35
|
+
export const toBaseSkill = (payload) => ({
|
|
36
|
+
macroId: payload.macro_id,
|
|
37
|
+
name: payload.name,
|
|
38
|
+
displayName: payload.display_name,
|
|
39
|
+
description: payload.description,
|
|
40
|
+
semver: payload.semver,
|
|
41
|
+
hasScripts: payload.has_scripts === true,
|
|
42
|
+
// === true, so an absent field withholds the write controls rather than offering ones
|
|
43
|
+
// the API refuses.
|
|
44
|
+
isMine: payload.is_mine === true,
|
|
45
|
+
isOwnedByTenant: payload.is_owned_by_tenant === true,
|
|
46
|
+
updatedAt: payload.updated_at ?? null,
|
|
47
|
+
visibility: toSkillVisibility(payload.visibility),
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Whether a 2xx body carries a prompt.
|
|
51
|
+
*
|
|
52
|
+
* Narrows to {@link PromptBody} and NOT to the whole payload, because the only consumer —
|
|
53
|
+
* the version-body read — dereferences `instructions` and nothing else. Asserting the
|
|
54
|
+
* whole shape here would claim `skill` was checked when it was not, and tying the read to
|
|
55
|
+
* it would fail a response whose prompt was fine and whose `skill` block was malformed,
|
|
56
|
+
* which is exactly what happened when this was one predicate instead of two.
|
|
57
|
+
*
|
|
58
|
+
* Takes `unknown`: the input is an unvalidated network body, and typing the parameter as
|
|
59
|
+
* the payload interface would assume the very thing being checked.
|
|
60
|
+
*/
|
|
61
|
+
export const hasPromptBody = (data) => typeof data?.instructions ===
|
|
62
|
+
'string';
|
|
63
|
+
/**
|
|
64
|
+
* Whether a 2xx body can build a whole {@link SkillDetail}.
|
|
65
|
+
*
|
|
66
|
+
* Narrows to the payload type itself, which is now truthful: the two fields this checks
|
|
67
|
+
* are the two the interface still declares required, and the other three are optional
|
|
68
|
+
* there. Stricter than {@link hasPromptBody} by exactly one field. Four epics read this shape
|
|
69
|
+
* and each used to guard it differently — `data != null`, `data?.skill != null`, and a
|
|
70
|
+
* real `instructions` check — so one malformed response became a failure in one place, an
|
|
71
|
+
* empty prompt in another, and `undefined` in a third.
|
|
72
|
+
*/
|
|
73
|
+
export const isSkillDetailPayload = (data) => hasPromptBody(data) && data.skill != null;
|
|
74
|
+
/**
|
|
75
|
+
* The detail payload every write route answers with.
|
|
76
|
+
*
|
|
77
|
+
* Shared because three epics need it — save, visibility, and the detail read — and a
|
|
78
|
+
* fourth copy is how one of them quietly stops carrying a field the others do.
|
|
79
|
+
*
|
|
80
|
+
* The coercions are not defensive habit; the payload type makes them mandatory — the
|
|
81
|
+
* three optional fields cannot be assigned to a non-optional `SkillDetail` without one.
|
|
82
|
+
* Which fields get coerced and which do not is the deliberate part:
|
|
83
|
+
*
|
|
84
|
+
* - `instructions` is load-bearing and already proven, so it passes through. Defaulting a
|
|
85
|
+
* missing prompt to `''` is the one thing this module exists to prevent: the user
|
|
86
|
+
* cannot tell an empty prompt from a skill that has one, and may save over the real
|
|
87
|
+
* thing.
|
|
88
|
+
* - `instructionsCharCount` falls back to the prompt's own length, NOT to 0. A 12,000
|
|
89
|
+
* character prompt displayed as "0 characters" is a lie the user can see, and the
|
|
90
|
+
* prompt is right here to measure. Approximate only for astral-plane characters, where
|
|
91
|
+
* UTF-16 units and the server's count differ — a rounding error, against a guaranteed
|
|
92
|
+
* wrong answer.
|
|
93
|
+
* - `artifact` and `artifactTitle` degrade to empty. They render a preview card, so
|
|
94
|
+
* losing them costs a card; failing the whole read would cost the user the skill.
|
|
95
|
+
*/
|
|
96
|
+
export const toSkillDetail = (payload) => ({
|
|
97
|
+
skill: { ...toBaseSkill(payload.skill), exampleHints: [] },
|
|
98
|
+
instructions: payload.instructions,
|
|
99
|
+
instructionsCharCount: payload.instructions_char_count ?? payload.instructions.length,
|
|
100
|
+
artifact: payload.artifact ?? '',
|
|
101
|
+
artifactTitle: payload.artifact_title ?? '',
|
|
102
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createSelector } from '@reduxjs/toolkit';
|
|
2
2
|
import { getInvoicingPlanById, getInvoicingPlansByIds, } from '../../../entity/invoicing/plan/planSelector';
|
|
3
3
|
import { getInvoicingConfigView, invoicingFieldHelpForGroup, } from '../invoicingConfigView/invoicingConfigViewSelector';
|
|
4
|
+
import { INVOICING_INCOME_ACCOUNT_TYPES, getInvoicingQboView, invoicingAccountHierarchyForTypes, isInvoicingQboConnected, } from '../invoicingQboView/invoicingQboViewSelector';
|
|
4
5
|
import { INVOICING_CATALOG_APPLICABILITY_VALUES, INVOICING_CATALOG_PRICING_MODEL_VALUES, INVOICING_CATALOG_PRORATION_VALUES, INVOICING_CATALOG_TIER_TYPE_VALUES, entityToInvoicingCatalogItemFormLocalData, } from './invoicingCatalogItemFormConfig';
|
|
5
6
|
export const getEditInvoicingCatalogItemDetailViewState = (state) => ({
|
|
6
7
|
fetchState: state.editInvoicingCatalogItemDetailViewState.fetchState,
|
|
@@ -48,9 +49,16 @@ export const getInvoicingCatalogItemFormView = (state, id) => {
|
|
|
48
49
|
const applicableItemOptions = id != null
|
|
49
50
|
? allApplicableItemOptions.filter((option) => option.id !== id)
|
|
50
51
|
: allApplicableItemOptions;
|
|
52
|
+
const isQboConnected = isInvoicingQboConnected(state);
|
|
51
53
|
const { billingPeriodUnitOptions, catalogItemTypeOptions, catalogTierTypeOptionsByPricingModel, catalogTrialEndActionOptions, catalogTrialUnitOptions, } = getInvoicingConfigView(state);
|
|
52
54
|
return {
|
|
53
55
|
fieldHelp: invoicingFieldHelpForGroup(state, 'catalog'),
|
|
56
|
+
qboClassHierarchy: isQboConnected
|
|
57
|
+
? getInvoicingQboView(state).nestedClassHierarchy
|
|
58
|
+
: [],
|
|
59
|
+
qboIncomeAccountHierarchy: isQboConnected
|
|
60
|
+
? invoicingAccountHierarchyForTypes(state, INVOICING_INCOME_ACCOUNT_TYPES)
|
|
61
|
+
: [],
|
|
54
62
|
applicabilityValues: INVOICING_CATALOG_APPLICABILITY_VALUES,
|
|
55
63
|
applicableItemOptions,
|
|
56
64
|
localData,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { EMPTY, from } from 'rxjs';
|
|
2
|
+
import { filter, mergeMap, withLatestFrom } from 'rxjs/operators';
|
|
3
|
+
import { fetchAccountList } from '../../../accountList/accountListReducer';
|
|
4
|
+
import { fetchClassList } from '../../../classList/classListReducer';
|
|
5
|
+
import { fetchInvoicingAccountingPickers } from '../invoicingQboViewReducer';
|
|
6
|
+
import { INVOICING_ACCOUNT_LIST_KEY } from '../invoicingQboViewSelector';
|
|
7
|
+
// Tops up the shared account and class lists, skipping either when the store
|
|
8
|
+
// already holds it or has it in flight.
|
|
9
|
+
export const fetchInvoicingAccountingPickersEpic = (actions$, state$) => actions$.pipe(filter(fetchInvoicingAccountingPickers.match), withLatestFrom(state$), mergeMap(([, state]) => {
|
|
10
|
+
const actions = [];
|
|
11
|
+
const accountList = state.accountListState.byReportId[INVOICING_ACCOUNT_LIST_KEY];
|
|
12
|
+
if (accountList.hasValidState() === false &&
|
|
13
|
+
accountList.fetchState !== 'In-Progress') {
|
|
14
|
+
// AP/AR accounts cannot back an invoicing mapping, so they are excluded
|
|
15
|
+
// here — which is why this uses invoicing's own bucket rather than the
|
|
16
|
+
// general `accountList` other screens fill without those exclusions.
|
|
17
|
+
actions.push(fetchAccountList(INVOICING_ACCOUNT_LIST_KEY, true, true, true));
|
|
18
|
+
}
|
|
19
|
+
const classList = state.classListState;
|
|
20
|
+
if (classList.hasValidState() === false &&
|
|
21
|
+
classList.fetchState !== 'In-Progress') {
|
|
22
|
+
// Without deleted classes, matching almost every other caller of this
|
|
23
|
+
// single global slice. Asking for deleted rows here would have handed
|
|
24
|
+
// them to the vendor and transaction screens too — and the picker
|
|
25
|
+
// filters deleted classes out anyway, so there is nothing to gain.
|
|
26
|
+
actions.push(fetchClassList());
|
|
27
|
+
}
|
|
28
|
+
return actions.length > 0 ? from(actions) : EMPTY;
|
|
29
|
+
}));
|
|
@@ -6,29 +6,6 @@ export function mapInvoicingQboConnection(payload) {
|
|
|
6
6
|
realmId: payload.realm_id ?? undefined,
|
|
7
7
|
};
|
|
8
8
|
}
|
|
9
|
-
export function mapInvoicingQboAccountOptions(rows) {
|
|
10
|
-
return rows.flatMap((row) => {
|
|
11
|
-
// Only qbo_id is a valid QBO AccountRef/ClassRef value — platform-internal
|
|
12
|
-
// ids (account_id / id) are display metadata and must never be submitted.
|
|
13
|
-
const accountId = row.qbo_id ?? row.id ?? row.account_id ?? '';
|
|
14
|
-
if (accountId === '') {
|
|
15
|
-
return [];
|
|
16
|
-
}
|
|
17
|
-
const accountName = row.name ??
|
|
18
|
-
row.account_name ??
|
|
19
|
-
row.accounting_class_name ??
|
|
20
|
-
row.qbo_accounting_class_name ??
|
|
21
|
-
row.fully_qualified_name ??
|
|
22
|
-
accountId;
|
|
23
|
-
return [
|
|
24
|
-
{
|
|
25
|
-
id: accountId,
|
|
26
|
-
name: accountName,
|
|
27
|
-
accountType: row.account_type ?? undefined,
|
|
28
|
-
},
|
|
29
|
-
];
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
9
|
export function mapInvoicingQboSyncHealth(payload) {
|
|
33
10
|
return {
|
|
34
11
|
connection: payload.connection != null
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { createSlice } from '@reduxjs/toolkit';
|
|
2
2
|
import { clearAll } from '../../../rootActions';
|
|
3
3
|
export const initialState = {
|
|
4
|
-
accountsByAccountTypes: {},
|
|
5
|
-
accountsFetchStateByAccountTypes: {},
|
|
6
4
|
backfillFetchState: { fetchState: 'Not-Started', error: undefined },
|
|
7
|
-
classes: [],
|
|
8
|
-
classesFetchState: { fetchState: 'Not-Started', error: undefined },
|
|
9
5
|
connection: undefined,
|
|
10
6
|
connectionFetchState: { fetchState: 'Not-Started', error: undefined },
|
|
11
7
|
retryFetchStateByTaskId: {},
|
|
@@ -29,41 +25,8 @@ const invoicingQboView = createSlice({
|
|
|
29
25
|
updateInvoicingQboConnectionFailure(draft, action) {
|
|
30
26
|
draft.connectionFetchState = { fetchState: 'Error', error: action.payload };
|
|
31
27
|
},
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
draft.accountsFetchStateByAccountTypes[action.payload.accountTypes] = {
|
|
35
|
-
fetchState: 'In-Progress',
|
|
36
|
-
error: undefined,
|
|
37
|
-
};
|
|
38
|
-
},
|
|
39
|
-
prepare(accountTypes) {
|
|
40
|
-
return { payload: { accountTypes } };
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
updateInvoicingQboAccounts(draft, action) {
|
|
44
|
-
draft.accountsByAccountTypes[action.payload.accountTypes] =
|
|
45
|
-
action.payload.accounts;
|
|
46
|
-
draft.accountsFetchStateByAccountTypes[action.payload.accountTypes] = {
|
|
47
|
-
fetchState: 'Completed',
|
|
48
|
-
error: undefined,
|
|
49
|
-
};
|
|
50
|
-
},
|
|
51
|
-
updateInvoicingQboAccountsFailure(draft, action) {
|
|
52
|
-
draft.accountsFetchStateByAccountTypes[action.payload.accountTypes] = {
|
|
53
|
-
fetchState: 'Error',
|
|
54
|
-
error: action.payload.status,
|
|
55
|
-
};
|
|
56
|
-
},
|
|
57
|
-
fetchInvoicingQboClasses(draft) {
|
|
58
|
-
draft.classesFetchState = { fetchState: 'In-Progress', error: undefined };
|
|
59
|
-
},
|
|
60
|
-
updateInvoicingQboClasses(draft, action) {
|
|
61
|
-
draft.classes = action.payload;
|
|
62
|
-
draft.classesFetchState = { fetchState: 'Completed', error: undefined };
|
|
63
|
-
},
|
|
64
|
-
updateInvoicingQboClassesFailure(draft, action) {
|
|
65
|
-
draft.classesFetchState = { fetchState: 'Error', error: action.payload };
|
|
66
|
-
},
|
|
28
|
+
// No state of its own: the epic decides which shared lists to load.
|
|
29
|
+
fetchInvoicingAccountingPickers() { },
|
|
67
30
|
fetchInvoicingQboSyncHealth(draft) {
|
|
68
31
|
draft.syncHealthFetchState = {
|
|
69
32
|
fetchState: 'In-Progress',
|
|
@@ -127,5 +90,5 @@ const invoicingQboView = createSlice({
|
|
|
127
90
|
});
|
|
128
91
|
},
|
|
129
92
|
});
|
|
130
|
-
export const { fetchInvoicingQboConnection, updateInvoicingQboConnection, updateInvoicingQboConnectionFailure,
|
|
93
|
+
export const { fetchInvoicingQboConnection, updateInvoicingQboConnection, updateInvoicingQboConnectionFailure, fetchInvoicingAccountingPickers, fetchInvoicingQboSyncHealth, updateInvoicingQboSyncHealth, updateInvoicingQboSyncHealthFailure, retryInvoicingQboSyncTask, retryInvoicingQboSyncTaskSuccess, retryInvoicingQboSyncTaskFailure, startInvoicingQboBackfill, startInvoicingQboBackfillSuccess, startInvoicingQboBackfillFailure, } = invoicingQboView.actions;
|
|
131
94
|
export default invoicingQboView.reducer;
|
|
@@ -1,16 +1,134 @@
|
|
|
1
|
+
import { getAccountList, getNestedAccountListHierarchy, } from '../../accountList/accountListSelector';
|
|
2
|
+
import { getClassList, getNestedClassListHierarchy, } from '../../classList/classListSelector';
|
|
3
|
+
// Its own bucket: invoicing needs the AP/AR exclusions that the vendor and
|
|
4
|
+
// transaction screens sharing `accountList` do not pass.
|
|
5
|
+
export const INVOICING_ACCOUNT_LIST_KEY = 'invoicingAccountList';
|
|
6
|
+
/** Whether QBO is connected, without building the option lists to find out. */
|
|
7
|
+
export const isInvoicingQboConnected = (state) => state.invoicingQboViewState.connection?.connected === true;
|
|
8
|
+
/**
|
|
9
|
+
* Account types that can back invoicing revenue. Named here so screens ask for
|
|
10
|
+
* "income accounts" rather than repeating the accounting service's vocabulary.
|
|
11
|
+
*/
|
|
12
|
+
export const INVOICING_INCOME_ACCOUNT_TYPES = [
|
|
13
|
+
'income',
|
|
14
|
+
'other_income',
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Accounts and classes as the invoicing pickers need them. Both come from the
|
|
18
|
+
* shared accounting-service lists, so whatever another screen already loaded
|
|
19
|
+
* is reused. `qboId` is the submitted value because that is what the invoicing
|
|
20
|
+
* API stores; an account without one cannot be posted, so it is dropped rather
|
|
21
|
+
* than offered.
|
|
22
|
+
*/
|
|
23
|
+
const invoicingAccountingOptions = (state, accountTypes) => {
|
|
24
|
+
const { accounts } = getAccountList(state.accountState, state.accountListState, INVOICING_ACCOUNT_LIST_KEY);
|
|
25
|
+
return accounts.flatMap((account) => {
|
|
26
|
+
if (account.qboId == null || account.isDeleted === true) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
if (accountTypes != null &&
|
|
30
|
+
(account.accountType == null ||
|
|
31
|
+
!accountTypes.includes(account.accountType))) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
id: String(account.qboId),
|
|
37
|
+
name: account.accountName,
|
|
38
|
+
accountType: account.accountType,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Normalises an account-type name for comparison. The config's mapping kinds
|
|
45
|
+
* name QuickBooks types the way QuickBooks displays them ("Other Current
|
|
46
|
+
* Asset", "Expense"), while the accounting service uses snake_case and
|
|
47
|
+
* sometimes pluralises ("other_current_assets", "expenses"). Comparing the
|
|
48
|
+
* normalised, de-pluralised forms keeps a picker from silently emptying because
|
|
49
|
+
* of a trailing "s".
|
|
50
|
+
*/
|
|
51
|
+
const normalizeAccountTypeName = (value) => {
|
|
52
|
+
const normalized = value
|
|
53
|
+
.trim()
|
|
54
|
+
.toLowerCase()
|
|
55
|
+
.replace(/[^a-z0-9]+/g, '_');
|
|
56
|
+
// Singularise so "Other Current Liability" meets `other_current_liabilities`
|
|
57
|
+
// and "Expense" meets `expenses`. `-ies` needs its own case: stripping the
|
|
58
|
+
// trailing "s" alone would leave "liabilitie", matching nothing.
|
|
59
|
+
return normalized.replace(/ies$/, 'y').replace(/([^s])s$/, '$1');
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* The accounts valid for one QBO mapping kind, whose `accountTypes` is a
|
|
63
|
+
* comma-separated list of QuickBooks type names. An unrecognised list yields no
|
|
64
|
+
* accounts rather than every account: offering a bank account where income is
|
|
65
|
+
* required would post a mapping QuickBooks rejects.
|
|
66
|
+
*/
|
|
67
|
+
export const invoicingAccountsForQboAccountTypes = (accounts, accountTypes) => {
|
|
68
|
+
const wanted = new Set(accountTypes
|
|
69
|
+
.split(',')
|
|
70
|
+
.map(normalizeAccountTypeName)
|
|
71
|
+
.filter((accountType) => accountType !== ''));
|
|
72
|
+
if (wanted.size === 0) {
|
|
73
|
+
return accounts;
|
|
74
|
+
}
|
|
75
|
+
return accounts.filter((account) => account.accountType != null &&
|
|
76
|
+
wanted.has(normalizeAccountTypeName(account.accountType)));
|
|
77
|
+
};
|
|
78
|
+
/** Classes as the invoicing pickers need them, from the shared class list. */
|
|
79
|
+
const invoicingAccountingClassOptions = (state) => {
|
|
80
|
+
const { classes } = getClassList(state.classState, state.classListState);
|
|
81
|
+
return classes.flatMap((accountingClass) => accountingClass.qboId == null || accountingClass.isDeleted === true
|
|
82
|
+
? []
|
|
83
|
+
: [
|
|
84
|
+
{
|
|
85
|
+
id: String(accountingClass.qboId),
|
|
86
|
+
name: accountingClass.className,
|
|
87
|
+
},
|
|
88
|
+
]);
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* The account tree narrowed to the given account types. The tree itself carries
|
|
92
|
+
* no account type, so the flat account list supplies that and the matching
|
|
93
|
+
* `qboId`s are kept — which is why callers that only need "income accounts, as a
|
|
94
|
+
* tree" do not have to hold a second flat list to filter with.
|
|
95
|
+
*/
|
|
96
|
+
export const invoicingAccountHierarchyForTypes = (state, accountTypes) => {
|
|
97
|
+
const allowedQboIds = new Set(invoicingAccountingOptions(state, accountTypes).map((account) => account.id));
|
|
98
|
+
const keepMatching = (nodes) => nodes.flatMap((node) => {
|
|
99
|
+
const children = keepMatching(node.children);
|
|
100
|
+
const isMatch = node.qboId != null && allowedQboIds.has(String(node.qboId));
|
|
101
|
+
// A parent that is not itself a match is still kept when it holds one, so
|
|
102
|
+
// the tree's shape survives the filter.
|
|
103
|
+
if (!isMatch && children.length === 0) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
return [{ ...node, children }];
|
|
107
|
+
});
|
|
108
|
+
return keepMatching(getNestedAccountListHierarchy(state.accountListState, INVOICING_ACCOUNT_LIST_KEY));
|
|
109
|
+
};
|
|
1
110
|
/**
|
|
2
111
|
* Single bundled view for the QBO integration settings screen — the
|
|
3
|
-
* connection,
|
|
4
|
-
*
|
|
112
|
+
* connection, the account and class options, sync health, and the fetch/action
|
|
113
|
+
* states for each of them.
|
|
5
114
|
*/
|
|
6
115
|
export const getInvoicingQboView = (state) => {
|
|
7
116
|
const qboState = state.invoicingQboViewState;
|
|
117
|
+
const accountList = state.accountListState.byReportId[INVOICING_ACCOUNT_LIST_KEY];
|
|
8
118
|
return {
|
|
9
|
-
|
|
10
|
-
|
|
119
|
+
accounts: invoicingAccountingOptions(state),
|
|
120
|
+
accountsFetchState: {
|
|
121
|
+
fetchState: accountList.fetchState,
|
|
122
|
+
error: accountList.error,
|
|
123
|
+
},
|
|
124
|
+
nestedAccountHierarchy: getNestedAccountListHierarchy(state.accountListState, INVOICING_ACCOUNT_LIST_KEY),
|
|
125
|
+
nestedClassHierarchy: getNestedClassListHierarchy(state.classListState),
|
|
11
126
|
backfillState: qboState.backfillFetchState,
|
|
12
|
-
classes:
|
|
13
|
-
classesFetchState:
|
|
127
|
+
classes: invoicingAccountingClassOptions(state),
|
|
128
|
+
classesFetchState: {
|
|
129
|
+
fetchState: state.classListState.fetchState,
|
|
130
|
+
error: state.classListState.error,
|
|
131
|
+
},
|
|
14
132
|
connection: qboState.connection,
|
|
15
133
|
connectionFetchState: qboState.connectionFetchState,
|
|
16
134
|
retryFetchStateByTaskId: qboState.retryFetchStateByTaskId,
|
package/lib/index.d.ts
CHANGED
|
@@ -968,12 +968,12 @@ export { AiAccountantCustomerViewState, AiAccountantSortKey, AiAccountantUIState
|
|
|
968
968
|
export { AiAccountantFilterCategoryField } from './view/companyView/types/cockpitTypes';
|
|
969
969
|
export { cancelAiAccountantOnboarding, fetchAiAccountantCustomers, fetchAiAccountantJobs, setSelectedTenantIdsForJobTrigger, triggerAiAccountantJob, updateAiAccountantUIState, } from './view/aiAccountantView/aiAccountantViewReducer';
|
|
970
970
|
export { AiAccountantCockpitView, getAiAccountantCockpitView, } from './view/aiAccountantView/aiAccountantViewSelector';
|
|
971
|
-
export { fetchChatSessionsForUser, createSession, submitQuestion, updateAiCfoViewScrollPosition, updateCurrentInput, updateCotCollapsedState, setSession, clearCurrentSessionId, clearDeleteChatSessionStatus, clearLastContextMessage, createSessionAndSubmit, stopSubmit, stopSubmitQuestion, fetchChatHistory, acceptMasterTOS, deleteChatSession, submitFeedback, updateResponseState, fetchSuggestedQuestions, fetchSuggestedQuestionsSuccess, fetchSuggestedQuestionsFailure, fetchSkills, fetchSkillsSuccess, fetchSkillsFailure, applyAiCfoSidePanelHostPageTransition, clearAiCfoSidePanelHostPageContext, } from './view/aiCfoView/aiCfoViewReducer';
|
|
972
|
-
export { AiCfoViewState, AiCfoViewUIState, Skill, SkillsState, SuggestedQuestionsForPageContext, } from './view/aiCfoView/aiCfoViewState';
|
|
971
|
+
export { fetchChatSessionsForUser, createSession, submitQuestion, updateAiCfoViewScrollPosition, updateCurrentInput, updateCotCollapsedState, setSession, clearCurrentSessionId, clearDeleteChatSessionStatus, clearLastContextMessage, createSessionAndSubmit, stopSubmit, stopSubmitQuestion, fetchChatHistory, acceptMasterTOS, deleteChatSession, submitFeedback, updateResponseState, fetchSuggestedQuestions, fetchSuggestedQuestionsSuccess, fetchSuggestedQuestionsFailure, fetchSkills, fetchSkillsSuccess, fetchSkillsFailure, fetchSkillDetail, clearSkillDetail, clearSkillMutation, deleteSkill, fetchSkillVersionBody, fetchSkillVersions, saveSkill, setSkillVisibility, openSkillsBrowser, closeSkillsBrowser, applyAiCfoSidePanelHostPageTransition, clearAiCfoSidePanelHostPageContext, } from './view/aiCfoView/aiCfoViewReducer';
|
|
972
|
+
export { AiCfoViewState, AiCfoViewUIState, Skill, SkillDetail, SkillDetailState, SkillDraft, SkillMutationState, SkillVersion, SkillVersionBodyState, SkillVersionsState, SkillVisibility, SkillsState, SuggestedQuestionsForPageContext, } from './view/aiCfoView/aiCfoViewState';
|
|
973
973
|
export { FetchSuggestedQuestionsResponse, SuggestedQuestionsDataPayload, FetchSkillsResponse, SkillSummaryPayload, SkillListDataPayload, } from './view/aiCfoView/aiCfoViewPayload';
|
|
974
974
|
export type { FeedbackType, SubmitFeedbackPayload, } from './view/aiCfoView/aiCfoViewPayload';
|
|
975
975
|
export type { AiCfoSuggestedQuestionsPageContext } from './common/aiCfo/aiCfoSuggestedQuestionsPageContext';
|
|
976
|
-
export { AiCfoViewSelector, getAiCfoView, getSuggestedQuestionsForPageContext, getSkills, } from './view/aiCfoView/aiCfoViewSelector';
|
|
976
|
+
export { AiCfoViewSelector, getAiCfoView, getSuggestedQuestionsForPageContext, getSkills, getSkillDetail, getSkillMutation, getSkillVersionBody, getSkillVersions, getSkillsEnabled, } from './view/aiCfoView/aiCfoViewSelector';
|
|
977
977
|
export { MessagePayload, ChatSessionPayload, AiCfoAnswerPayload, } from './entity/aiCfo/aiCfoPayload';
|
|
978
978
|
export { setNewSession, setSessions, updateAiCfoAnswerCardPolicyWizardPlan, upsertAnswerPayload, upsertOrAddQuestionAnswerPayload, addQuestionPayload, clearSession, toAiCfoExports, toAiCfoVisualization, appendSyntheticAiCfoAnswer, updateSyntheticAiCfoAnswer, removeSyntheticAiCfoAnswer, } from './entity/aiCfo/aiCfoReducer';
|
|
979
979
|
export { AiCfoSelectorView, getAllQuestionAnswersForChatSession, getQuestionAnswerByIdForChatSession, getAllQuestionsForChatSession, getAiCfoSelectorView, getSyntheticAiCfoAnswersForChatSession, getSyntheticAiCfoAnswerByQuestionAnswerId, ChatSessionWithOrderedQuestionAnswers, } from './entity/aiCfo/aiCfoSelector';
|
|
@@ -1240,10 +1240,12 @@ export type { InvoicingBrandingFormView, InvoicingSettingsHubView, InvoicingSett
|
|
|
1240
1240
|
export type { InvoicingBrandingFormLocalData } from './view/invoicing/settingsView/invoicingBrandingFormConfig';
|
|
1241
1241
|
export { INVOICING_BUSINESS_ADDRESS_TYPE, entityToInvoicingBrandingFormLocalData, initialInvoicingBrandingFormLocalData, } from './view/invoicing/settingsView/invoicingBrandingFormConfig';
|
|
1242
1242
|
export { isStripeConnectedFromSettings } from './view/invoicing/settingsView/invoicingSettingsHelpers';
|
|
1243
|
-
export {
|
|
1243
|
+
export { fetchInvoicingAccountingPickers, fetchInvoicingQboConnection, fetchInvoicingQboSyncHealth, retryInvoicingQboSyncTask, startInvoicingQboBackfill, } from './view/invoicing/invoicingQboView/invoicingQboViewReducer';
|
|
1244
|
+
export { INVOICING_INCOME_ACCOUNT_TYPES, invoicingAccountsForQboAccountTypes, } from './view/invoicing/invoicingQboView/invoicingQboViewSelector';
|
|
1245
|
+
export type { InvoicingAccountingOption } from './view/invoicing/invoicingQboView/invoicingQboViewSelector';
|
|
1244
1246
|
export { getInvoicingQboView } from './view/invoicing/invoicingQboView/invoicingQboViewSelector';
|
|
1245
1247
|
export type { InvoicingQboView } from './view/invoicing/invoicingQboView/invoicingQboViewSelector';
|
|
1246
|
-
export type {
|
|
1248
|
+
export type { InvoicingQboConnection, InvoicingQboFailedTask, InvoicingQboSyncHealth, } from './view/invoicing/invoicingQboView/invoicingQboViewPayload';
|
|
1247
1249
|
export { INVOICING_QBO_CUSTOMER_MATCHING_MODES, getInvoicingQboAccountMappingId, toInvoicingNamedOptionsByKey, toInvoicingQboCustomerMatchingMode, withInvoicingQboAccountMappingId, } from './entity/invoicing/invoicingCommonPayload';
|
|
1248
1250
|
export type { InvoicingNamedOption, InvoicingFieldHelp, InvoicingFieldHelpByCode, InvoicingFieldHelpGroup, InvoicingNamedOptionsByKey, InvoicingQboAccountMappingKindOption, InvoicingQboAccountMappingsPayload, InvoicingQboCustomerMatchingMode, InvoicingQboSettingsPayload, } from './entity/invoicing/invoicingCommonPayload';
|
|
1249
1251
|
export { acceptInvoicingTerms, enableInvoicing, fetchInvoicingConfig, resetInvoicingConfigActionStates, updateInvoicingConfig, } from './view/invoicing/invoicingConfigView/invoicingConfigViewReducer';
|