@zeniai/client-epic-state 5.2.35 → 5.2.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/lib/commonPayloadTypes/classesView/reportByClassPayloadV2.d.ts +1 -13
  2. package/lib/entity/sectionClassesViewV2/sectionClassesViewEpic.js +7 -10
  3. package/lib/entity/sectionClassesViewV2/sectionClassesViewReducer.d.ts +3 -3
  4. package/lib/entity/sectionProjectView/sectionProjectViewEpic.js +2 -6
  5. package/lib/entity/sectionProjectView/sectionProjectViewReducer.d.ts +3 -3
  6. package/lib/entity/sectionProjectView/sectionProjectViewReducer.js +3 -4
  7. package/lib/epic.js +0 -12
  8. package/lib/esm/entity/class/classReducer.js +1 -1
  9. package/lib/esm/entity/sectionClassesViewV2/sectionClassesViewEpic.js +7 -10
  10. package/lib/esm/entity/sectionProjectView/sectionProjectViewEpic.js +2 -6
  11. package/lib/esm/entity/sectionProjectView/sectionProjectViewReducer.js +4 -5
  12. package/lib/esm/epic.js +0 -12
  13. package/lib/esm/index.js +2 -2
  14. package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +1 -266
  15. package/lib/esm/view/aiCfoView/aiCfoViewSelector.js +0 -50
  16. package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewPayload.js +0 -4
  17. package/lib/index.d.ts +4 -4
  18. package/lib/index.js +40 -55
  19. package/lib/view/aiCfoView/aiCfoViewPayload.d.ts +0 -90
  20. package/lib/view/aiCfoView/aiCfoViewReducer.d.ts +123 -164
  21. package/lib/view/aiCfoView/aiCfoViewReducer.js +3 -268
  22. package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +1 -13
  23. package/lib/view/aiCfoView/aiCfoViewSelector.js +0 -55
  24. package/lib/view/aiCfoView/aiCfoViewState.d.ts +0 -157
  25. package/lib/view/cashFlowClassesView/cashFlowClassesViewPayload.d.ts +4 -4
  26. package/lib/view/cashFlowProjectView/cashFlowProjectViewPayload.d.ts +4 -4
  27. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewPayload.d.ts +11 -1
  28. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewPayload.js +0 -4
  29. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.d.ts +2 -1
  30. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.d.ts +2 -1
  31. package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewState.d.ts +1 -15
  32. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewReducer.d.ts +1 -1
  33. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewState.d.ts +1 -1
  34. package/lib/view/profitAndLossProjectView/profitAndLossProjectViewPayload.d.ts +0 -12
  35. package/package.json +1 -1
  36. package/lib/esm/view/aiCfoView/epics/deleteRoutineEpic.js +0 -35
  37. package/lib/esm/view/aiCfoView/epics/fetchRoutineRunsEpic.js +0 -35
  38. package/lib/esm/view/aiCfoView/epics/fetchRoutinesEpic.js +0 -37
  39. package/lib/esm/view/aiCfoView/epics/runRoutineEpic.js +0 -34
  40. package/lib/esm/view/aiCfoView/epics/saveRoutineEpic.js +0 -65
  41. package/lib/esm/view/aiCfoView/epics/setRoutineEnabledEpic.js +0 -28
  42. package/lib/esm/view/aiCfoView/epics/toRoutine.js +0 -64
  43. package/lib/esm/view/aiCfoView/epics/toRoutineRun.js +0 -42
  44. package/lib/view/aiCfoView/epics/deleteRoutineEpic.d.ts +0 -18
  45. package/lib/view/aiCfoView/epics/deleteRoutineEpic.js +0 -39
  46. package/lib/view/aiCfoView/epics/fetchRoutineRunsEpic.d.ts +0 -13
  47. package/lib/view/aiCfoView/epics/fetchRoutineRunsEpic.js +0 -39
  48. package/lib/view/aiCfoView/epics/fetchRoutinesEpic.d.ts +0 -7
  49. package/lib/view/aiCfoView/epics/fetchRoutinesEpic.js +0 -41
  50. package/lib/view/aiCfoView/epics/runRoutineEpic.d.ts +0 -14
  51. package/lib/view/aiCfoView/epics/runRoutineEpic.js +0 -38
  52. package/lib/view/aiCfoView/epics/saveRoutineEpic.d.ts +0 -13
  53. package/lib/view/aiCfoView/epics/saveRoutineEpic.js +0 -69
  54. package/lib/view/aiCfoView/epics/setRoutineEnabledEpic.d.ts +0 -15
  55. package/lib/view/aiCfoView/epics/setRoutineEnabledEpic.js +0 -32
  56. package/lib/view/aiCfoView/epics/toRoutine.d.ts +0 -17
  57. package/lib/view/aiCfoView/epics/toRoutine.js +0 -69
  58. package/lib/view/aiCfoView/epics/toRoutineRun.d.ts +0 -12
  59. package/lib/view/aiCfoView/epics/toRoutineRun.js +0 -47
@@ -1,7 +1,7 @@
1
1
  import { InvoicingCustomerPayload } from '../../../entity/invoicing/invoicingCustomer/invoicingCustomerPayload';
2
2
  import { ZeniAPIResponse } from '../../../responsePayload';
