@zeniai/client-epic-state 4.19.99-betaSS3 → 4.19.99-betaVR3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.js +2 -2
- package/lib/esm/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +4 -3
- package/lib/esm/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector.js +3 -8
- package/lib/esm/view/profitAndLossClassesView/profitAndLossHorizontalEnrichment.js +140 -59
- package/lib/tsconfig.typecheck.tsbuildinfo +1 -0
- package/lib/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.js +1 -1
- package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +4 -3
- package/lib/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector.js +3 -8
- package/lib/view/profitAndLossClassesView/profitAndLossHorizontalEnrichment.js +140 -59
- package/package.json +1 -1
|
@@ -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),
|
|
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) {
|
package/lib/esm/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js
CHANGED
|
@@ -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),
|
|
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),
|
|
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
|
|
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). */
|
package/lib/esm/view/profitAndLossClassesView/profitAndLossClassesByClassHorizontalSelector.js
CHANGED
|
@@ -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
|
-
//
|
|
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 =
|
|
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(
|
|
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
|
|
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) =>
|
|
36
|
+
.map((_, i) => (parentIndices[i] === -1 ? i : -1))
|
|
36
37
|
.filter((i) => i >= 0),
|
|
37
38
|
childrenRowIndicesByParent: childrenRecord,
|
|
38
39
|
};
|
|
40
|
+
const siblingsPerRow = buildSiblingIndicesPerRow(section.accounts, parentIndices);
|
|
41
|
+
const hasChildFlags = computeHorizontalHasChildFlags(parentIndices);
|
|
39
42
|
for (let i = 0; i < section.accounts.length; i++) {
|
|
40
43
|
const row = section.accounts[i];
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return cb?.balance.amount ?? 0;
|
|
44
|
-
});
|
|
44
|
+
// Same column order as `report.classColumns` (see `pivotSectionToHorizontal`).
|
|
45
|
+
const classRowAmounts = row.classBalances.map((b) => b.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:
|
|
52
|
+
horizontalTreeView: buildHorizontalTreeViewFromPrecomputed(section.accounts, i, parentIndices, siblingsPerRow, hasChildFlags),
|
|
52
53
|
};
|
|
53
54
|
}
|
|
54
55
|
}
|
|
@@ -120,64 +121,157 @@ function buildHorizontalClassColumnBalancesSlice(report, classAmounts, totalAmou
|
|
|
120
121
|
},
|
|
121
122
|
};
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Parent index per DFS row — O(n). Matches the scan in the former
|
|
126
|
+
* `findHorizontalAccountParentIndex` (depth <= 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 d = accountRow.depth;
|
|
138
|
+
while (stack.length > 0) {
|
|
139
|
+
const topIdx = stack[stack.length - 1];
|
|
140
|
+
if (topIdx == null) {
|
|
141
|
+
break;
|
|
133
142
|
}
|
|
134
|
-
|
|
143
|
+
const topRow = rows[topIdx];
|
|
144
|
+
if (topRow == null) {
|
|
145
|
+
stack.pop();
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (topRow.depth < d) {
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
stack.pop();
|
|
152
|
+
}
|
|
153
|
+
if (d <= 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 === d - 1 ? peek : -1;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
stack.push(rowIndex);
|
|
136
171
|
}
|
|
137
|
-
return
|
|
172
|
+
return parentIndex;
|
|
138
173
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
for (let
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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 g = keyToIndices.get(key);
|
|
186
|
+
if (g == null) {
|
|
187
|
+
g = [];
|
|
188
|
+
keyToIndices.set(key, g);
|
|
148
189
|
}
|
|
190
|
+
g.push(rowIndex);
|
|
149
191
|
}
|
|
150
|
-
|
|
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
|
|
153
|
-
|
|
154
|
-
|
|
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
|
|
157
|
-
const
|
|
219
|
+
function buildHorizontalSubAccountAncestorMetadataFromPrecomputed(dfsOrderedAccountRows, currentRowIndex, parentIndices, siblingsPerRow, hasChildFlags) {
|
|
220
|
+
const chain = [];
|
|
158
221
|
let walkRowIndex = currentRowIndex;
|
|
159
222
|
while (walkRowIndex >= 0) {
|
|
160
|
-
|
|
161
|
-
|
|
223
|
+
chain.unshift(walkRowIndex);
|
|
224
|
+
const parentRowIndex = parentIndices[walkRowIndex];
|
|
225
|
+
if (parentRowIndex == null) {
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
walkRowIndex = parentRowIndex;
|
|
162
229
|
}
|
|
163
|
-
return
|
|
164
|
-
const
|
|
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:
|
|
244
|
+
depth: accountRow.depth - 2,
|
|
167
245
|
hasSibling: siblingIndices.length > 1,
|
|
168
|
-
hasChildren:
|
|
246
|
+
hasChildren: hasChild === true,
|
|
169
247
|
isLastNode: rowIndex === siblingIndices[siblingIndices.length - 1],
|
|
170
|
-
name:
|
|
248
|
+
name: accountRow.accountName,
|
|
171
249
|
};
|
|
172
250
|
});
|
|
173
251
|
}
|
|
174
|
-
function
|
|
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 sibs = siblingIndices ?? [currentRowIndex];
|
|
259
|
+
return {
|
|
260
|
+
subAccountAncestorMetadata: buildHorizontalSubAccountAncestorMetadataFromPrecomputed(dfsOrderedAccountRows, currentRowIndex, parentIndices, siblingsPerRow, hasChildFlags),
|
|
261
|
+
treeDepth,
|
|
262
|
+
hasChildren,
|
|
263
|
+
hasSiblings: sibs.length > 1,
|
|
264
|
+
isLeaf: !hasChildren,
|
|
265
|
+
isLastNode: currentRowIndex === sibs[sibs.length - 1],
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function buildHorizontalAccountChildIndicesByParent(parentIndices) {
|
|
175
269
|
const childIndicesByParentRowIndex = new Map();
|
|
176
|
-
for (let rowIndex = 0; rowIndex <
|
|
177
|
-
const parentRowIndex =
|
|
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
|
|
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
|
-
}
|