@zeniai/client-epic-state 4.19.99-betaSS3 → 4.19.99-betaSS4

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.
@@ -21,13 +21,13 @@ export const fetchJeSchedulesEpic = (actions$, state$, zeniAPI) => actions$.pipe
21
21
  if (isSuccessResponse(response) && response.data != null) {
22
22
  const updateActions = [];
23
23
  updateActions.push(updateJESchedules([
24
- ...response.data.draft_je_schedules,
24
+ ...response.data.je_schedules,
25
25
  ...response.data.failed_je_postings.je_schedules,
26
26
  ]), updateJESchedulesDetails([...response.data.failed_je_postings.je_schedules], response.data.failed_je_postings.scheduled_journal_entries), initializeJeScheduleLocalData([
27
27
  ...response.data.failed_je_postings.je_schedules,
28
28
  ]));
29
29
  updateActions.push(fetchJeSchedulesSuccess({
30
- jeSchedules: [...response.data.draft_je_schedules],
30
+ jeSchedules: [...response.data.je_schedules],
31
31
  failedJeSchedules: [
32
32
  ...response.data.failed_je_postings.je_schedules,
33
33
  ],
@@ -1,9 +1,9 @@
1
1
  import { concat, of, timer } from 'rxjs';
2
- import { catchError, filter, mergeMap } from 'rxjs/operators';
2
+ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
3
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
4
4
  import { BULK_UPLOAD_BAR_COMPLETE_HOLD_MS } from '../../bulkUploadTiming';
5
5
  import { bulkUploadReceipts, bulkUploadReceiptsFailure, bulkUploadReceiptsSuccess, updateBulkUploadProgress, } from '../../reducers/missingReceiptsViewReducer';
6
- export const bulkUploadReceiptsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(bulkUploadReceipts.match), mergeMap((action) => {
6
+ export const bulkUploadReceiptsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(bulkUploadReceipts.match), switchMap((action) => {
7
7
  const { files } = action.payload;
8
8
  const formData = new FormData();
9
9
  for (const file of files) {
@@ -25,7 +25,7 @@ export const pusherBatchStatusCompletionEpic = (actions$) => actions$.pipe(filte
25
25
  * force `phase` to `completed` (dismiss automatching banner) and refresh the batch list.
26
26
  * Cancels when the batch completes normally, bulk state is cleared, or another upload supersedes.
27
27
  */
28
- export const bulkUploadAutomatchingTimeoutEpic = (actions$, state$) => actions$.pipe(filter(bulkUploadReceiptsSuccess.match), mergeMap((action) => {
28
+ export const bulkUploadAutomatchingTimeoutEpic = (actions$, state$) => actions$.pipe(filter(bulkUploadReceiptsSuccess.match), switchMap((action) => {
29
29
  const { batchId } = action.payload;
30
30
  return timer(BULK_UPLOAD_AUTOMATCHING_TIMEOUT_MS).pipe(mergeMap(() => {
31
31
  const bulk = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload;
@@ -44,10 +44,11 @@ export const bulkUploadAutomatchingTimeoutEpic = (actions$, state$) => actions$.
44
44
  * error snackbar per upload session (first failure only) so the user is not spammed every
45
45
  * interval; Pusher or a later successful poll still completes the flow.
46
46
  */
47
- export const pollBulkUploadBatchStatusEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(bulkUploadReceiptsSuccess.match), mergeMap((action) => {
47
+ export const pollBulkUploadBatchStatusEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(bulkUploadReceiptsSuccess.match), switchMap((action) => {
48
48
  const { batchId } = action.payload;
49
49
  let pollErrorNotified = false;
50
- return timer(FALLBACK_FIRST_DELAY_MS, FALLBACK_POLL_INTERVAL_MS).pipe(takeUntil(merge(actions$.pipe(filter(clearBulkUpload.match)), actions$.pipe(filter(pusherBatchStatusUpdate.match), filter((action) => action.payload.batchId === batchId)), actions$.pipe(filter(fetchBulkUploadBatchDetailsSuccess.match), filter((action) => action.payload.batchId === batchId)), actions$.pipe(filter(bulkUploadAutomatchingTimedOut.match), filter((action) => action.payload.batchId === batchId)))), switchMap(() => zeniAPI
50
+ return timer(FALLBACK_FIRST_DELAY_MS, FALLBACK_POLL_INTERVAL_MS).pipe(takeUntil(merge(actions$.pipe(filter(clearBulkUpload.match)), actions$.pipe(filter(pusherBatchStatusUpdate.match), filter((action) => action.payload.batchId === batchId &&
51
+ action.payload.status === 'completed')), actions$.pipe(filter(fetchBulkUploadBatchDetailsSuccess.match), filter((action) => action.payload.batchId === batchId)), actions$.pipe(filter(bulkUploadAutomatchingTimedOut.match), filter((action) => action.payload.batchId === batchId)))), switchMap(() => zeniAPI
51
52
  .getJSON(`${zeniAPI.apiEndPoints.communicationAgentMicroServiceBaseUrl}/1.0/batches/${batchId}`)
52
53
  .pipe(mergeMap((response) => {
53
54
  /** Same batch-details URL; polls until `status` is completed (Pusher may win first). */
@@ -49,16 +49,11 @@ export const getProfitAndLossClassesHorizontalView = (filter, accountState, clas
49
49
  const calculatedSections = {};
50
50
  let reportCurrency = defaultCustomerCurrency;
51
51
  if (fetchState.fetchState === 'Completed' && selectedPeriod != null) {
52
- // Get section reports using existing selector infrastructure
53
- const sectionReports = {};
54
- HORIZONTAL_PANDL_SECTION_IDS.forEach((sectionId) => {
55
- sectionReports[sectionId] = getSectionClassesViewReport(accountState, classState, sectionsState, { reportId, sectionId }, filter);
56
- });
57
52
  reportCurrency =
58
53
  profitAndLossClassesViewState.currency ?? defaultCustomerCurrency;
59
- // Pivot each section from class→account to account→class
54
+ // Fetch each section and pivot class→account to account→class in one pass
60
55
  HORIZONTAL_PANDL_SECTION_IDS.forEach((sectionId) => {
61
- const sectionReport = sectionReports[sectionId];
56
+ const sectionReport = getSectionClassesViewReport(accountState, classState, sectionsState, { reportId, sectionId }, filter);
62
57
  if (sectionReport != null) {
63
58
  sections[sectionId] = pivotSectionToHorizontal(sectionReport, classColumns, SECTION_TITLES[sectionId] ?? sectionId, reportCurrency);
64
59
  }
@@ -261,7 +256,7 @@ function sumBalancesInSlice(balances) {
261
256
  if (balances.length === 0) {
262
257
  return 0;
263
258
  }
264
- const summed = getSummationFreeRangeTotalBalance([...balances]);
259
+ const summed = getSummationFreeRangeTotalBalance(balances);
265
260
  return summed?.balance.amount ?? 0;
266
261
  }
267
262
  /**
@@ -25,30 +25,31 @@ function enrichHorizontalSection(report, section) {
25
25
  return total?.balance.amount ?? 0;
26
26
  });
27
27
  section.sectionHeaderBalancesSlice = buildHorizontalClassColumnBalancesSlice(report, classAmounts, section.sectionTotal.amount);
28
- const childrenRowIndicesByParent = buildHorizontalAccountChildIndicesByParent(section.accounts);
28
+ const parentIndices = computeHorizontalAccountParentIndices(section.accounts);
29
+ const childrenRowIndicesByParent = buildHorizontalAccountChildIndicesByParent(parentIndices);
29
30
  const childrenRecord = {};
30
31
  childrenRowIndicesByParent.forEach((childIndices, parentIndex) => {
31
32
  childrenRecord[parentIndex] = childIndices;
32
33
  });
33
34
  section.treeLayout = {
34
35
  rootAccountRowIndices: section.accounts
35
- .map((_, i) => findHorizontalAccountParentIndex(section.accounts, i) === -1 ? i : -1)
36
+ .map((_, i) => (parentIndices[i] === -1 ? i : -1))
36
37
  .filter((i) => i >= 0),
37
38
  childrenRowIndicesByParent: childrenRecord,
38
39
  };
39
- for (let i = 0; i < section.accounts.length; i++) {
40
- const row = section.accounts[i];
41
- const classRowAmounts = report.classColumns.map((col) => {
42
- const cb = row.classBalances.find((b) => b.classId === col.classId);
43
- return cb?.balance.amount ?? 0;
44
- });
40
+ const siblingsPerRow = buildSiblingIndicesPerRow(section.accounts, parentIndices);
41
+ const hasChildFlags = computeHorizontalHasChildFlags(parentIndices);
42
+ for (let accountIndex = 0; accountIndex < section.accounts.length; accountIndex++) {
43
+ const row = section.accounts[accountIndex];
44
+ // Same column order as `report.classColumns` (see `pivotSectionToHorizontal`).
45
+ const classRowAmounts = row.classBalances.map((classBalance) => classBalance.balance.amount);
45
46
  row.accountReportData = {
46
47
  accountId: row.accountId,
47
48
  accountName: row.accountName,
48
49
  accountType: row.accountType,
49
50
  depth: row.depth,
50
51
  balancesInTimeframe: buildHorizontalClassColumnBalancesSlice(report, classRowAmounts, row.rowTotal.amount),
51
- horizontalTreeView: buildHorizontalTreeView(section.accounts, i),
52
+ horizontalTreeView: buildHorizontalTreeViewFromPrecomputed(section.accounts, accountIndex, parentIndices, siblingsPerRow, hasChildFlags),
52
53
  };
53
54
  }
54
55
  }
@@ -80,9 +81,9 @@ function buildHorizontalClassColumnBalancesSlice(report, classAmounts, totalAmou
80
81
  return emptySlice;
81
82
  }
82
83
  const { startDate, endDate } = report.selectedPeriod;
83
- const balances = report.classColumns.map((col, i) => ({
84
+ const balances = report.classColumns.map((col, columnIndex) => ({
84
85
  balance: {
85
- amount: classAmounts[i] ?? 0,
86
+ amount: classAmounts[columnIndex] ?? 0,
86
87
  currencyCode,
87
88
  currencySymbol,
88
89
  },
@@ -120,64 +121,157 @@ function buildHorizontalClassColumnBalancesSlice(report, classAmounts, totalAmou
120
121
  },
121
122
  };
122
123
  }
123
- function findHorizontalAccountParentIndex(dfsOrderedAccountRows, childRowIndex) {
124
- const childDepth = dfsOrderedAccountRows[childRowIndex].depth;
125
- if (childDepth <= 2) {
126
- return -1;
127
- }
128
- for (let earlierRowIndex = childRowIndex - 1; earlierRowIndex >= 0; earlierRowIndex--) {
129
- const earlierDepth = dfsOrderedAccountRows[earlierRowIndex].depth;
130
- if (earlierDepth < childDepth) {
131
- if (earlierDepth === childDepth - 1) {
132
- return earlierRowIndex;
124
+ /**
125
+ * Parent index per DFS row — O(n). Matches the scan in the former
126
+ * `findHorizontalAccountParentIndex` (depth &lt;= 2 rows have no parent).
127
+ */
128
+ function computeHorizontalAccountParentIndices(rows) {
129
+ const accountRowCount = rows.length;
130
+ const parentIndex = new Array(accountRowCount).fill(-1);
131
+ const stack = [];
132
+ for (let rowIndex = 0; rowIndex < accountRowCount; rowIndex++) {
133
+ const accountRow = rows[rowIndex];
134
+ if (accountRow == null) {
135
+ continue;
136
+ }
137
+ const depth = accountRow.depth;
138
+ while (stack.length > 0) {
139
+ const topIndex = stack[stack.length - 1];
140
+ if (topIndex == null) {
141
+ break;
133
142
  }
134
- return -1;
143
+ const topRow = rows[topIndex];
144
+ if (topRow == null) {
145
+ stack.pop();
146
+ continue;
147
+ }
148
+ if (topRow.depth < depth) {
149
+ break;
150
+ }
151
+ stack.pop();
152
+ }
153
+ if (depth <= 2) {
154
+ parentIndex[rowIndex] = -1;
155
+ }
156
+ else if (stack.length === 0) {
157
+ parentIndex[rowIndex] = -1;
135
158
  }
159
+ else {
160
+ const peek = stack[stack.length - 1];
161
+ if (peek == null) {
162
+ parentIndex[rowIndex] = -1;
163
+ }
164
+ else {
165
+ const parentRow = rows[peek];
166
+ parentIndex[rowIndex] =
167
+ parentRow != null && parentRow.depth === depth - 1 ? peek : -1;
168
+ }
169
+ }
170
+ stack.push(rowIndex);
136
171
  }
137
- return -1;
172
+ return parentIndex;
138
173
  }
139
- function getHorizontalAccountSiblingIndices(dfsOrderedAccountRows, rowIndex) {
140
- const parentRowIndex = findHorizontalAccountParentIndex(dfsOrderedAccountRows, rowIndex);
141
- const rowDepth = dfsOrderedAccountRows[rowIndex].depth;
142
- const siblings = [];
143
- for (let i = 0; i < dfsOrderedAccountRows.length; i++) {
144
- if (dfsOrderedAccountRows[i].depth === rowDepth &&
145
- findHorizontalAccountParentIndex(dfsOrderedAccountRows, i) ===
146
- parentRowIndex) {
147
- siblings.push(i);
174
+ /** Sibling row indices (same parent + depth), in traversal order — O(n). */
175
+ function buildSiblingIndicesPerRow(rows, parentIndices) {
176
+ const accountRowCount = rows.length;
177
+ const keyToIndices = new Map();
178
+ for (let rowIndex = 0; rowIndex < accountRowCount; rowIndex++) {
179
+ const parentRowIndex = parentIndices[rowIndex];
180
+ const accountRow = rows[rowIndex];
181
+ if (parentRowIndex == null || accountRow == null) {
182
+ continue;
183
+ }
184
+ const key = `${parentRowIndex}:${accountRow.depth}`;
185
+ let siblingGroup = keyToIndices.get(key);
186
+ if (siblingGroup == null) {
187
+ siblingGroup = [];
188
+ keyToIndices.set(key, siblingGroup);
148
189
  }
190
+ siblingGroup.push(rowIndex);
149
191
  }
150
- return siblings;
192
+ const siblingsPerRow = new Array(accountRowCount);
193
+ for (let rowIndex = 0; rowIndex < accountRowCount; rowIndex++) {
194
+ const parentRowIndex = parentIndices[rowIndex];
195
+ const accountRow = rows[rowIndex];
196
+ if (parentRowIndex == null || accountRow == null) {
197
+ siblingsPerRow[rowIndex] = [rowIndex];
198
+ continue;
199
+ }
200
+ const key = `${parentRowIndex}:${accountRow.depth}`;
201
+ const group = keyToIndices.get(key);
202
+ siblingsPerRow[rowIndex] = group != null ? group : [rowIndex];
203
+ }
204
+ return siblingsPerRow;
151
205
  }
152
- function horizontalAccountHasChildRows(dfsOrderedAccountRows, parentRowIndex) {
153
- return dfsOrderedAccountRows.some((_, rowIndex) => findHorizontalAccountParentIndex(dfsOrderedAccountRows, rowIndex) ===
154
- parentRowIndex);
206
+ function computeHorizontalHasChildFlags(parentIndices) {
207
+ const rowCount = parentIndices.length;
208
+ const flags = new Array(rowCount).fill(false);
209
+ for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
210
+ const parentRowIndex = parentIndices[rowIndex];
211
+ if (parentRowIndex != null &&
212
+ parentRowIndex >= 0 &&
213
+ parentRowIndex < rowCount) {
214
+ flags[parentRowIndex] = true;
215
+ }
216
+ }
217
+ return flags;
155
218
  }
156
- function buildHorizontalSubAccountAncestorMetadata(dfsOrderedAccountRows, currentRowIndex) {
157
- const ancestorRowIndicesRootToLeaf = [];
219
+ function buildHorizontalSubAccountAncestorMetadataFromPrecomputed(dfsOrderedAccountRows, currentRowIndex, parentIndices, siblingsPerRow, hasChildFlags) {
220
+ const chain = [];
158
221
  let walkRowIndex = currentRowIndex;
159
222
  while (walkRowIndex >= 0) {
160
- ancestorRowIndicesRootToLeaf.unshift(walkRowIndex);
161
- walkRowIndex = findHorizontalAccountParentIndex(dfsOrderedAccountRows, walkRowIndex);
223
+ chain.unshift(walkRowIndex);
224
+ const parentRowIndex = parentIndices[walkRowIndex];
225
+ if (parentRowIndex == null) {
226
+ break;
227
+ }
228
+ walkRowIndex = parentRowIndex;
162
229
  }
163
- return ancestorRowIndicesRootToLeaf.map((rowIndex) => {
164
- const siblingIndices = getHorizontalAccountSiblingIndices(dfsOrderedAccountRows, rowIndex);
230
+ return chain.map((rowIndex) => {
231
+ const accountRow = dfsOrderedAccountRows[rowIndex];
232
+ const siblingIndices = siblingsPerRow[rowIndex];
233
+ const hasChild = hasChildFlags[rowIndex];
234
+ if (accountRow == null || siblingIndices == null) {
235
+ return {
236
+ depth: 0,
237
+ hasSibling: false,
238
+ hasChildren: hasChild === true,
239
+ isLastNode: true,
240
+ name: '',
241
+ };
242
+ }
165
243
  return {
166
- depth: dfsOrderedAccountRows[rowIndex].depth - 2,
244
+ depth: accountRow.depth - 2,
167
245
  hasSibling: siblingIndices.length > 1,
168
- hasChildren: horizontalAccountHasChildRows(dfsOrderedAccountRows, rowIndex),
246
+ hasChildren: hasChild === true,
169
247
  isLastNode: rowIndex === siblingIndices[siblingIndices.length - 1],
170
- name: dfsOrderedAccountRows[rowIndex].accountName,
248
+ name: accountRow.accountName,
171
249
  };
172
250
  });
173
251
  }
174
- function buildHorizontalAccountChildIndicesByParent(dfsOrderedAccountRows) {
252
+ function buildHorizontalTreeViewFromPrecomputed(dfsOrderedAccountRows, currentRowIndex, parentIndices, siblingsPerRow, hasChildFlags) {
253
+ const siblingIndices = siblingsPerRow[currentRowIndex];
254
+ const accountRow = dfsOrderedAccountRows[currentRowIndex];
255
+ const hasChildrenFlag = hasChildFlags[currentRowIndex];
256
+ const treeDepth = accountRow != null ? Math.max(0, accountRow.depth - 2) : 0;
257
+ const hasChildren = hasChildrenFlag === true;
258
+ const siblings = siblingIndices ?? [currentRowIndex];
259
+ return {
260
+ subAccountAncestorMetadata: buildHorizontalSubAccountAncestorMetadataFromPrecomputed(dfsOrderedAccountRows, currentRowIndex, parentIndices, siblingsPerRow, hasChildFlags),
261
+ treeDepth,
262
+ hasChildren,
263
+ hasSiblings: siblings.length > 1,
264
+ isLeaf: !hasChildren,
265
+ isLastNode: currentRowIndex === siblings[siblings.length - 1],
266
+ };
267
+ }
268
+ function buildHorizontalAccountChildIndicesByParent(parentIndices) {
175
269
  const childIndicesByParentRowIndex = new Map();
176
- for (let rowIndex = 0; rowIndex < dfsOrderedAccountRows.length; rowIndex++) {
177
- const parentRowIndex = findHorizontalAccountParentIndex(dfsOrderedAccountRows, rowIndex);
178
- if (parentRowIndex >= 0) {
270
+ for (let rowIndex = 0; rowIndex < parentIndices.length; rowIndex++) {
271
+ const parentRowIndex = parentIndices[rowIndex];
272
+ if (parentRowIndex != null && parentRowIndex >= 0) {
179
273
  let childRowIndices = childIndicesByParentRowIndex.get(parentRowIndex);
180
- if (childRowIndices === undefined) {
274
+ if (childRowIndices == null) {
181
275
  childRowIndices = [];
182
276
  childIndicesByParentRowIndex.set(parentRowIndex, childRowIndices);
183
277
  }
@@ -186,16 +280,3 @@ function buildHorizontalAccountChildIndicesByParent(dfsOrderedAccountRows) {
186
280
  }
187
281
  return childIndicesByParentRowIndex;
188
282
  }
189
- function buildHorizontalTreeView(dfsOrderedAccountRows, currentRowIndex) {
190
- const treeDepth = Math.max(0, dfsOrderedAccountRows[currentRowIndex].depth - 2);
191
- const siblingIndices = getHorizontalAccountSiblingIndices(dfsOrderedAccountRows, currentRowIndex);
192
- const hasChildren = horizontalAccountHasChildRows(dfsOrderedAccountRows, currentRowIndex);
193
- return {
194
- subAccountAncestorMetadata: buildHorizontalSubAccountAncestorMetadata(dfsOrderedAccountRows, currentRowIndex),
195
- treeDepth,
196
- hasChildren,
197
- hasSiblings: siblingIndices.length > 1,
198
- isLeaf: !hasChildren,
199
- isLastNode: currentRowIndex === siblingIndices[siblingIndices.length - 1],
200
- };
201
- }
@@ -24,13 +24,13 @@ const fetchJeSchedulesEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, op
24
24
  if ((0, responsePayload_1.isSuccessResponse)(response) && response.data != null) {
25
25
  const updateActions = [];
26
26
  updateActions.push((0, jeSchedulesReducer_1.updateJESchedules)([
27
- ...response.data.draft_je_schedules,
27
+ ...response.data.je_schedules,
28
28
  ...response.data.failed_je_postings.je_schedules,
29
29
  ]), (0, jeSchedulesReducer_1.updateJESchedulesDetails)([...response.data.failed_je_postings.je_schedules], response.data.failed_je_postings.scheduled_journal_entries), (0, jeSchedulesViewReducer_1.initializeJeScheduleLocalData)([
30
30
  ...response.data.failed_je_postings.je_schedules,
31
31
  ]));
32
32
  updateActions.push((0, jeSchedulesViewReducer_1.fetchJeSchedulesSuccess)({
33
- jeSchedules: [...response.data.draft_je_schedules],
33
+ jeSchedules: [...response.data.je_schedules],
34
34
  failedJeSchedules: [
35
35
  ...response.data.failed_je_postings.je_schedules,
36
36
  ],
@@ -6,7 +6,7 @@ const operators_1 = require("rxjs/operators");
6
6
  const responsePayload_1 = require("../../../../responsePayload");
7
7
  const bulkUploadTiming_1 = require("../../bulkUploadTiming");
8
8
  const missingReceiptsViewReducer_1 = require("../../reducers/missingReceiptsViewReducer");
9
- const bulkUploadReceiptsEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.bulkUploadReceipts.match), (0, operators_1.mergeMap)((action) => {
9
+ const bulkUploadReceiptsEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.bulkUploadReceipts.match), (0, operators_1.switchMap)((action) => {
10
10
  const { files } = action.payload;
11
11
  const formData = new FormData();
12
12
  for (const file of files) {
@@ -29,7 +29,7 @@ exports.pusherBatchStatusCompletionEpic = pusherBatchStatusCompletionEpic;
29
29
  * force `phase` to `completed` (dismiss automatching banner) and refresh the batch list.
30
30
  * Cancels when the batch completes normally, bulk state is cleared, or another upload supersedes.
31
31
  */
32
- const bulkUploadAutomatchingTimeoutEpic = (actions$, state$) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.bulkUploadReceiptsSuccess.match), (0, operators_1.mergeMap)((action) => {
32
+ const bulkUploadAutomatchingTimeoutEpic = (actions$, state$) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.bulkUploadReceiptsSuccess.match), (0, operators_1.switchMap)((action) => {
33
33
  const { batchId } = action.payload;
34
34
  return (0, rxjs_1.timer)(bulkUploadTiming_1.BULK_UPLOAD_AUTOMATCHING_TIMEOUT_MS).pipe((0, operators_1.mergeMap)(() => {
35
35
  const bulk = state$.value.expenseAutomationMissingReceiptsViewState.bulkUpload;
@@ -49,10 +49,11 @@ exports.bulkUploadAutomatchingTimeoutEpic = bulkUploadAutomatchingTimeoutEpic;
49
49
  * error snackbar per upload session (first failure only) so the user is not spammed every
50
50
  * interval; Pusher or a later successful poll still completes the flow.
51
51
  */
52
- const pollBulkUploadBatchStatusEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.bulkUploadReceiptsSuccess.match), (0, operators_1.mergeMap)((action) => {
52
+ const pollBulkUploadBatchStatusEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.bulkUploadReceiptsSuccess.match), (0, operators_1.switchMap)((action) => {
53
53
  const { batchId } = action.payload;
54
54
  let pollErrorNotified = false;
55
- return (0, rxjs_1.timer)(FALLBACK_FIRST_DELAY_MS, FALLBACK_POLL_INTERVAL_MS).pipe((0, operators_1.takeUntil)((0, rxjs_1.merge)(actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.clearBulkUpload.match)), actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.pusherBatchStatusUpdate.match), (0, operators_1.filter)((action) => action.payload.batchId === batchId)), actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.fetchBulkUploadBatchDetailsSuccess.match), (0, operators_1.filter)((action) => action.payload.batchId === batchId)), actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.bulkUploadAutomatchingTimedOut.match), (0, operators_1.filter)((action) => action.payload.batchId === batchId)))), (0, operators_1.switchMap)(() => zeniAPI
55
+ return (0, rxjs_1.timer)(FALLBACK_FIRST_DELAY_MS, FALLBACK_POLL_INTERVAL_MS).pipe((0, operators_1.takeUntil)((0, rxjs_1.merge)(actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.clearBulkUpload.match)), actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.pusherBatchStatusUpdate.match), (0, operators_1.filter)((action) => action.payload.batchId === batchId &&
56
+ action.payload.status === 'completed')), actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.fetchBulkUploadBatchDetailsSuccess.match), (0, operators_1.filter)((action) => action.payload.batchId === batchId)), actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.bulkUploadAutomatchingTimedOut.match), (0, operators_1.filter)((action) => action.payload.batchId === batchId)))), (0, operators_1.switchMap)(() => zeniAPI
56
57
  .getJSON(`${zeniAPI.apiEndPoints.communicationAgentMicroServiceBaseUrl}/1.0/batches/${batchId}`)
57
58
  .pipe((0, operators_1.mergeMap)((response) => {
58
59
  /** Same batch-details URL; polls until `status` is completed (Pusher may win first). */
@@ -10,11 +10,11 @@ export interface ExpenseAutomationJESchedulesQueryPayload {
10
10
  start_date: string;
11
11
  }
12
12
  interface ExpenseAutomationJESchedulesPayload {
13
- draft_je_schedules: JEScheduledTransactionPayload[];
14
13
  failed_je_postings: {
15
14
  je_schedules: JEScheduledTransactionPayload[];
16
15
  scheduled_journal_entries: ScheduledJournalEntryPayload[];
17
16
  };
17
+ je_schedules: JEScheduledTransactionPayload[];
18
18
  }
19
19
  export type ExpenseAutomationJESchedulesResponse = ZeniAPIResponse<ExpenseAutomationJESchedulesPayload>;
20
20
  export interface IgnoreDraftJESchedulePayload {
@@ -52,16 +52,11 @@ const getProfitAndLossClassesHorizontalView = (filter, accountState, classState,
52
52
  const calculatedSections = {};
53
53
  let reportCurrency = currencyHelper_1.defaultCustomerCurrency;
54
54
  if (fetchState.fetchState === 'Completed' && selectedPeriod != null) {
55
- // Get section reports using existing selector infrastructure
56
- const sectionReports = {};
57
- HORIZONTAL_PANDL_SECTION_IDS.forEach((sectionId) => {
58
- sectionReports[sectionId] = (0, sectionClassesViewSelector_1.getSectionClassesViewReport)(accountState, classState, sectionsState, { reportId, sectionId }, filter);
59
- });
60
55
  reportCurrency =
61
56
  profitAndLossClassesViewState.currency ?? currencyHelper_1.defaultCustomerCurrency;
62
- // Pivot each section from class→account to account→class
57
+ // Fetch each section and pivot class→account to account→class in one pass
63
58
  HORIZONTAL_PANDL_SECTION_IDS.forEach((sectionId) => {
64
- const sectionReport = sectionReports[sectionId];
59
+ const sectionReport = (0, sectionClassesViewSelector_1.getSectionClassesViewReport)(accountState, classState, sectionsState, { reportId, sectionId }, filter);
65
60
  if (sectionReport != null) {
66
61
  sections[sectionId] = pivotSectionToHorizontal(sectionReport, classColumns, SECTION_TITLES[sectionId] ?? sectionId, reportCurrency);
67
62
  }
@@ -265,7 +260,7 @@ function sumBalancesInSlice(balances) {
265
260
  if (balances.length === 0) {
266
261
  return 0;
267
262
  }
268
- const summed = (0, getSummationBalance_1.getSummationFreeRangeTotalBalance)([...balances]);
263
+ const summed = (0, getSummationBalance_1.getSummationFreeRangeTotalBalance)(balances);
269
264
  return summed?.balance.amount ?? 0;
270
265
  }
271
266
  /**
@@ -28,30 +28,31 @@ function enrichHorizontalSection(report, section) {
28
28
  return total?.balance.amount ?? 0;
29
29
  });
30
30
  section.sectionHeaderBalancesSlice = buildHorizontalClassColumnBalancesSlice(report, classAmounts, section.sectionTotal.amount);
31
- const childrenRowIndicesByParent = buildHorizontalAccountChildIndicesByParent(section.accounts);
31
+ const parentIndices = computeHorizontalAccountParentIndices(section.accounts);
32
+ const childrenRowIndicesByParent = buildHorizontalAccountChildIndicesByParent(parentIndices);
32
33
  const childrenRecord = {};
33
34
  childrenRowIndicesByParent.forEach((childIndices, parentIndex) => {
34
35
  childrenRecord[parentIndex] = childIndices;
35
36
  });
36
37
  section.treeLayout = {
37
38
  rootAccountRowIndices: section.accounts
38
- .map((_, i) => findHorizontalAccountParentIndex(section.accounts, i) === -1 ? i : -1)
39
+ .map((_, i) => (parentIndices[i] === -1 ? i : -1))
39
40
  .filter((i) => i >= 0),
40
41
  childrenRowIndicesByParent: childrenRecord,
41
42
  };
42
- for (let i = 0; i < section.accounts.length; i++) {
43
- const row = section.accounts[i];
44
- const classRowAmounts = report.classColumns.map((col) => {
45
- const cb = row.classBalances.find((b) => b.classId === col.classId);
46
- return cb?.balance.amount ?? 0;
47
- });
43
+ const siblingsPerRow = buildSiblingIndicesPerRow(section.accounts, parentIndices);
44
+ const hasChildFlags = computeHorizontalHasChildFlags(parentIndices);
45
+ for (let accountIndex = 0; accountIndex < section.accounts.length; accountIndex++) {
46
+ const row = section.accounts[accountIndex];
47
+ // Same column order as `report.classColumns` (see `pivotSectionToHorizontal`).
48
+ const classRowAmounts = row.classBalances.map((classBalance) => classBalance.balance.amount);
48
49
  row.accountReportData = {
49
50
  accountId: row.accountId,
50
51
  accountName: row.accountName,
51
52
  accountType: row.accountType,
52
53
  depth: row.depth,
53
54
  balancesInTimeframe: buildHorizontalClassColumnBalancesSlice(report, classRowAmounts, row.rowTotal.amount),
54
- horizontalTreeView: buildHorizontalTreeView(section.accounts, i),
55
+ horizontalTreeView: buildHorizontalTreeViewFromPrecomputed(section.accounts, accountIndex, parentIndices, siblingsPerRow, hasChildFlags),
55
56
  };
56
57
  }
57
58
  }
@@ -83,9 +84,9 @@ function buildHorizontalClassColumnBalancesSlice(report, classAmounts, totalAmou
83
84
  return emptySlice;
84
85
  }
85
86
  const { startDate, endDate } = report.selectedPeriod;
86
- const balances = report.classColumns.map((col, i) => ({
87
+ const balances = report.classColumns.map((col, columnIndex) => ({
87
88
  balance: {
88
- amount: classAmounts[i] ?? 0,
89
+ amount: classAmounts[columnIndex] ?? 0,
89
90
  currencyCode,
90
91
  currencySymbol,
91
92
  },
@@ -123,64 +124,157 @@ function buildHorizontalClassColumnBalancesSlice(report, classAmounts, totalAmou
123
124
  },
124
125
  };
125
126
  }
126
- function findHorizontalAccountParentIndex(dfsOrderedAccountRows, childRowIndex) {
127
- const childDepth = dfsOrderedAccountRows[childRowIndex].depth;
128
- if (childDepth <= 2) {
129
- return -1;
130
- }
131
- for (let earlierRowIndex = childRowIndex - 1; earlierRowIndex >= 0; earlierRowIndex--) {
132
- const earlierDepth = dfsOrderedAccountRows[earlierRowIndex].depth;
133
- if (earlierDepth < childDepth) {
134
- if (earlierDepth === childDepth - 1) {
135
- return earlierRowIndex;
127
+ /**
128
+ * Parent index per DFS row — O(n). Matches the scan in the former
129
+ * `findHorizontalAccountParentIndex` (depth &lt;= 2 rows have no parent).
130
+ */
131
+ function computeHorizontalAccountParentIndices(rows) {
132
+ const accountRowCount = rows.length;
133
+ const parentIndex = new Array(accountRowCount).fill(-1);
134
+ const stack = [];
135
+ for (let rowIndex = 0; rowIndex < accountRowCount; rowIndex++) {
136
+ const accountRow = rows[rowIndex];
137
+ if (accountRow == null) {
138
+ continue;
139
+ }
140
+ const depth = accountRow.depth;
141
+ while (stack.length > 0) {
142
+ const topIndex = stack[stack.length - 1];
143
+ if (topIndex == null) {
144
+ break;
136
145
  }
137
- return -1;
146
+ const topRow = rows[topIndex];
147
+ if (topRow == null) {
148
+ stack.pop();
149
+ continue;
150
+ }
151
+ if (topRow.depth < depth) {
152
+ break;
153
+ }
154
+ stack.pop();
155
+ }
156
+ if (depth <= 2) {
157
+ parentIndex[rowIndex] = -1;
158
+ }
159
+ else if (stack.length === 0) {
160
+ parentIndex[rowIndex] = -1;
138
161
  }
162
+ else {
163
+ const peek = stack[stack.length - 1];
164
+ if (peek == null) {
165
+ parentIndex[rowIndex] = -1;
166
+ }
167
+ else {
168
+ const parentRow = rows[peek];
169
+ parentIndex[rowIndex] =
170
+ parentRow != null && parentRow.depth === depth - 1 ? peek : -1;
171
+ }
172
+ }
173
+ stack.push(rowIndex);
139
174
  }
140
- return -1;
175
+ return parentIndex;
141
176
  }
142
- function getHorizontalAccountSiblingIndices(dfsOrderedAccountRows, rowIndex) {
143
- const parentRowIndex = findHorizontalAccountParentIndex(dfsOrderedAccountRows, rowIndex);
144
- const rowDepth = dfsOrderedAccountRows[rowIndex].depth;
145
- const siblings = [];
146
- for (let i = 0; i < dfsOrderedAccountRows.length; i++) {
147
- if (dfsOrderedAccountRows[i].depth === rowDepth &&
148
- findHorizontalAccountParentIndex(dfsOrderedAccountRows, i) ===
149
- parentRowIndex) {
150
- siblings.push(i);
177
+ /** Sibling row indices (same parent + depth), in traversal order — O(n). */
178
+ function buildSiblingIndicesPerRow(rows, parentIndices) {
179
+ const accountRowCount = rows.length;
180
+ const keyToIndices = new Map();
181
+ for (let rowIndex = 0; rowIndex < accountRowCount; rowIndex++) {
182
+ const parentRowIndex = parentIndices[rowIndex];
183
+ const accountRow = rows[rowIndex];
184
+ if (parentRowIndex == null || accountRow == null) {
185
+ continue;
186
+ }
187
+ const key = `${parentRowIndex}:${accountRow.depth}`;
188
+ let siblingGroup = keyToIndices.get(key);
189
+ if (siblingGroup == null) {
190
+ siblingGroup = [];
191
+ keyToIndices.set(key, siblingGroup);
151
192
  }
193
+ siblingGroup.push(rowIndex);
152
194
  }
153
- return siblings;
195
+ const siblingsPerRow = new Array(accountRowCount);
196
+ for (let rowIndex = 0; rowIndex < accountRowCount; rowIndex++) {
197
+ const parentRowIndex = parentIndices[rowIndex];
198
+ const accountRow = rows[rowIndex];
199
+ if (parentRowIndex == null || accountRow == null) {
200
+ siblingsPerRow[rowIndex] = [rowIndex];
201
+ continue;
202
+ }
203
+ const key = `${parentRowIndex}:${accountRow.depth}`;
204
+ const group = keyToIndices.get(key);
205
+ siblingsPerRow[rowIndex] = group != null ? group : [rowIndex];
206
+ }
207
+ return siblingsPerRow;
154
208
  }
155
- function horizontalAccountHasChildRows(dfsOrderedAccountRows, parentRowIndex) {
156
- return dfsOrderedAccountRows.some((_, rowIndex) => findHorizontalAccountParentIndex(dfsOrderedAccountRows, rowIndex) ===
157
- parentRowIndex);
209
+ function computeHorizontalHasChildFlags(parentIndices) {
210
+ const rowCount = parentIndices.length;
211
+ const flags = new Array(rowCount).fill(false);
212
+ for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
213
+ const parentRowIndex = parentIndices[rowIndex];
214
+ if (parentRowIndex != null &&
215
+ parentRowIndex >= 0 &&
216
+ parentRowIndex < rowCount) {
217
+ flags[parentRowIndex] = true;
218
+ }
219
+ }
220
+ return flags;
158
221
  }
159
- function buildHorizontalSubAccountAncestorMetadata(dfsOrderedAccountRows, currentRowIndex) {
160
- const ancestorRowIndicesRootToLeaf = [];
222
+ function buildHorizontalSubAccountAncestorMetadataFromPrecomputed(dfsOrderedAccountRows, currentRowIndex, parentIndices, siblingsPerRow, hasChildFlags) {
223
+ const chain = [];
161
224
  let walkRowIndex = currentRowIndex;
162
225
  while (walkRowIndex >= 0) {
163
- ancestorRowIndicesRootToLeaf.unshift(walkRowIndex);
164
- walkRowIndex = findHorizontalAccountParentIndex(dfsOrderedAccountRows, walkRowIndex);
226
+ chain.unshift(walkRowIndex);
227
+ const parentRowIndex = parentIndices[walkRowIndex];
228
+ if (parentRowIndex == null) {
229
+ break;
230
+ }
231
+ walkRowIndex = parentRowIndex;
165
232
  }
166
- return ancestorRowIndicesRootToLeaf.map((rowIndex) => {
167
- const siblingIndices = getHorizontalAccountSiblingIndices(dfsOrderedAccountRows, rowIndex);
233
+ return chain.map((rowIndex) => {
234
+ const accountRow = dfsOrderedAccountRows[rowIndex];
235
+ const siblingIndices = siblingsPerRow[rowIndex];
236
+ const hasChild = hasChildFlags[rowIndex];
237
+ if (accountRow == null || siblingIndices == null) {
238
+ return {
239
+ depth: 0,
240
+ hasSibling: false,
241
+ hasChildren: hasChild === true,
242
+ isLastNode: true,
243
+ name: '',
244
+ };
245
+ }
168
246
  return {
169
- depth: dfsOrderedAccountRows[rowIndex].depth - 2,
247
+ depth: accountRow.depth - 2,
170
248
  hasSibling: siblingIndices.length > 1,
171
- hasChildren: horizontalAccountHasChildRows(dfsOrderedAccountRows, rowIndex),
249
+ hasChildren: hasChild === true,
172
250
  isLastNode: rowIndex === siblingIndices[siblingIndices.length - 1],
173
- name: dfsOrderedAccountRows[rowIndex].accountName,
251
+ name: accountRow.accountName,
174
252
  };
175
253
  });
176
254
  }
177
- function buildHorizontalAccountChildIndicesByParent(dfsOrderedAccountRows) {
255
+ function buildHorizontalTreeViewFromPrecomputed(dfsOrderedAccountRows, currentRowIndex, parentIndices, siblingsPerRow, hasChildFlags) {
256
+ const siblingIndices = siblingsPerRow[currentRowIndex];
257
+ const accountRow = dfsOrderedAccountRows[currentRowIndex];
258
+ const hasChildrenFlag = hasChildFlags[currentRowIndex];
259
+ const treeDepth = accountRow != null ? Math.max(0, accountRow.depth - 2) : 0;
260
+ const hasChildren = hasChildrenFlag === true;
261
+ const siblings = siblingIndices ?? [currentRowIndex];
262
+ return {
263
+ subAccountAncestorMetadata: buildHorizontalSubAccountAncestorMetadataFromPrecomputed(dfsOrderedAccountRows, currentRowIndex, parentIndices, siblingsPerRow, hasChildFlags),
264
+ treeDepth,
265
+ hasChildren,
266
+ hasSiblings: siblings.length > 1,
267
+ isLeaf: !hasChildren,
268
+ isLastNode: currentRowIndex === siblings[siblings.length - 1],
269
+ };
270
+ }
271
+ function buildHorizontalAccountChildIndicesByParent(parentIndices) {
178
272
  const childIndicesByParentRowIndex = new Map();
179
- for (let rowIndex = 0; rowIndex < dfsOrderedAccountRows.length; rowIndex++) {
180
- const parentRowIndex = findHorizontalAccountParentIndex(dfsOrderedAccountRows, rowIndex);
181
- if (parentRowIndex >= 0) {
273
+ for (let rowIndex = 0; rowIndex < parentIndices.length; rowIndex++) {
274
+ const parentRowIndex = parentIndices[rowIndex];
275
+ if (parentRowIndex != null && parentRowIndex >= 0) {
182
276
  let childRowIndices = childIndicesByParentRowIndex.get(parentRowIndex);
183
- if (childRowIndices === undefined) {
277
+ if (childRowIndices == null) {
184
278
  childRowIndices = [];
185
279
  childIndicesByParentRowIndex.set(parentRowIndex, childRowIndices);
186
280
  }
@@ -189,16 +283,3 @@ function buildHorizontalAccountChildIndicesByParent(dfsOrderedAccountRows) {
189
283
  }
190
284
  return childIndicesByParentRowIndex;
191
285
  }
192
- function buildHorizontalTreeView(dfsOrderedAccountRows, currentRowIndex) {
193
- const treeDepth = Math.max(0, dfsOrderedAccountRows[currentRowIndex].depth - 2);
194
- const siblingIndices = getHorizontalAccountSiblingIndices(dfsOrderedAccountRows, currentRowIndex);
195
- const hasChildren = horizontalAccountHasChildRows(dfsOrderedAccountRows, currentRowIndex);
196
- return {
197
- subAccountAncestorMetadata: buildHorizontalSubAccountAncestorMetadata(dfsOrderedAccountRows, currentRowIndex),
198
- treeDepth,
199
- hasChildren,
200
- hasSiblings: siblingIndices.length > 1,
201
- isLeaf: !hasChildren,
202
- isLastNode: currentRowIndex === siblingIndices[siblingIndices.length - 1],
203
- };
204
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "4.19.99-betaSS3",
3
+ "version": "4.19.99-betaSS4",
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",