3
3
  import { AddressUpdatableInfo } from '../../addressView/addressViewState';
4
- import { InvoicingCustomerFormLocalData, InvoicingPaymentLinkEmailPreviewData } from './editInvoicingCustomerDetailViewState';
4
+ import { InvoicingCustomerFormLocalData } from './editInvoicingCustomerDetailViewState';
5
5
  export type SaveInvoicingCustomerResponse = ZeniAPIResponse<InvoicingCustomerPayload>;
6
6
  /**
7
7
  * Build the create/update customer request body from the form draft. The
@@ -25,5 +25,15 @@ export type InvoicingPaymentLinkEmailPreviewResponse = ZeniAPIResponse<Invoicing
25
25
  * The email rendered once with sentinel tokens standing in for the subject and
26
26
  * body, so the composer can splice a draft into it without another request.
27
27
  */
28
+ export interface InvoicingPaymentLinkEmailPreviewData {
29
+ /** Default body prefill, plain text. */
30
+ body: string;
31
+ bodyToken: string;
32
+ renderedHtmlTemplate: string;
33
+ /** Default subject prefill. */
34
+ subject: string;
35
+ subjectToken: string;
36
+ to: string;
37
+ }
28
38
  export declare const toInvoicingPaymentLinkEmailPreview: (payload: InvoicingPaymentLinkEmailPreviewPayload) => InvoicingPaymentLinkEmailPreviewData;
29
39
  export type AddPromotionalCreditsResponse = ZeniAPIResponse<InvoicingCustomerPayload>;
@@ -34,10 +34,6 @@ const localDataToSaveInvoicingCustomerPayload = (localData, billingAddress) => {
34
34
  };
35
35
  };
36
36
  exports.localDataToSaveInvoicingCustomerPayload = localDataToSaveInvoicingCustomerPayload;
