@zeniai/client-epic-state 5.3.12 → 5.3.14-beta0MM

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 (57) hide show
  1. package/lib/commonStateTypes/animations.d.ts +1 -1
  2. package/lib/commonStateTypes/animations.js +1 -2
  3. package/lib/entity/actualMatching/actualMatchingPayload.d.ts +1 -0
  4. package/lib/entity/actualMatching/actualMatchingPayload.js +3 -0
  5. package/lib/entity/actualMatching/actualMatchingReducer.js +2 -1
  6. package/lib/entity/actualMatching/actualMatchingState.d.ts +1 -0
  7. package/lib/entity/actualMatching/actualMatchingTypes.d.ts +2 -0
  8. package/lib/entity/actualMatching/actualMatchingTypes.js +9 -0
  9. package/lib/epic.d.ts +2 -1
  10. package/lib/epic.js +2 -0
  11. package/lib/esm/commonStateTypes/animations.js +1 -2
  12. package/lib/esm/entity/actualMatching/actualMatchingPayload.js +3 -0
  13. package/lib/esm/entity/actualMatching/actualMatchingReducer.js +2 -1
  14. package/lib/esm/entity/actualMatching/actualMatchingTypes.js +8 -1
  15. package/lib/esm/epic.js +2 -0
  16. package/lib/esm/index.js +5 -4
  17. package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +65 -3
  18. package/lib/esm/view/aiCfoView/aiCfoViewSelector.js +25 -0
  19. package/lib/esm/view/aiCfoView/epics/transcribeAudioEpic.js +60 -0
  20. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingEpic.js +2 -1
  21. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingMatchEpic.js +8 -2
  22. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/matchAndReverseAccrualsEpic.js +7 -3
  23. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/actualMatching/undoReversalAccrualsEpic.js +12 -7
  24. package/lib/esm/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +30 -23
  25. package/lib/esm/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +1 -0
  26. package/lib/esm/view/expenseAutomationView/types/transactionGroup.js +10 -5
  27. package/lib/esm/view/transactionDetail/createTransaction/createTransactionPayload.js +19 -3
  28. package/lib/esm/view/transactionDetail/createTransaction/createTransactionState.js +2 -0
  29. package/lib/index.d.ts +6 -5
  30. package/lib/index.js +30 -22
  31. package/lib/view/aiCfoView/aiCfoViewPayload.d.ts +20 -0
  32. package/lib/view/aiCfoView/aiCfoViewReducer.d.ts +2 -2
  33. package/lib/view/aiCfoView/aiCfoViewReducer.js +66 -4
  34. package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +8 -1
  35. package/lib/view/aiCfoView/aiCfoViewSelector.js +27 -0
  36. package/lib/view/aiCfoView/aiCfoViewState.d.ts +9 -0
  37. package/lib/view/aiCfoView/epics/transcribeAudioEpic.d.ts +19 -0
  38. package/lib/view/aiCfoView/epics/transcribeAudioEpic.js +64 -0
  39. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingEpic.js +2 -1
  40. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/fetchActualMatchingMatchEpic.js +8 -2
  41. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/matchAndReverseAccrualsEpic.js +7 -3
  42. package/lib/view/expenseAutomationView/epics/jeSchedule/actualMatching/undoReversalAccrualsEpic.js +12 -7
  43. package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +2 -2
  44. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.d.ts +9 -3
  45. package/lib/view/expenseAutomationView/reducers/actualMatchingViewReducer.js +30 -23
  46. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +2 -2
  47. package/lib/view/expenseAutomationView/selectorTypes/actualMatchingViewSelectorTypes.d.ts +1 -0
  48. package/lib/view/expenseAutomationView/selectors/actualMatchingViewSelector.js +1 -0
  49. package/lib/view/expenseAutomationView/types/transactionGroup.d.ts +6 -6
  50. package/lib/view/expenseAutomationView/types/transactionGroup.js +10 -5
  51. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +2 -2
  52. package/lib/view/transactionDetail/createTransaction/createTransactionPayload.d.ts +6 -3
  53. package/lib/view/transactionDetail/createTransaction/createTransactionPayload.js +19 -3
  54. package/lib/view/transactionDetail/createTransaction/createTransactionState.d.ts +7 -2
  55. package/lib/view/transactionDetail/createTransaction/createTransactionState.js +2 -0
  56. package/lib/view/transactionDetail/transactionDetailReducer.d.ts +20 -20
  57. package/package.json +1 -1
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.transcribeAudioEpic = void 0;
4
+ const rxjs_1 = require("rxjs");
5
+ const operators_1 = require("rxjs/operators");
6
+ const responsePayload_1 = require("../../../responsePayload");
7
+ const rootActions_1 = require("../../../rootActions");
8
+ const aiCfoViewReducer_1 = require("../aiCfoViewReducer");
9
+ const AUDIO_FIELD = 'audio';
10
+ const LANGUAGE_FIELD = 'language';
11
+ /** Extension only has to match the recorded format; the server reads the part's MIME type. */
12
+ const AUDIO_FILE_NAME = 'dictation.wav';
13
+ /**
14
+ * A hung request would otherwise pin `isTranscribing` true forever, and the composer
15
+ * gates both submit and the mic on it — leaving the user unable to send even typed
16
+ * text until something else resolved it.
17
+ */
18
+ const TRANSCRIPTION_TIMEOUT_MS = 30000;
19
+ /**
20
+ * Voice dictation for the AI CFO composer. Dispatch `transcribeAudio(blob, language)` with a
21
+ * finished recording to POST it to the AI CFO micro-service `/1.0/transcriptions` endpoint;
22
+ * on success the transcript is appended to the composer input for the user to edit and send.
23
+ *
24
+ * `switchMap` (not `mergeMap`): a newer recording supersedes an older in-flight one, so a
25
+ * slow response can never append a stale transcript after a fresh one has already landed.
26
+ * The AbortController makes that cancellation real — `fetch` wrapped in a promise ignores
27
+ * an RxJS unsubscribe, so without the signal the superseded request would run to completion
28
+ * and still bill a transcription call whose result nothing wants.
29
+ */
30
+ const transcribeAudioEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(aiCfoViewReducer_1.transcribeAudio.match), (0, operators_1.switchMap)((action) => {
31
+ const { audio, chatSessionId, language } = action.payload;
32
+ const abortController = new AbortController();
33
+ const formData = new FormData();
34
+ formData.append(AUDIO_FIELD, audio, AUDIO_FILE_NAME);
35
+ if (language != null && language !== '') {
36
+ formData.append(LANGUAGE_FIELD, language);
37
+ }
38
+ return zeniAPI
39
+ .postFormData(`${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/transcriptions`, formData, abortController.signal)
40
+ .pipe((0, operators_1.timeout)(TRANSCRIPTION_TIMEOUT_MS),
41
+ // A tenant switch clears the store; a transcript still in flight must not
42
+ // land in the next tenant's composer (both sessions can be `undefined`, so
43
+ // the reducer's session guard would let it through).
44
+ (0, operators_1.takeUntil)(actions$.pipe((0, operators_1.filter)(rootActions_1.clearAll.match))), (0, operators_1.map)((response) => {
45
+ if (!(0, responsePayload_1.isSuccessResponse)(response)) {
46
+ return (0, aiCfoViewReducer_1.transcribeAudioFailure)(response.status);
47
+ }
48
+ // An EMPTY transcript is a success (the mic caught silence) and the
49
+ // reducer leaves the composer alone. A MISSING one breaks the response
50
+ // contract, so it surfaces as an error rather than being coerced to
51
+ // silence the user cannot distinguish from a quiet room.
52
+ if (response.data?.transcript == null) {
53
+ return (0, aiCfoViewReducer_1.transcribeAudioFailure)((0, responsePayload_1.createZeniAPIStatus)('Transcription succeeded but the response carried no transcript', 'Unexpected Error'));
54
+ }
55
+ // The session id rides along so a transcript that lands after the user
56
+ // switched sessions is dropped rather than misfiled.
57
+ return (0, aiCfoViewReducer_1.transcribeAudioSuccess)(response.data.transcript, chatSessionId);
58
+ }), (0, operators_1.finalize)(() => abortController.abort()), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, aiCfoViewReducer_1.transcribeAudioFailure)((0, responsePayload_1.createZeniAPIStatus)(
59
+ // message (detailed) first, then the short title — per createZeniAPIStatus(message, title).
60
+ // JSON.stringify(Error) drops the (non-enumerable) message — extract it explicitly.
61
+ 'Transcription REST API call errored out: ' +
62
+ (error instanceof Error ? error.message : String(error)), 'Unexpected Error')))));
63
+ }));
64
+ exports.transcribeAudioEpic = transcribeAudioEpic;
@@ -4,6 +4,7 @@ exports.fetchActualMatchingEpic = void 0;
4
4
  const rxjs_1 = require("rxjs");