37
- /**
38
- * The email rendered once with sentinel tokens standing in for the subject and
39
- * body, so the composer can splice a draft into it without another request.
40
- */
41
37
  const toInvoicingPaymentLinkEmailPreview = (payload) => ({
42
38
  body: payload.body,
43
39
  bodyToken: payload.body_token,
@@ -1,6 +1,7 @@
1
1
  import { ID } from '../../../commonStateTypes/common';
2
2
  import { ZeniAPIStatus } from '../../../responsePayload';
3
- import { EditInvoicingCustomerDetailViewState, InvoicingCustomerFormLocalData, InvoicingPaymentLinkEmailPreviewData } from './editInvoicingCustomerDetailViewState';
3
+ import { InvoicingPaymentLinkEmailPreviewData } from './editInvoicingCustomerDetailViewPayload';
4
+ import { EditInvoicingCustomerDetailViewState, InvoicingCustomerFormLocalData } from './editInvoicingCustomerDetailViewState';
4
5
  /** Add promotional credits to a customer. */
5
6
  export interface SubmitAddPromotionalCreditsPayload {
6
7
  amount: number;
@@ -5,7 +5,8 @@ import { InvoicingFieldHelpByCode, InvoicingNamedOption } from '../../../entity/
5
5
  import { InvoicingCustomer, InvoicingCustomerPaymentMethod } from '../../../entity/invoicing/invoicingCustomer/invoicingCustomerState';
6
6
  import { RootState } from '../../../rootStateTypes';
7
7
  import { InvoicingSetupIntent } from '../customerPaymentMethod/customerPaymentMethodState';
8
- import { InvoicingCustomerFormLocalData, InvoicingPaymentLinkEmailPreviewData } from './editInvoicingCustomerDetailViewState';
8
+ import { InvoicingPaymentLinkEmailPreviewData } from './editInvoicingCustomerDetailViewPayload';
9
+ import { InvoicingCustomerFormLocalData } from './editInvoicingCustomerDetailViewState';
9
10
  /**
10
11
  * Read-only view for the customer detail page: the customer entity + its fetch
11
12
  * state, plus everything the page renders in view mode — recent invoices, the
@@ -1,19 +1,5 @@
1
1
  import { FetchStateAndError, FetchedState, ID } from '../../../commonStateTypes/common';
2
- /**
3
- * The payment-link email the composer opens with: the backend's prefill plus
4
- * the tokens the frontend splices the user's edits into. State, not wire shape
5
- * — `toInvoicingPaymentLinkEmailPreview` in the payload file maps onto it.
6
- */
7
- export interface InvoicingPaymentLinkEmailPreviewData {
8
- /** Default body prefill, plain text. */
9
- body: string;
10
- bodyToken: string;
11
- renderedHtmlTemplate: string;
12
- /** Default subject prefill. */
13
- subject: string;
14
- subjectToken: string;
15
- to: string;
16
- }
2
+ import { InvoicingPaymentLinkEmailPreviewData } from './editInvoicingCustomerDetailViewPayload';
17
3
  /**
18
4
  * Draft form state for the create/edit customer form. Held in CES so the form
19
5
  * flows its data from here (mirrors the bill-pay / reimbursement pattern). The
@@ -26,7 +26,7 @@ export declare const fetchInvoicingCustomerList: import("@reduxjs/toolkit").Acti
26
26
  ids: ID[];
27
27
  keepExistingListItems: boolean;
28
28
  nextCursor: string | null;
29
- }, "invoicingCustomerListView/updateInvoicingCustomerList">, updateInvoicingCustomerListFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "invoicingCustomerListView/updateInvoicingCustomerListFailure">, fetchInvoicingCustomerCounts: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"invoicingCustomerListView/fetchInvoicingCustomerCounts">, updateInvoicingCustomerCounts: import("@reduxjs/toolkit").ActionCreatorWithPayload<InvoicingListCounts, "invoicingCustomerListView/updateInvoicingCustomerCounts">, updateInvoicingCustomerCountsFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "invoicingCustomerListView/updateInvoicingCustomerCountsFailure">, updateInvoicingCustomerFilters: import("@reduxjs/toolkit").ActionCreatorWithPayload<InvoicingCustomerFilters, "invoicingCustomerListView/updateInvoicingCustomerFilters">, setInvoicingCustomerActiveTab: import("@reduxjs/toolkit").ActionCreatorWithPayload<"active" | "all" | "trial" | "at_risk", "invoicingCustomerListView/setInvoicingCustomerActiveTab">, addInvoicingCustomerToList: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
29
+ }, "invoicingCustomerListView/updateInvoicingCustomerList">, updateInvoicingCustomerListFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "invoicingCustomerListView/updateInvoicingCustomerListFailure">, fetchInvoicingCustomerCounts: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"invoicingCustomerListView/fetchInvoicingCustomerCounts">, updateInvoicingCustomerCounts: import("@reduxjs/toolkit").ActionCreatorWithPayload<InvoicingListCounts, "invoicingCustomerListView/updateInvoicingCustomerCounts">, updateInvoicingCustomerCountsFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "invoicingCustomerListView/updateInvoicingCustomerCountsFailure">, updateInvoicingCustomerFilters: import("@reduxjs/toolkit").ActionCreatorWithPayload<InvoicingCustomerFilters, "invoicingCustomerListView/updateInvoicingCustomerFilters">, setInvoicingCustomerActiveTab: import("@reduxjs/toolkit").ActionCreatorWithPayload<"active" | "all" | "at_risk" | "trial", "invoicingCustomerListView/setInvoicingCustomerActiveTab">, addInvoicingCustomerToList: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
30
30
  id: ID;
31
31
  status?: string;
32
32
  }, "invoicingCustomerListView/addInvoicingCustomerToList">;
@@ -20,7 +20,7 @@ export interface InvoicingCustomerColumnConfig {
20
20
  }
21
21
  export declare const INVOICING_CUSTOMER_COLUMNS: InvoicingCustomerColumnConfig[];
22
22
  export declare const ALL_INVOICING_CUSTOMER_TAB_IDS: readonly ["all", "active", "trial", "at_risk"];
23
- export declare const toInvoicingCustomerTabId: (v: string) => "active" | "all" | "trial" | "at_risk";
23
+ export declare const toInvoicingCustomerTabId: (v: string) => "active" | "all" | "at_risk" | "trial";
24
24
  export type InvoicingCustomerTabId = ReturnType<typeof toInvoicingCustomerTabId>;
25
25
  export interface InvoicingCustomerListViewState extends FetchedState {
26
26
  /** Selected status tab (CES-owned so it persists across page revisits). */
@@ -17,18 +17,6 @@ interface ProjectCOABalanceGroupedPayloadByProjectIdV2 {
17
17
  export interface SectionByProjectCOABalanceGroupedPayloadV2 extends COABalanceGroupedPayloadV2 {
18
18
  projects: ProjectCOABalanceGroupedPayloadByProjectIdV2;
19
19
  }
20
- interface CalculatedProjectCOABalanceGroupedPayloadByProjectIdV2 {
21
- [project_Id: string]: COABalanceGroupedPayloadV2;
22
- }
23
- /**
24
- * A section whose per-project entries carry a time series and nothing else: the
25
- * backend fills the account-wise map only for the account sections, so the
26
- * calculated sections (net income, the operating total, change in cash) are
27
- * grouped by project with no accounts underneath.
28
- */
29
- export interface CalculatedSectionByProjectCOABalanceGroupedPayloadV2 extends COABalanceGroupedPayloadV2 {
30
- projects: CalculatedProjectCOABalanceGroupedPayloadByProjectIdV2;
31
- }
32
20
  export interface ProfitAndLossProjectViewPayload extends ReportByProjectMetadataPayloadV2 {
33
21
  cogs: SectionByProjectCOABalanceGroupedPayloadV2;
34
22
  earnings_before_interest_and_tax: COABalanceGroupedPayloadV2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.2.35",
3
+ "version": "5.2.36",
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",
@@ -1,35 +0,0 @@
1
- import { of } from 'rxjs';
2
- import { catchError, filter, mergeMap } from 'rxjs/operators';
3
- import { createZeniAPIStatus, isSuccessStatus, } from '../../../responsePayload';
4
- import { deleteRoutine, deleteRoutineFailure, deleteRoutineSuccess, fetchRoutines, } from '../aiCfoViewReducer';
5
- /**
6
- * DELETE /1.0/routines/{scheduleId}.
7
- *
8
- * The row has already left the list by the time this runs — the reducer removes
9
- * it optimistically so the click does not feel ignored.
10
- *
11
- * A failure therefore has to put it BACK, not merely report. The routine still
12
- * exists and is still emailing the user their financials on a schedule; leaving
13
- * it hidden would tell them they had stopped something they had not. So a
14
- * failure drops the tombstone and refetches, and the error explains why the row
15
- * returned.
16
- */
17
- export const deleteRoutineEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(deleteRoutine.match),
18
- // mergeMap: deleting one routine must not cancel the delete of another.
19
- mergeMap((action) => {
20
- const { scheduleId } = action.payload;
21
- return zeniAPI
22
- .deleteAndGetJSON(`${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/routines/${encodeURIComponent(scheduleId)}`)
23
- .pipe(mergeMap((response) =>
24
- // isSuccessStatus, NOT isSuccessResponse: the latter also requires a
25
- // non-null `data`, and a successful DELETE here is a 204 with no body.
26
- // deleteSkillEpic carries the same note for the same reason — using
27
- // the wrong one reported every completed delete as a failure, which
28
- // here would also drop the tombstone and refetch the row back.
29
- isSuccessStatus(response)
30
- ? of(deleteRoutineSuccess({ scheduleId }))
31
- : of(deleteRoutineFailure({ scheduleId, status: response.status }), fetchRoutines())), catchError((error) => of(deleteRoutineFailure({
32
- scheduleId,
33
- status: createZeniAPIStatus('Unexpected Error', 'Delete routine REST API call failed', error instanceof Error ? { message: error.message } : undefined),
34
- }), fetchRoutines())));
35
- }));
@@ -1,35 +0,0 @@
1
- import { of } from 'rxjs';
2
- import { catchError, filter, map, switchMap } from 'rxjs/operators';
3
- import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
4
- import { fetchRoutineRuns, fetchRoutineRunsFailure, fetchRoutineRunsSuccess, } from '../aiCfoViewReducer';
5
- import { isRoutineRunPayload, toRoutineRun } from './toRoutineRun';
6
- /**
7
- * GET /1.0/routines/{scheduleId}/runs — one routine's run history.
8
- *
9
- * switchMap: only the history the user has open matters, so expanding a second
10
- * routine while the first is still loading should abandon the first rather than
11
- * race it into the single slot the state keeps.
12
- */
13
- export const fetchRoutineRunsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchRoutineRuns.match), switchMap((action) => {
14
- const { scheduleId } = action.payload;
15
- return zeniAPI
16
- .getJSON(`${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/routines/${encodeURIComponent(scheduleId)}/runs`)
17
- .pipe(map((response) => {
18
- // The explicit `data == null` is what narrows the type — the helper
19
- // returns a plain boolean, not a type guard — and it means a
20
- // malformed 2xx becomes a failure rather than an empty history,
21
- // which would read as "this has never run" when it has.
22
- if (!isSuccessResponse(response) || response.data == null) {
23
- return fetchRoutineRunsFailure(response.status);
24
- }
25
- const { data } = response;
26
- return fetchRoutineRunsSuccess({
27
- // Filtered, not trusted: one malformed row must not take the
28
- // whole history down, and a run without an id is not addressable
29
- // by anything the UI does with it.
30
- runs: (data.runs ?? []).filter(isRoutineRunPayload).map(toRoutineRun),
31
- scheduleId,
32
- sessionId: data.session_id ?? null,
33
- });
34
- }), catchError((error) => of(fetchRoutineRunsFailure(createZeniAPIStatus('Unexpected Error', 'Fetch routine runs REST API call failed', error instanceof Error ? { message: error.message } : undefined)))));
35
- }));
@@ -1,37 +0,0 @@
1
- import { of } from 'rxjs';
2
- import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
- import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
4
- import { fetchRoutines, fetchRoutinesFailure, fetchRoutinesSuccess, } from '../aiCfoViewReducer';
5
- import { isRoutinePayload, toRoutine } from './toRoutine';
6
- /** GET /1.0/routines — the caller's own scheduled runs. */
7
- export const fetchRoutinesEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchRoutines.match),
8
- // switchMap: a read. A newer list request makes an older one's answer
9
- // irrelevant, and letting both land means the older can arrive second and
10
- // overwrite the newer — which matters here because every write refetches,
11
- // so two GETs in flight is the normal case rather than the exotic one. It
12
- // also widens the delete tombstone's race window.
13
- switchMap(() => zeniAPI
14
- .getJSON(`${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/routines`)
15
- .pipe(mergeMap((response) => {
16
- // The explicit `data == null` is what narrows the type — the
17
- // helper returns a plain boolean, not a type guard — and it means a
18
- // malformed 2xx becomes a failure rather than a silent empty list,
19
- // which would read as "you have no routines" when you do.
20
- if (!isSuccessResponse(response) || response.data == null) {
21
- return of(fetchRoutinesFailure(response.status));
22
- }
23
- const { data } = response;
24
- return of(fetchRoutinesSuccess({
25
- // Malformed rows are dropped, not defaulted into existence: a
26
- // routine with no id cannot be opened, paused or deleted, so
27
- // rendering one offers controls that cannot work.
28
- routines: (data.routines ?? [])
29
- .filter(isRoutinePayload)
30
- .map(toRoutine),
31
- maxRoutines: data.max_routines ?? 0,
32
- // Absent reads as OFF, matching the server's fail-closed gate:
33
- // an older backend that does not send it should hide the entry,
34
- // not show one whose endpoints 404.
35
- routinesEnabled: data.routines_enabled === true,
36
- }));
37
- }), catchError((error) => of(fetchRoutinesFailure(createZeniAPIStatus('Unexpected Error', 'Fetch routines REST API call failed', error instanceof Error ? { message: error.message } : undefined)))))));
@@ -1,34 +0,0 @@
1
- import { of } from 'rxjs';
2
- import { catchError, filter, mergeMap } from 'rxjs/operators';
3
- import { createZeniAPIStatus, isSuccessStatus } from '../../../responsePayload';
4
- import { fetchRoutineRuns, fetchRoutines, runRoutine, runRoutineFailure, runRoutineSuccess, } from '../aiCfoViewReducer';
5
- /**
6
- * POST /1.0/routines/{scheduleId}/run — run a routine now.
7
- *
8
- * The response says the run was QUEUED, not that it finished: the answer is
9
- * emailed a minute or two later. So nothing is spliced in — both refetches are
10
- * what show the new run, and the outcome arrives on a later fetch rather than
11
- * being polled for.
12
- */
13
- export const runRoutineEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(runRoutine.match),
14
- // mergeMap, not exhaustMap: these target different rows, so running one
15
- // routine must not swallow a request to run another. A double-click on the
16
- // SAME row is already harmless — the server keys a manual run by the
17
- // minute, so the second press finds the first run and reports it.
18
- mergeMap((action) => {
19
- const { scheduleId } = action.payload;
20
- return zeniAPI
21
- .postAndGetJSON(`${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/routines/${encodeURIComponent(scheduleId)}/run`, {})
22
- .pipe(mergeMap((response) =>
23
- // isSuccessStatus, not isSuccessResponse: "already_running" is a
24
- // legitimate 200 whose body carries no routine, and treating a
25
- // second press as a failure would be a lie about what happened.
26
- isSuccessStatus(response)
27
- ? of(runRoutineSuccess(), fetchRoutines(),
28
- // The run history too, or the panel the user is looking at
29
- // keeps showing the runs from before they pressed Run. The
30
- // new row comes back as claimed/running — the run has been
31
- // queued, not finished — which is the honest thing to show.
32
- fetchRoutineRuns({ scheduleId }))
33
- : of(runRoutineFailure(response.status))), catchError((error) => of(runRoutineFailure(createZeniAPIStatus('Unexpected Error', 'Run routine REST API call failed', error instanceof Error ? { message: error.message } : undefined)))));
34
- }));
@@ -1,65 +0,0 @@
1
- import { of } from 'rxjs';
2
- import { catchError, exhaustMap, filter, mergeMap } from 'rxjs/operators';
3
- import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
4
- import { fetchRoutines, saveRoutine, saveRoutineFailure, saveRoutineSuccess, } from '../aiCfoViewReducer';
5
- /**
6
- * POST /1.0/routines (create) or PUT /1.0/routines/{scheduleId} (update).
7
- *
8
- * One epic for both: to a user they are the same act, and the server answers
9
- * both with the routine. Splitting them would duplicate the body mapping and
10
- * the error handling for a difference the form does not have.
11
- */
12
- export const saveRoutineEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(saveRoutine.match),
13
- // exhaustMap, and neither of the obvious two.
14
- //
15
- // switchMap is wrong: it cancels the client's subscription, not the request
16
- // that already left the browser. The routine is still created; the client
17
- // just stops listening and never tells the user.
18
- //
19
- // mergeMap is wrong for the same reason in reverse: a double-clicked Save
20
- // sends two POSTs, and the user gets two routines that both email them the
21
- // company's financials on the same schedule.
22
- //
23
- // exhaustMap ignores further saves while one is in flight, which is exactly
24
- // what a submit button should do.
25
- exhaustMap((action) => {
26
- const { draft, scheduleId } = action.payload;
27
- const base = `${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/routines`;
28
- const isUpdate = scheduleId != null && scheduleId !== '';
29
- const body = {
30
- name: draft.name,
31
- recurrence: draft.recurrence,
32
- hour: draft.hour,
33
- minute: draft.minute ?? 0,
34
- tz_name: draft.tzName,
35
- // Spread rather than sent as null. `weekday` and `day_of_month` are
36
- // meaningful only for their own recurrence, and the server validates
37
- // that pairing — sending a stale weekday alongside a monthly cadence is
38
- // a 400 rather than a field politely ignored.
39
- ...(draft.recurrence === 'weekly' &&
40
- draft.weekday != null && { weekday: draft.weekday }),
41
- ...(draft.recurrence === 'monthly' &&
42
- draft.dayOfMonth != null && { day_of_month: draft.dayOfMonth }),
43
- ...(draft.enabled != null && { enabled: draft.enabled }),
44
- // `macro_id` is create-only: re-sending it on an update would let an
45
- // edit silently repoint a schedule at a different skill.
46
- ...(!isUpdate && draft.macroId != null && { macro_id: draft.macroId }),
47
- // `prompt` is allowed on both. On update the server accepts it only for
48
- // a one-off, where it saves a new version of the routine's own
49
- // instructions — that is editing what this routine says, not repointing
50
- // it at someone else's skill.
51
- ...(draft.prompt != null && { prompt: draft.prompt }),
52
- };
53
- const request$ = isUpdate
54
- ? zeniAPI.putAndGetJSON(`${base}/${encodeURIComponent(scheduleId)}`, body)
55
- : zeniAPI.postAndGetJSON(base, body);
56
- return request$.pipe(mergeMap((response) => {
57
- if (!isSuccessResponse(response)) {
58
- return of(saveRoutineFailure(response.status));
59
- }
60
- // Refetch rather than splice the response in: a create changes the
61
- // list's ordering, and the server recomputes `next_run_at` on every
62
- // write, so a local insert would show a time the next load moves.
63
- return of(saveRoutineSuccess(), fetchRoutines());
64
- }), catchError((error) => of(saveRoutineFailure(createZeniAPIStatus('Unexpected Error', 'Save routine REST API call failed', error instanceof Error ? { message: error.message } : undefined)))));
65
- }));
@@ -1,28 +0,0 @@
1
- import { of } from 'rxjs';
2
- import { catchError, filter, mergeMap } from 'rxjs/operators';
3
- import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
4
- import { fetchRoutines, saveRoutineFailure, saveRoutineSuccess, setRoutineEnabled, } from '../aiCfoViewReducer';
5
- /**
6
- * PUT /1.0/routines/{scheduleId} with nothing but `enabled`.
7
- *
8
- * Separate from `saveRoutineEpic` because pausing is not editing. A routine
9
- * whose cadence this client version does not recognise has `recurrence: null`
10
- * and cannot produce a `RoutineDraft` — and that is precisely the routine
11
- * someone most needs to be able to stop. Sending only `enabled` also means a
12
- * pause can never rewrite a schedule as a side effect.
13
- */
14
- export const setRoutineEnabledEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(setRoutineEnabled.match),
15
- // mergeMap: pausing one routine must not cancel pausing another, and these
16
- // target different rows so they cannot collide the way two saves of one
17
- // form would.
18
- mergeMap((action) => {
19
- const { enabled, scheduleId } = action.payload;
20
- return zeniAPI
21
- .putAndGetJSON(`${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/routines/${encodeURIComponent(scheduleId)}`, { enabled })
22
- .pipe(mergeMap((response) => isSuccessResponse(response)
23
- ? of(saveRoutineSuccess(), fetchRoutines())
24
- : // Refetch on failure too: the switch was flipped optimistically,
25
- // and leaving it showing "paused" for a routine that is still
26
- // running would be the one lie this list must not tell.
27
- of(saveRoutineFailure(response.status), fetchRoutines())), catchError((error) => of(saveRoutineFailure(createZeniAPIStatus('Unexpected Error', 'Set routine enabled REST API call failed', error instanceof Error ? { message: error.message } : undefined)), fetchRoutines())));
28
- }));
@@ -1,64 +0,0 @@
1
- const RECURRENCES = ['daily', 'weekly', 'monthly'];
2
- const STATUSES = ['succeeded', 'failed', 'timeout'];
3
- /**
4
- * A routine is usable if it has an identity. That is the whole bar.
5
- *
6
- * `schedule_id` and `name` are what every consumer dereferences, so those are
7
- * what this establishes.
8
- *
9
- * Deliberately NOT also requiring `macro_id`. A routine whose skill was deleted
10
- * can come back without one — and it is still an active schedule emailing the
11
- * user's financials. Dropping it here would make it invisible in the UI while
12
- * it kept running, with no way for anyone to turn it off. Showing a broken row
13
- * that can be paused or deleted is strictly better than hiding a live one.
14
- */
15
- export const isRoutinePayload = (value) => {
16
- const candidate = value;
17
- return (typeof candidate?.schedule_id === 'string' &&
18
- candidate.schedule_id !== '' &&
19
- typeof candidate.name === 'string');
20
- };
21
- /**
22
- * Narrow a server string to a known recurrence, or null.
23
- *
24
- * Null, NOT a fallback to 'daily'. A read-side default becomes a write-side
25
- * value the moment the user opens that routine and saves it — so guessing here
26
- * would quietly convert a cadence this client does not understand into a daily
27
- * one, and the owner would find out from the volume of email. Null says "this
28
- * client does not know", and the UI shows the row so it can still be paused or
29
- * deleted while refusing to edit its schedule.
30
- */
31
- const toRecurrence = (value) => RECURRENCES.includes(value)
32
- ? value
33
- : null;
34
- const toStatus = (value) => STATUSES.includes(value) ? value : null;
35
- /** Map one routine off the wire. Every default here is a deliberate reading. */
36
- export const toRoutine = (payload) => ({
37
- unreadRuns: payload.unread_runs ?? 0,
38
- consecutiveFailures: payload.consecutive_failures ?? 0,
39
- dayOfMonth: payload.day_of_month ?? null,
40
- // Absent reads as enabled: a routine the server returned without the flag is
41
- // one it intends to run, and defaulting to false would show every row paused.
42
- enabled: payload.enabled !== false,
43
- hour: payload.hour ?? 0,
44
- isAdHoc: payload.is_ad_hoc === true,
45
- lastRunAt: payload.last_run_at ?? null,
46
- lastStatus: toStatus(payload.last_status),
47
- // '' is the sanctioned "unset ID" (CLAUDE.md), not a papered-over absence:
48
- // the row still renders, and the UI reads this to show it as broken rather
49
- // than offering to open a skill that is not there.
50
- macroId: payload.macro_id ?? '',
51
- minute: payload.minute ?? 0,
52
- name: payload.name,
53
- nextRunAt: payload.next_run_at ?? null,
54
- pinnedVersion: payload.pinned_version ?? null,
55
- recurrence: toRecurrence(payload.recurrence),
56
- scheduleId: payload.schedule_id,
57
- sessionId: payload.session_id ?? null,
58
- // Null, not a guessed 'UTC'. Same trap as `recurrence`: a read-side default
59
- // is written straight back on the next save, silently moving the routine's
60
- // delivery by however many hours the real zone differed by. The server always
61
- // sends this, so null means something is wrong and the UI should say so.
62
- tzName: payload.tz_name ?? null,
63
- weekday: payload.weekday ?? null,
64
- });
@@ -1,42 +0,0 @@
1
- const STATUSES = [
2
- 'claimed',
3
- 'running',
4
- 'succeeded',
5
- 'failed',
6
- 'timeout',
7
- ];
8
- /**
9
- * A run is usable if it has an identity — the same bar `isRoutinePayload` sets.
10
- *
11
- * Nothing else is required. A run that is still in flight has no `finished_at`,
12
- * a claimed one has no `started_at`, and a failed one may have neither an error
13
- * nor an answer to link to. Requiring any of those would drop exactly the rows
14
- * the history exists to show.
15
- */
16
- export const isRoutineRunPayload = (value) => {
17
- const candidate = value;
18
- return typeof candidate?.run_id === 'string' && candidate.run_id !== '';
19
- };
20
- /**
21
- * Narrow a server status to one this client knows, or null.
22
- *
23
- * Null rather than a fallback, for the same reason `toRecurrence` returns null:
24
- * a guess here would badge an unknown state as something specific, and on a run
25
- * history the difference between "failed" and "we don't know" is the whole
26
- * message. The UI shows an unknown status as neither succeeded nor failed.
27
- */
28
- const toStatus = (value) => STATUSES.find((status) => status === value) ?? null;
29
- /** Empty strings become null: the server sends '' for an absent deep link. */
30
- const toId = (value) => value != null && value !== '' ? value : null;
31
- export const toRoutineRun = (payload) => ({
32
- runId: payload.run_id,
33
- attempt: payload.attempt ?? 1,
34
- isManual: payload.is_manual === true,
35
- error: toId(payload.error),
36
- finishedAt: toId(payload.finished_at),
37
- questionAnswerId: toId(payload.question_answer_id),
38
- sessionId: toId(payload.session_id),
39
- scheduledFor: toId(payload.scheduled_for),
40
- startedAt: toId(payload.started_at),
41
- status: toStatus(payload.status),
42
- });
@@ -1,18 +0,0 @@
1
- import { ActionsObservable } from 'redux-observable';
2
- import { Observable } from 'rxjs';
3
- import { ZeniAPI } from '../../../zeniAPI';
4
- import { deleteRoutine, deleteRoutineFailure, deleteRoutineSuccess, fetchRoutines } from '../aiCfoViewReducer';
5
- export type ActionType = ReturnType<typeof deleteRoutine> | ReturnType<typeof deleteRoutineSuccess> | ReturnType<typeof deleteRoutineFailure> | ReturnType<typeof fetchRoutines>;
6
- /**
7
- * DELETE /1.0/routines/{scheduleId}.
8
- *
9
- * The row has already left the list by the time this runs — the reducer removes
10
- * it optimistically so the click does not feel ignored.
11
- *
12
- * A failure therefore has to put it BACK, not merely report. The routine still
13
- * exists and is still emailing the user their financials on a schedule; leaving
14
- * it hidden would tell them they had stopped something they had not. So a
15
- * failure drops the tombstone and refetches, and the error explains why the row
16
- * returned.
17
- */
18
- export declare const deleteRoutineEpic: (actions$: ActionsObservable<ActionType>, _state$: unknown, zeniAPI: ZeniAPI) => Observable<ActionType>;
@@ -1,39 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deleteRoutineEpic = void 0;
4
- const rxjs_1 = require("rxjs");
5
- const operators_1 = require("rxjs/operators");
6
- const responsePayload_1 = require("../../../responsePayload");
7
- const aiCfoViewReducer_1 = require("../aiCfoViewReducer");
8
- /**
9
- * DELETE /1.0/routines/{scheduleId}.
10
- *
11
- * The row has already left the list by the time this runs — the reducer removes
12
- * it optimistically so the click does not feel ignored.
13
- *
14
- * A failure therefore has to put it BACK, not merely report. The routine still
15
- * exists and is still emailing the user their financials on a schedule; leaving
16
- * it hidden would tell them they had stopped something they had not. So a
17
- * failure drops the tombstone and refetches, and the error explains why the row
18
- * returned.
19
- */
20
- const deleteRoutineEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(aiCfoViewReducer_1.deleteRoutine.match),
21
- // mergeMap: deleting one routine must not cancel the delete of another.
22
- (0, operators_1.mergeMap)((action) => {
23
- const { scheduleId } = action.payload;
24
- return zeniAPI
25
- .deleteAndGetJSON(`${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/routines/${encodeURIComponent(scheduleId)}`)
26
- .pipe((0, operators_1.mergeMap)((response) =>
27
- // isSuccessStatus, NOT isSuccessResponse: the latter also requires a
28
- // non-null `data`, and a successful DELETE here is a 204 with no body.
29
- // deleteSkillEpic carries the same note for the same reason — using
30
- // the wrong one reported every completed delete as a failure, which
31
- // here would also drop the tombstone and refetch the row back.
32
- (0, responsePayload_1.isSuccessStatus)(response)
33
- ? (0, rxjs_1.of)((0, aiCfoViewReducer_1.deleteRoutineSuccess)({ scheduleId }))
34
- : (0, rxjs_1.of)((0, aiCfoViewReducer_1.deleteRoutineFailure)({ scheduleId, status: response.status }), (0, aiCfoViewReducer_1.fetchRoutines)())), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, aiCfoViewReducer_1.deleteRoutineFailure)({
35
- scheduleId,
36
- status: (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Delete routine REST API call failed', error instanceof Error ? { message: error.message } : undefined),
37
- }), (0, aiCfoViewReducer_1.fetchRoutines)())));
38
- }));
39
- exports.deleteRoutineEpic = deleteRoutineEpic;
@@ -1,13 +0,0 @@
1
- import { ActionsObservable } from 'redux-observable';
2
- import { Observable } from 'rxjs';
3
- import { ZeniAPI } from '../../../zeniAPI';
4
- import { fetchRoutineRuns, fetchRoutineRunsFailure, fetchRoutineRunsSuccess } from '../aiCfoViewReducer';
5
- export type ActionType = ReturnType<typeof fetchRoutineRuns> | ReturnType<typeof fetchRoutineRunsSuccess> | ReturnType<typeof fetchRoutineRunsFailure>;
6
- /**
7
- * GET /1.0/routines/{scheduleId}/runs — one routine's run history.
8
- *
9
- * switchMap: only the history the user has open matters, so expanding a second
10
- * routine while the first is still loading should abandon the first rather than
11
- * race it into the single slot the state keeps.
12
- */
13
- export declare const fetchRoutineRunsEpic: (actions$: ActionsObservable<ActionType>, _state$: unknown, zeniAPI: ZeniAPI) => Observable<ActionType>;
@@ -1,39 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fetchRoutineRunsEpic = void 0;
4
- const rxjs_1 = require("rxjs");
5
- const operators_1 = require("rxjs/operators");
6
- const responsePayload_1 = require("../../../responsePayload");
7
- const aiCfoViewReducer_1 = require("../aiCfoViewReducer");
8
- const toRoutineRun_1 = require("./toRoutineRun");
9
- /**
10
- * GET /1.0/routines/{scheduleId}/runs — one routine's run history.
11
- *
12
- * switchMap: only the history the user has open matters, so expanding a second
13
- * routine while the first is still loading should abandon the first rather than
14
- * race it into the single slot the state keeps.
15
- */
16
- const fetchRoutineRunsEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(aiCfoViewReducer_1.fetchRoutineRuns.match), (0, operators_1.switchMap)((action) => {
17
- const { scheduleId } = action.payload;
18
- return zeniAPI
19
- .getJSON(`${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/routines/${encodeURIComponent(scheduleId)}/runs`)
20
- .pipe((0, operators_1.map)((response) => {
21
- // The explicit `data == null` is what narrows the type — the helper
22
- // returns a plain boolean, not a type guard — and it means a
23
- // malformed 2xx becomes a failure rather than an empty history,
24
- // which would read as "this has never run" when it has.
25
- if (!(0, responsePayload_1.isSuccessResponse)(response) || response.data == null) {
26
- return (0, aiCfoViewReducer_1.fetchRoutineRunsFailure)(response.status);
27
- }
28
- const { data } = response;
29
- return (0, aiCfoViewReducer_1.fetchRoutineRunsSuccess)({
30
- // Filtered, not trusted: one malformed row must not take the
31
- // whole history down, and a run without an id is not addressable
32
- // by anything the UI does with it.
33
- runs: (data.runs ?? []).filter(toRoutineRun_1.isRoutineRunPayload).map(toRoutineRun_1.toRoutineRun),
34
- scheduleId,
35
- sessionId: data.session_id ?? null,
36
- });
37
- }), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, aiCfoViewReducer_1.fetchRoutineRunsFailure)((0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Fetch routine runs REST API call failed', error instanceof Error ? { message: error.message } : undefined)))));
38
- }));
39
- exports.fetchRoutineRunsEpic = fetchRoutineRunsEpic;
@@ -1,7 +0,0 @@
1
- import { ActionsObservable } from 'redux-observable';
2
- import { Observable } from 'rxjs';
3
- import { ZeniAPI } from '../../../zeniAPI';
4
- import { fetchRoutines, fetchRoutinesFailure, fetchRoutinesSuccess } from '../aiCfoViewReducer';
5
- export type ActionType = ReturnType<typeof fetchRoutines> | ReturnType<typeof fetchRoutinesSuccess> | ReturnType<typeof fetchRoutinesFailure>;
6
- /** GET /1.0/routines — the caller's own scheduled runs. */
7
- export declare const fetchRoutinesEpic: (actions$: ActionsObservable<ActionType>, _state$: unknown, zeniAPI: ZeniAPI) => Observable<ActionType>;