5
5
  const operators_1 = require("rxjs/operators");
6
6
  const actualMatchingReducer_1 = require("../../../../../entity/actualMatching/actualMatchingReducer");
7
+ const actualMatchingTypes_1 = require("../../../../../entity/actualMatching/actualMatchingTypes");
7
8
  const responsePayload_1 = require("../../../../../responsePayload");
8
9
  const rootActions_1 = require("../../../../../rootActions");
9
10
  const actualMatchingPayload_1 = require("../../../payload/actualMatchingPayload");
@@ -29,7 +30,7 @@ const fetchActualMatchingEpic = (actions$, state$, zeniAPI) => actions$.pipe((0,
29
30
  updateActions.push((0, actualMatchingReducer_1.updateActualMatchingMatches)(matches, (0, actualMatchingViewSelector_1.getTenantDisplayCurrency)(state)));
30
31
  }
31
32
  updateActions.push((0, actualMatchingViewReducer_1.fetchActualMatchingSuccess)({
32
- matchIds: matches.map((match) => match.match_id),
33
+ matchIds: matches.map((match) => (0, actualMatchingTypes_1.actualMatchingRowKey)(match.match_id, match.match_batch_id)),
33
34
  selectedPeriod: requestPeriod,
34
35
  refreshViewInBackground,
35
36
  }));
@@ -9,15 +9,19 @@ const rootActions_1 = require("../../../../../rootActions");
9
9
  const actualMatchingViewReducer_1 = require("../../../reducers/actualMatchingViewReducer");
10
10
  const actualMatchingViewSelector_1 = require("../../../selectors/actualMatchingViewSelector");
11
11
  const fetchActualMatchingMatchEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(actualMatchingViewReducer_1.fetchActualMatchingMatch.match), (0, operators_1.mergeMap)((action) => {
12
- const { matchId, refreshViewInBackground } = action.payload;
12
+ const { matchId, matchBatchId, refreshViewInBackground } = action.payload;
13
+ const batchQuery = matchBatchId != null && matchBatchId !== ''
14
+ ? `?batch_id=${encodeURIComponent(matchBatchId)}`
15
+ : '';
13
16
  return zeniAPI
14
- .getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals/actual-matching/${encodeURIComponent(matchId)}`)
17
+ .getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals/actual-matching/${encodeURIComponent(matchId)}${batchQuery}`)
15
18
  .pipe((0, operators_1.mergeMap)((response) => {
16
19
  if ((0, responsePayload_1.isSuccessResponse)(response) && response.data != null) {
17
20
  return (0, rxjs_1.from)([
18
21
  (0, actualMatchingReducer_1.updateActualMatchingMatches)([response.data], (0, actualMatchingViewSelector_1.getTenantDisplayCurrency)(state$.value)),
19
22
  (0, actualMatchingViewReducer_1.fetchActualMatchingMatchSuccess)({
20
23
  matchId,
24
+ matchBatchId,
21
25
  refreshViewInBackground,
22
26
  }),
23
27
  ]);
@@ -25,6 +29,7 @@ const fetchActualMatchingMatchEpic = (actions$, state$, zeniAPI) => actions$.pip
25
29
  return (0, rxjs_1.from)([
26
30
  (0, actualMatchingViewReducer_1.fetchActualMatchingMatchFailure)({
27
31
  matchId,
32
+ matchBatchId,
28
33
  refreshViewInBackground,
29
34
  status: response.status,
30
35
  }),
@@ -32,6 +37,7 @@ const fetchActualMatchingMatchEpic = (actions$, state$, zeniAPI) => actions$.pip
32
37
  }), (0, operators_1.catchError)((error) => (0, rxjs_1.from)([
33
38
  (0, actualMatchingViewReducer_1.fetchActualMatchingMatchFailure)({
34
39
  matchId,
40
+ matchBatchId,
35
41
  refreshViewInBackground,
36
42
  status: (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', `Fetch actual matching match REST API call errored out ${JSON.stringify(error)}`),
37
43
  }),
@@ -59,8 +59,12 @@ function matchAndReverseHttpFailSnackbar(vendorName) {
59
59
  });
60
60
  }
61
61
  function matchVendorName(state, matchId) {
62
- return ((0, actualMatchingSelector_1.getActualMatchingMatchById)(state.actualMatchingState, matchId)
63
- ?.vendorName ?? '');
62
+ const stored = (0, actualMatchingSelector_1.getActualMatchingMatchById)(state.actualMatchingState, matchId);
63
+ if (stored?.vendorName != null && stored.vendorName !== '') {
64
+ return stored.vendorName;
65
+ }
66
+ const sameBill = Object.values(state.actualMatchingState.byMatchId).find((match) => match.matchId === matchId);
67
+ return sameBill?.vendorName ?? '';
64
68
  }
65
69
  const matchAndReverseAccrualsEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(actualMatchingViewReducer_1.matchAndReverseAccruals.match),
66
70
  // Per-match exhaust: a second click on the same match drops while HTTP is in
@@ -99,7 +103,7 @@ const matchAndReverseAccrualsEpic = (actions$, state$, zeniAPI) => actions$.pipe
99
103
  // List + detail: list can omit leftovers; detail GET always
100
104
  // writes intended occupancy so leftover retry can join C
101
105
  // onto the occupied bill instead of a stale cached row.
102
- successActions.push(...(0, refreshActualMatchingList_1.refreshActualMatchingList)(state$.value), (0, actualMatchingViewReducer_1.fetchActualMatchingMatch)(matchId, true));
106
+ successActions.push(...(0, refreshActualMatchingList_1.refreshActualMatchingList)(state$.value), (0, actualMatchingViewReducer_1.fetchActualMatchingMatch)(matchId, true, response.data.match_batch_id));
103
107
  }
104
108
  const snackbar = matchAndReverseSnackbar(results, vendorName);
105
109
  if (snackbar != null) {
@@ -4,6 +4,7 @@ exports.undoReversalAccrualsEpic = void 0;
4
4
  const rxjs_1 = require("rxjs");
5
5
  const operators_1 = require("rxjs/operators");
6
6
  const actualMatchingSelector_1 = require("../../../../../entity/actualMatching/actualMatchingSelector");
7
+ const actualMatchingTypes_1 = require("../../../../../entity/actualMatching/actualMatchingTypes");
7
8
  const snackbarReducer_1 = require("../../../../../entity/snackbar/snackbarReducer");
8
9
  const responsePayload_1 = require("../../../../../responsePayload");
9
10
  const rootActions_1 = require("../../../../../rootActions");
@@ -40,41 +41,45 @@ function undoReversalSnackbar(results, vendorName) {
40
41
  variables,
41
42
  });
42
43
  }
43
- function matchVendorName(state, matchId) {
44
- return ((0, actualMatchingSelector_1.getActualMatchingMatchById)(state.actualMatchingState, matchId)?.vendorName ??
44
+ function matchVendorName(state, matchId, matchBatchId) {
45
+ const rowKey = (0, actualMatchingTypes_1.actualMatchingRowKey)(matchId, matchBatchId);
46
+ return ((0, actualMatchingSelector_1.getActualMatchingMatchById)(state.actualMatchingState, rowKey)?.vendorName ??
47
+ (0, actualMatchingSelector_1.getActualMatchingMatchById)(state.actualMatchingState, matchId)?.vendorName ??
45
48
  '');
46
49
  }
47
50
  const undoReversalAccrualsEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(actualMatchingViewReducer_1.undoReversalAccruals.match),
48
51
  // Per-match exhaust: a second click on the same match drops while HTTP is in
49
52
  // flight. A different match must still run — a global exhaustMap left the
50
53
  // other match's reducer In-Progress with no request.
51
- (0, operators_1.groupBy)((action) => action.payload.matchId), (0, operators_1.mergeMap)((perMatch$) => perMatch$.pipe((0, operators_1.exhaustMap)((action) => {
52
- const { accrualIds, matchId } = action.payload;
54
+ (0, operators_1.groupBy)((action) => (0, actualMatchingTypes_1.actualMatchingRowKey)(action.payload.matchId, action.payload.matchBatchId)), (0, operators_1.mergeMap)((perMatch$) => perMatch$.pipe((0, operators_1.exhaustMap)((action) => {
55
+ const { accrualIds, matchId, matchBatchId } = action.payload;
53
56
  const body = {
54
57
  accrual_ids: accrualIds,
55
58
  };
56
- const vendorName = matchVendorName(state$.value, matchId);
59
+ const vendorName = matchVendorName(state$.value, matchId, matchBatchId);
57
60
  return zeniAPI
58
61
  .postAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/accruals/undo-reversal`, body)
59
62
  .pipe((0, operators_1.mergeMap)((response) => {
60
63
  if ((0, responsePayload_1.isSuccessResponse)(response) && response.data != null) {
61
64
  const results = response.data.results.map(actualMatchingPayload_1.toUndoReversalResult);
62
65
  const successActions = [
63
- (0, actualMatchingViewReducer_1.undoReversalAccrualsSuccess)(matchId, results),
66
+ (0, actualMatchingViewReducer_1.undoReversalAccrualsSuccess)(matchId, results, matchBatchId),
64
67
  ];
65
68
  const anyUndone = results.some((result) => result.status === 'undone');
66
69
  if (anyUndone) {
67
- successActions.push(...(0, refreshActualMatchingList_1.refreshActualMatchingList)(state$.value), (0, actualMatchingViewReducer_1.fetchActualMatchingMatch)(matchId, true));
70
+ successActions.push(...(0, refreshActualMatchingList_1.refreshActualMatchingList)(state$.value), (0, actualMatchingViewReducer_1.fetchActualMatchingMatch)(matchId, true, matchBatchId));
68
71
  }
69
72
  successActions.push(undoReversalSnackbar(results, vendorName));
70
73
  return (0, rxjs_1.from)(successActions);
71
74
  }
72
75
  return (0, rxjs_1.of)((0, actualMatchingViewReducer_1.undoReversalAccrualsFailure)({
73
76
  matchId,
77
+ matchBatchId,
74
78
  status: response.status,
75
79
  }), undoReversalSnackbar([], vendorName));
76
80
  }), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, actualMatchingViewReducer_1.undoReversalAccrualsFailure)({
77
81
  matchId,
82
+ matchBatchId,
78
83
  status: (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Undo reversal accruals REST API call errored out' +
79
84
  JSON.stringify(error)),
80
85
  }), undoReversalSnackbar([], vendorName))), (0, operators_1.takeUntil)(actions$.pipe((0, operators_1.filter)(rootActions_1.clearAll.match))));
@@ -28,8 +28,8 @@ export interface TransactionCategorizationQueryPayload {
28
28
  */
29
29
  search_all_statuses?: boolean;
30
30
  /**
31
- * Statement-level scope (`'expense' | 'income' | 'all'`). Always sent by the
32
- * list epics. The backend `is_revenue_automation_enabled` gate is
31
+ * Statement-level scope (`'expense' | 'income' | 'other' | 'all'`). Always
32
+ * sent by the list epics. The backend `is_revenue_automation_enabled` gate is
33
33
  * authoritative: when it is off the backend ignores this and returns the
34
34
  * legacy expense scope regardless of the value, so there is no need to gate
35
35
  * the send on the client (the gate isn't available to the redux epics anyway
@@ -14,16 +14,19 @@ export declare const ackCloseMatchAfterIgnore: import("@reduxjs/toolkit").Action
14
14
  refreshViewInBackground: boolean;
15
15
  selectedPeriod: MonthYearPeriod;
16
16
  status: ZeniAPIStatus;
17
- }, "expenseAutomationActualMatchingView/fetchActualMatchingFailure">, fetchActualMatchingMatch: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[matchId: string, refreshViewInBackground?: any], {
17
+ }, "expenseAutomationActualMatchingView/fetchActualMatchingFailure">, fetchActualMatchingMatch: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[matchId: string, refreshViewInBackground?: any, matchBatchId?: string | undefined], {
18
18
  matchId: string;
19
+ matchBatchId: string | undefined;
19
20
  refreshViewInBackground: any;
20
21
  }, "expenseAutomationActualMatchingView/fetchActualMatchingMatch", never, never>, fetchActualMatchingMatchFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
21
22
  matchId: ActualMatchingMatchId;
22
23
  refreshViewInBackground: boolean;
23
24
  status: ZeniAPIStatus;
25
+ matchBatchId?: string;
24
26
  }, "expenseAutomationActualMatchingView/fetchActualMatchingMatchFailure">, fetchActualMatchingMatchSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
25
27
  matchId: ActualMatchingMatchId;
26
28
  refreshViewInBackground: boolean;
29
+ matchBatchId?: string;
27
30
  }, "expenseAutomationActualMatchingView/fetchActualMatchingMatchSuccess">, fetchActualMatchingPage: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[period: TimePeriod, refreshViewInBackground?: any], {
28
31
  period: TimePeriod;
29
32
  refreshViewInBackground: any;
@@ -76,15 +79,18 @@ export declare const ackCloseMatchAfterIgnore: import("@reduxjs/toolkit").Action
76
79
  append: boolean;
77
80
  nextPageToken: string | null;
78
81
  results: ActualMatchingTransactionSearchResult[];
79
- }, "expenseAutomationActualMatchingView/searchMatchTransactionsSuccess">, undoReversalAccruals: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[matchId: string, accrualIds: string[]], {
82
+ }, "expenseAutomationActualMatchingView/searchMatchTransactionsSuccess">, undoReversalAccruals: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[matchId: string, accrualIds: string[], matchBatchId?: string | undefined], {
80
83
  matchId: string;
81
84
  accrualIds: string[];
85
+ matchBatchId: string | undefined;
82
86
  }, "expenseAutomationActualMatchingView/undoReversalAccruals", never, never>, undoReversalAccrualsFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
83
87
  matchId: ActualMatchingMatchId;
84
88
  status: ZeniAPIStatus;
85
- }, "expenseAutomationActualMatchingView/undoReversalAccrualsFailure">, undoReversalAccrualsSuccess: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[matchId: string, results: UndoReversalResult[]], {
89
+ matchBatchId?: string;
90
+ }, "expenseAutomationActualMatchingView/undoReversalAccrualsFailure">, undoReversalAccrualsSuccess: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[matchId: string, results: UndoReversalResult[], matchBatchId?: string | undefined], {
86
91
  matchId: string;
87
92
  results: UndoReversalResult[];
93
+ matchBatchId: string | undefined;
88
94
  }, "expenseAutomationActualMatchingView/undoReversalAccrualsSuccess", never, never>, updateActualMatchingUIState: import("@reduxjs/toolkit").ActionCreatorWithPayload<Partial<ActualMatchingViewUIState>, "expenseAutomationActualMatchingView/updateActualMatchingUIState">;
89
95
  declare const _default: import("redux").Reducer<ActualMatchingViewState>;
90
96
  export default _default;
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.updateActualMatchingUIState = exports.undoReversalAccrualsSuccess = exports.undoReversalAccrualsFailure = exports.undoReversalAccruals = exports.searchMatchTransactionsSuccess = exports.searchMatchTransactionsFailure = exports.searchMatchTransactions = exports.searchMatchAccrualsSuccess = exports.searchMatchAccrualsFailure = exports.searchMatchAccruals = exports.openMatchNewModal = exports.matchAndReverseAccrualsSuccess = exports.matchAndReverseAccrualsFailure = exports.matchAndReverseAccruals = exports.ignoreActualMatchingSuggestionSuccess = exports.ignoreActualMatchingSuggestionFailure = exports.ignoreActualMatchingSuggestion = exports.fetchActualMatchingSuccess = exports.fetchActualMatchingPage = exports.fetchActualMatchingMatchSuccess = exports.fetchActualMatchingMatchFailure = exports.fetchActualMatchingMatch = exports.fetchActualMatchingFailure = exports.fetchActualMatching = exports.closeMatchNewModal = exports.clearMatchNewModal = exports.clearActualMatchingView = exports.ackCloseMatchAfterUndo = exports.ackCloseMatchAfterIgnore = exports.initialState = exports.initialMatchModalState = exports.initialAccrualSearchState = exports.initialTransactionSearchState = void 0;
5
5
  const toolkit_1 = require("@reduxjs/toolkit");
6
6
  const timePeriod_1 = require("../../../commonStateTypes/timePeriod");
7
+ const actualMatchingTypes_1 = require("../../../entity/actualMatching/actualMatchingTypes");
7
8
  const jeScheduleHelper_1 = require("../../../entity/jeSchedules/jeScheduleHelper");
8
9
  const responsePayload_1 = require("../../../responsePayload");
9
10
  const rootActions_1 = require("../../../rootActions");
@@ -187,19 +188,20 @@ const expenseAutomationActualMatchingView = (0, toolkit_1.createSlice)({
187
188
  }
188
189
  },
189
190
  fetchActualMatchingMatch: {
190
- prepare(matchId, refreshViewInBackground = false) {
191
- return { payload: { matchId, refreshViewInBackground } };
191
+ prepare(matchId, refreshViewInBackground = false, matchBatchId) {
192
+ return { payload: { matchId, matchBatchId, refreshViewInBackground } };
192
193
  },
193
194
  reducer(draft, action) {
194
- const { matchId, refreshViewInBackground } = action.payload;
195
+ const { matchId, matchBatchId, refreshViewInBackground } = action.payload;
196
+ const rowKey = (0, actualMatchingTypes_1.actualMatchingRowKey)(matchId, matchBatchId);
195
197
  if (refreshViewInBackground) {
196
- draft.detailRefreshStatusByMatchId[matchId] = {
198
+ draft.detailRefreshStatusByMatchId[rowKey] = {
197
199
  fetchState: 'In-Progress',
198
200
  error: undefined,
199
201
  };
200
202
  }
201
203
  else {
202
- draft.detailFetchStateByMatchId[matchId] = {
204
+ draft.detailFetchStateByMatchId[rowKey] = {
203
205
  fetchState: 'In-Progress',
204
206
  error: undefined,
205
207
  };
@@ -207,30 +209,32 @@ const expenseAutomationActualMatchingView = (0, toolkit_1.createSlice)({
207
209
  },
208
210
  },
209
211
  fetchActualMatchingMatchSuccess(draft, action) {
210
- const { matchId, refreshViewInBackground } = action.payload;
212
+ const { matchId, matchBatchId, refreshViewInBackground } = action.payload;
213
+ const rowKey = (0, actualMatchingTypes_1.actualMatchingRowKey)(matchId, matchBatchId);
211
214
  if (refreshViewInBackground) {
212
- draft.detailRefreshStatusByMatchId[matchId] = {
215
+ draft.detailRefreshStatusByMatchId[rowKey] = {
213
216
  fetchState: 'Completed',
214
217
  error: undefined,
215
218
  };
216
219
  }
217
220
  else {
218
- draft.detailFetchStateByMatchId[matchId] = {
221
+ draft.detailFetchStateByMatchId[rowKey] = {
219
222
  fetchState: 'Completed',
220
223
  error: undefined,
221
224
  };
222
225
  }
223
226
  },
224
227
  fetchActualMatchingMatchFailure(draft, action) {
225
- const { matchId, refreshViewInBackground, status } = action.payload;
228
+ const { matchId, matchBatchId, refreshViewInBackground, status } = action.payload;
229
+ const rowKey = (0, actualMatchingTypes_1.actualMatchingRowKey)(matchId, matchBatchId);
226
230
  if (refreshViewInBackground) {
227
- draft.detailRefreshStatusByMatchId[matchId] = {
231
+ draft.detailRefreshStatusByMatchId[rowKey] = {
228
232
  fetchState: 'Error',
229
233
  error: status,
230
234
  };
231
235
  }
232
236
  else {
233
- draft.detailFetchStateByMatchId[matchId] = {
237
+ draft.detailFetchStateByMatchId[rowKey] = {
234
238
  fetchState: 'Error',
235
239
  error: status,
236
240
  };
@@ -421,40 +425,42 @@ const expenseAutomationActualMatchingView = (0, toolkit_1.createSlice)({
421
425
  },
422
426
  undoReversalAccruals: {
423
427
  reducer(draft, action) {
424
- draft.undoReversalStatusByMatchId[action.payload.matchId] = {
428
+ const rowKey = (0, actualMatchingTypes_1.actualMatchingRowKey)(action.payload.matchId, action.payload.matchBatchId);
429
+ draft.undoReversalStatusByMatchId[rowKey] = {
425
430
  fetchState: 'In-Progress',
426
431
  error: undefined,
427
432
  };
428
433
  },
429
- prepare(matchId, accrualIds) {
430
- return { payload: { matchId, accrualIds } };
434
+ prepare(matchId, accrualIds, matchBatchId) {
435
+ return { payload: { matchId, accrualIds, matchBatchId } };
431
436
  },
432
437
  },
433
438
  undoReversalAccrualsSuccess: {
434
439
  reducer(draft, action) {
435
- const { matchId, results } = action.payload;
436
- draft.undoReversalStatusByMatchId[matchId] = {
440
+ const { matchId, matchBatchId, results } = action.payload;
441
+ const rowKey = (0, actualMatchingTypes_1.actualMatchingRowKey)(matchId, matchBatchId);
442
+ draft.undoReversalStatusByMatchId[rowKey] = {
437
443
  fetchState: 'Completed',
438
444
  error: undefined,
439
445
  };
440
446
  const allUndone = results.length > 0 &&
441
447
  results.every((result) => result.status === 'undone');
442
448
  if (allUndone) {
443
- enqueueCloseMatchId(draft.closeMatchAfterUndoMatchIds, matchId);
449
+ enqueueCloseMatchId(draft.closeMatchAfterUndoMatchIds, rowKey);
444
450
  }
445
451
  else {
446
- draft.closeMatchAfterUndoMatchIds = dropCloseMatchId(draft.closeMatchAfterUndoMatchIds, matchId);
452
+ draft.closeMatchAfterUndoMatchIds = dropCloseMatchId(draft.closeMatchAfterUndoMatchIds, rowKey);
447
453
  }
448
454
  pruneLastMatchResultsForUndone(draft, matchId, results);
449
455
  if (allUndone) {
450
- delete draft.lastUndoResultsByMatchId[matchId];
456
+ delete draft.lastUndoResultsByMatchId[rowKey];
451
457
  }
452
458
  else {
453
- draft.lastUndoResultsByMatchId[matchId] = results;
459
+ draft.lastUndoResultsByMatchId[rowKey] = results;
454
460
  }
455
461
  },
456
- prepare(matchId, results) {
457
- return { payload: { matchId, results } };
462
+ prepare(matchId, results, matchBatchId) {
463
+ return { payload: { matchId, results, matchBatchId } };
458
464
  },
459
465
  },
460
466
  ackCloseMatchAfterUndo(draft) {
@@ -498,7 +504,8 @@ const expenseAutomationActualMatchingView = (0, toolkit_1.createSlice)({
498
504
  draft.closeMatchAfterIgnoreMatchIds.shift();
499
505
  },
500
506
  undoReversalAccrualsFailure(draft, action) {
501
- draft.undoReversalStatusByMatchId[action.payload.matchId] = {
507
+ const rowKey = (0, actualMatchingTypes_1.actualMatchingRowKey)(action.payload.matchId, action.payload.matchBatchId);
508
+ draft.undoReversalStatusByMatchId[rowKey] = {
502
509
  fetchState: 'Error',
503
510
  error: action.payload.status,
504
511
  };
@@ -70,8 +70,8 @@ export declare const fetchTransactionCategorization: import("@reduxjs/toolkit").
70
70
  newSelectedTab: "review" | "autoCategorized";
71
71
  }, "expenseAutomationTransactionsView/updateCurrentSelectedTransactionCategorizationTab", never, never>, updateTransactionCategorizationCompletedSubTab: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[newSubTab: "manual" | "all" | "ai_accountant"], {
72
72
  newSubTab: "manual" | "all" | "ai_accountant";
73
- }, "expenseAutomationTransactionsView/updateTransactionCategorizationCompletedSubTab", never, never>, updateTransactionGroup: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[newGroup: "income" | "expense" | "all"], {
74
- newGroup: "income" | "expense" | "all";
73
+ }, "expenseAutomationTransactionsView/updateTransactionCategorizationCompletedSubTab", never, never>, updateTransactionGroup: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[newGroup: "income" | "expense" | "other" | "all"], {
74
+ newGroup: "income" | "expense" | "other" | "all";
75
75
  }, "expenseAutomationTransactionsView/updateTransactionGroup", never, never>, updateTransactionCategorizationSaveStatus: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[selectedTab: "review" | "autoCategorized", fetchState: FetchState, error?: ZeniAPIStatus<Record<string, unknown>> | undefined], {
76
76
  selectedTab: "review" | "autoCategorized";
77
77
  fetchState: FetchState;
@@ -23,6 +23,7 @@ export interface ActualMatchingRow extends JEOneTimeAccrual {
23
23
  groupedMembers?: ActualMatchingGroupedAccrual[];
24
24
  intendedAccrualIds?: ID[];
25
25
  leftovers?: ActualMatchingLeftoverAccrual[];
26
+ matchBatchId?: string;
26
27
  matchShape?: string;
27
28
  matchTier?: string;
28
29
  reversalDate?: ZeniDate;
@@ -153,6 +153,7 @@ function toActualMatchingRow(match) {
153
153
  return {
154
154
  ...first,
155
155
  matchId: match.matchId,
156
+ matchBatchId: match.matchBatchId,
156
157
  kind: match.kind,
157
158
  accruedAmount: match.accruedAmount,
158
159
  actualAmount: match.actualAmount,
@@ -3,16 +3,16 @@
3
3
  * verbatim to the listing API as `transaction_group`. When revenue automation is
4
4
  * enabled for the tenant the backend scopes the whole view by the line
5
5
  * account_type: `'expense'` -> expense accounts, `'income'` -> income accounts,
6
- * `'all'` -> both. When off, every value falls back to the legacy expense scope
7
- * on the backend, so the group is only sent when the gate is on.
6
+ * `'other'` -> balance-sheet accounts other than bank/credit card (plus lines
7
+ * with no account type), `'all'` -> all three. The list epics always send it;
8
+ * when the gate is off the backend ignores it and returns the legacy expense
9
+ * scope.
8
10
  *
9
11
  * The wire value for expenses is the singular `'expense'` (matches the backend
10
12
  * `TransactionGroup` enum) even though the picker labels it "Expenses".
11
- * Balance-sheet groups (assets, liability) are reserved for later and are not
12
- * part of this union yet.
13
13
  */
14
- export declare const TRANSACTION_GROUPS: readonly ["all", "income", "expense"];
15
- export declare const toTransactionGroup: (v: string | null | undefined) => "income" | "expense" | "all";
14
+ export declare const TRANSACTION_GROUPS: readonly ["all", "income", "expense", "other"];
15
+ export declare const toTransactionGroup: (v: string | null | undefined) => "income" | "expense" | "other" | "all";
16
16
  export type TransactionGroup = ReturnType<typeof toTransactionGroup>;
17
17
  /**
18
18
  * Default group for the listing — matches the picker's default selection ("All").
@@ -7,15 +7,20 @@ const stringToUnion_1 = require("../../../commonStateTypes/stringToUnion");
7
7
  * verbatim to the listing API as `transaction_group`. When revenue automation is
8
8
  * enabled for the tenant the backend scopes the whole view by the line
9
9
  * account_type: `'expense'` -> expense accounts, `'income'` -> income accounts,
10
- * `'all'` -> both. When off, every value falls back to the legacy expense scope
11
- * on the backend, so the group is only sent when the gate is on.
10
+ * `'other'` -> balance-sheet accounts other than bank/credit card (plus lines
11
+ * with no account type), `'all'` -> all three. The list epics always send it;
12
+ * when the gate is off the backend ignores it and returns the legacy expense
13
+ * scope.
12
14
  *
13
15
  * The wire value for expenses is the singular `'expense'` (matches the backend
14
16
  * `TransactionGroup` enum) even though the picker labels it "Expenses".
15
- * Balance-sheet groups (assets, liability) are reserved for later and are not
16
- * part of this union yet.
17
17
  */
18
- exports.TRANSACTION_GROUPS = ['all', 'income', 'expense'];
18
+ exports.TRANSACTION_GROUPS = [
19
+ 'all',
20
+ 'income',
21
+ 'expense',
22
+ 'other',
23
+ ];
19
24
  const toTransactionGroup = (v) => (0, stringToUnion_1.stringToUnion)(v != null ? v.trim().toLowerCase() : '', exports.TRANSACTION_GROUPS);
20
25
  exports.toTransactionGroup = toTransactionGroup;
21
26
  /**
@@ -152,8 +152,8 @@ export type TransactionsViewState = {
152
152
  selectedTransactionCategorizationCompletedSubTab: CompletedSubTab;
153
153
  selectedTransactionCategorizationTab: TransactionsTab;
154
154
  /**
155
- * Statement-level scope (`'expense' | 'income' | 'all'`) forwarded to the
156
- * listing API as `transaction_group`. Unlike the in-memory {@link
155
+ * Statement-level scope (`'expense' | 'income' | 'other' | 'all'`) forwarded
156
+ * to the listing API as `transaction_group`. Unlike the in-memory {@link
157
157
  * TransactionsTabViewState.filters}, a change here invalidates every tab /
158
158
  * sub-tab / count and drives a server refetch (the backend re-scopes the
159
159
  * whole dataset by account_type). Only surfaced in the UI when the
@@ -13,8 +13,9 @@ import { CreateTransactionLocalData } from './createTransactionState';
13
13
  * constant NAMES; ZENI_LINE_DETAIL_TYPE_TO_QBO_LINE_DETAIL_TYPE_MAP converts them for the
14
14
  * body the backend sends on to QuickBooks, never for ours.
15
15
  * 2. The QBO Purchases (check, credit_card_credit) send their reference number as
16
- * `document_number`; a journal entry sends its entry name as `document_id`. Both land on
17
- * QBO's DocNumber, but QBOJournalEntry reads only `document_id`.
16
+ * `document_number`; a journal entry sends its entry name, a bill its bill number and a
17
+ * vendor credit its reference number as `document_id`. All land on QBO's DocNumber, but
18
+ * QBOJournalEntry reads only `document_id`.
18
19
  */
19
20
  export interface CreateTransactionAccountPayload {
20
21
  qbo_id: ID;
@@ -53,10 +54,12 @@ export interface CreateTransactionRemitToAddressPayload {
53
54
  export interface CreateTransactionRequest extends Record<string, unknown> {
54
55
  transaction_date: string;
55
56
  transaction_type: string;
56
- /** journal_entry entry name (QBO DocNumber). */
57
+ /** journal_entry entry name, bill number or vendor_credit reference number (QBO DocNumber). */
57
58
  document_id?: string;
58
59
  /** check / credit_card_credit reference number (QBO Purchase). */
59
60
  document_number?: string;
61
+ /** bill only. */
62
+ due_date?: string;
60
63
  from_account_id?: ID;
61
64
  /** credit_card_credit is modelled as a Purchase with Credit: true. */
62
65
  is_credit?: boolean;
@@ -135,13 +135,29 @@ const prepareCreateTransactionPayload = (state, localData) => {
135
135
  };
136
136
  }
137
137
  const lines = localData.lines.map((line) => toLinePayload(state, line, transactionType));
138
+ const documentIdField = localData.documentNumber != null && localData.documentNumber !== ''
139
+ ? { document_id: localData.documentNumber }
140
+ : {};
138
141
  if (transactionType === 'journal_entry') {
142
+ return { ...base, ...documentIdField, lines };
143
+ }
144
+ // Both post to accounts payable, so they carry no header account or payment type.
145
+ if (transactionType === 'vendor_credit') {
146
+ return {
147
+ ...base,
148
+ vendor_id: toQboVendorId(state, localData.vendorId),
149
+ ...documentIdField,
150
+ lines,
151
+ };
152
+ }
153
+ if (transactionType === 'bill') {
139
154
  return {
140
155
  ...base,
141
- ...(localData.documentNumber != null &&
142
- localData.documentNumber !== '' && {
143
- document_id: localData.documentNumber,
156
+ vendor_id: toQboVendorId(state, localData.vendorId),
157
+ ...(localData.dueDate != null && {
158
+ due_date: localData.dueDate.format(timePeriod_1.DEFAULT_DATE_FORMAT),
144
159
  }),
160
+ ...documentIdField,
145
161
  lines,
146
162
  };
147
163
  }
@@ -9,7 +9,7 @@ import { ZeniDate } from '../../../zeniDayJS';
9
9
  * The transaction types the Create Transaction flow can originate. A narrow subset of
10
10
  * TransactionType -- everything else is read-only and arrives via sync.
11
11
  */
12
- export declare const ALL_CREATABLE_TRANSACTION_TYPES: readonly ["expense", "check", "credit_card_credit", "transfer", "journal_entry"];
12
+ export declare const ALL_CREATABLE_TRANSACTION_TYPES: readonly ["expense", "check", "credit_card_credit", "transfer", "journal_entry", "bill", "vendor_credit"];
13
13
  export type CreatableTransactionType = (typeof ALL_CREATABLE_TRANSACTION_TYPES)[number];
14
14
  /**
15
15
  * The wire shape the create endpoint expects for a check's remit-to address. Deliberately not
@@ -54,8 +54,13 @@ export interface CreateTransactionLocalData {
54
54
  /** Empty for transfer, which has no lines at all. */
55
55
  lines: CreateTransactionLineLocalData[];
56
56
  transactionType: CreatableTransactionType;
57
- /** check / credit_card_credit reference number, or the journal_entry entry name. */
57
+ /**
58
+ * check / credit_card_credit / vendor_credit reference number, journal_entry entry name, or
59
+ * bill number.
60
+ */
58
61
  documentNumber?: string;
62
+ /** bill only. */
63
+ dueDate?: ZeniDate;
59
64
  /** transfer */
60
65
  fromAccountId?: ID;
61
66
  paymentType?: PaymentType;
@@ -11,4 +11,6 @@ exports.ALL_CREATABLE_TRANSACTION_TYPES = [
11
11
  'credit_card_credit',
12
12
  'transfer',
13
13
  'journal_entry',
14
+ 'bill',
15
+ 'vendor_credit',
14
16
  ];