@zeniai/client-epic-state 5.2.93 → 5.2.94-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.
- package/lib/commonStateTypes/timeframeTick.d.ts +1 -0
- package/lib/commonStateTypes/timeframeTick.js +1 -0
- package/lib/commonStateTypes/viewAndReport/reportUIState.d.ts +2 -0
- package/lib/commonStateTypes/viewAndReport/viewAndReport.d.ts +2 -0
- package/lib/esm/commonStateTypes/timeframeTick.js +1 -0
- package/lib/esm/view/balanceSheet/balanceSheetForTimeframeEpic.js +4 -0
- package/lib/esm/view/balanceSheet/balanceSheetReducer.js +8 -2
- package/lib/esm/view/cashFlow/cashFlowForTimeframeEpic.js +4 -0
- package/lib/esm/view/cashFlow/cashFlowReducer.js +2 -2
- package/lib/esm/view/cashInCashOut/cashInCashOutEpic.js +22 -14
- package/lib/esm/view/cashInCashOut/cashInCashOutReducer.js +161 -41
- package/lib/esm/view/cashInCashOut/cashInCashOutSelector.js +42 -15
- package/lib/esm/view/cashPosition/cashPositionEpic.js +21 -13
- package/lib/esm/view/cashPosition/cashPositionReducer.js +155 -44
- package/lib/esm/view/cashPosition/cashPositionSelector.js +28 -9
- package/lib/esm/view/expenseAutomationView/payload/actualMatchingPayload.js +5 -0
- package/lib/esm/view/netBurnOrIncome/netBurnOrIncomeEpic.js +7 -7
- package/lib/esm/view/netBurnOrIncome/netBurnOrIncomeForTimeframeEpic.js +25 -10
- package/lib/esm/view/netBurnOrIncome/netBurnOrIncomeReducer.js +184 -40
- package/lib/esm/view/netBurnOrIncome/netBurnOrIncomeSelector.js +26 -8
- package/lib/esm/view/opEx/opExEpic.js +7 -7
- package/lib/esm/view/opEx/opExForTimeFrameEpic.js +21 -6
- package/lib/esm/view/opEx/opExReducer.js +169 -32
- package/lib/esm/view/opEx/opExSelector.js +28 -10
- package/lib/esm/view/profitAndLoss/profitAndLossForTimeframeEpic.js +4 -0
- package/lib/esm/view/profitAndLoss/profitAndLossReducer.js +9 -2
- package/lib/esm/view/revenue/revenueEpic.js +7 -7
- package/lib/esm/view/revenue/revenueForTimeframeEpic.js +23 -8
- package/lib/esm/view/revenue/revenueReducer.js +183 -40
- package/lib/esm/view/revenue/revenueSelector.js +30 -10
- package/lib/view/balanceSheet/balanceSheetForTimeframeEpic.js +4 -0
- package/lib/view/balanceSheet/balanceSheetReducer.d.ts +3 -1
- package/lib/view/balanceSheet/balanceSheetReducer.js +8 -2
- package/lib/view/cashFlow/cashFlowForTimeframeEpic.js +4 -0
- package/lib/view/cashFlow/cashFlowReducer.d.ts +3 -1
- package/lib/view/cashFlow/cashFlowReducer.js +2 -2
- package/lib/view/cashInCashOut/cashInCashOutEpic.d.ts +8 -2
- package/lib/view/cashInCashOut/cashInCashOutEpic.js +22 -14
- package/lib/view/cashInCashOut/cashInCashOutReducer.d.ts +13 -1
- package/lib/view/cashInCashOut/cashInCashOutReducer.js +161 -41
- package/lib/view/cashInCashOut/cashInCashOutSelector.d.ts +2 -1
- package/lib/view/cashInCashOut/cashInCashOutSelector.js +42 -15
- package/lib/view/cashInCashOut/cashInCashOutState.d.ts +5 -1
- package/lib/view/cashPosition/cashPositionEpic.d.ts +8 -2
- package/lib/view/cashPosition/cashPositionEpic.js +21 -13
- package/lib/view/cashPosition/cashPositionReducer.d.ts +13 -2
- package/lib/view/cashPosition/cashPositionReducer.js +155 -44
- package/lib/view/cashPosition/cashPositionSelector.d.ts +2 -1
- package/lib/view/cashPosition/cashPositionSelector.js +28 -9
- package/lib/view/cashPosition/cashPositionState.d.ts +5 -1
- package/lib/view/expenseAutomationView/payload/actualMatchingPayload.d.ts +2 -0
- package/lib/view/expenseAutomationView/payload/actualMatchingPayload.js +5 -0
- package/lib/view/expenseAutomationView/types/actualMatchingViewState.d.ts +3 -0
- package/lib/view/financeStatement/financeStatementEpic.d.ts +6 -0
- package/lib/view/netBurnOrIncome/fetchNetBurnOrIncomeWithForecastEpic.d.ts +1 -0
- package/lib/view/netBurnOrIncome/netBurnOrIncomeEpic.js +7 -7
- package/lib/view/netBurnOrIncome/netBurnOrIncomeForTimeframeEpic.d.ts +2 -1
- package/lib/view/netBurnOrIncome/netBurnOrIncomeForTimeframeEpic.js +26 -11
- package/lib/view/netBurnOrIncome/netBurnOrIncomeReducer.d.ts +12 -3
- package/lib/view/netBurnOrIncome/netBurnOrIncomeReducer.js +184 -40
- package/lib/view/netBurnOrIncome/netBurnOrIncomeSelector.d.ts +1 -1
- package/lib/view/netBurnOrIncome/netBurnOrIncomeSelector.js +26 -8
- package/lib/view/netBurnOrIncome/netBurnOrIncomeState.d.ts +8 -0
- package/lib/view/opEx/fetchOpExWithForecastEpic.d.ts +1 -0
- package/lib/view/opEx/opExEpic.js +7 -7
- package/lib/view/opEx/opExForTimeFrameEpic.d.ts +2 -1
- package/lib/view/opEx/opExForTimeFrameEpic.js +22 -7
- package/lib/view/opEx/opExReducer.d.ts +12 -3
- package/lib/view/opEx/opExReducer.js +168 -31
- package/lib/view/opEx/opExSelector.d.ts +1 -1
- package/lib/view/opEx/opExSelector.js +28 -10
- package/lib/view/opEx/opExState.d.ts +6 -0
- package/lib/view/profitAndLoss/profitAndLossForTimeframeEpic.js +4 -0
- package/lib/view/profitAndLoss/profitAndLossReducer.d.ts +3 -1
- package/lib/view/profitAndLoss/profitAndLossReducer.js +9 -2
- package/lib/view/reportUIOptions/updateReportUIOptionCOABalancesRangeEpic.d.ts +15 -0
- package/lib/view/revenue/fetchRevenueWithForecastEpic.d.ts +1 -0
- package/lib/view/revenue/revenueEpic.js +7 -7
- package/lib/view/revenue/revenueForTimeframeEpic.d.ts +2 -1
- package/lib/view/revenue/revenueForTimeframeEpic.js +24 -9
- package/lib/view/revenue/revenueReducer.d.ts +12 -3
- package/lib/view/revenue/revenueReducer.js +182 -39
- package/lib/view/revenue/revenueSelector.d.ts +1 -1
- package/lib/view/revenue/revenueSelector.js +30 -10
- package/lib/view/revenue/revenueState.d.ts +8 -0
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import { getDataAvailableDateRange, getNumberOfPeriods, } from '../../commonStat
|
|
|
5
5
|
import { extractThisPeriod, getMatchingThisPeriod, } from '../../commonStateTypes/viewAndReport/thisPeriodHelpers';
|
|
6
6
|
import { mapInsightsPayloadtoInsightsState } from '../../entity/insights/insightSummaryPayload';
|
|
7
7
|
import { clearAll } from '../../rootActions';
|
|
8
|
+
import { date } from '../../zeniDayJS';
|
|
8
9
|
import { getAvailableCOABalancesWithMostRecentNonZeroBalance, getInitialSelectedAndHighlightedRanges, getSelectedAndHighlightedRangesForNewSelection, getSelectedAndHighlightedRangesForThisPeriod, } from '../common/getSelectedAndHighlightedRanges';
|
|
9
10
|
export const defaultPeriods = {
|
|
10
11
|
numberOfPeriods: 8,
|
|
@@ -14,6 +15,12 @@ export const initialState = {
|
|
|
14
15
|
error: undefined,
|
|
15
16
|
cashPosition: undefined,
|
|
16
17
|
dataAvailable: undefined,
|
|
18
|
+
// Custom-date slots must be explicit in initialState so clearAll's
|
|
19
|
+
// Object.assign(draft, initialState) resets them on tenant switch (absent
|
|
20
|
+
// keys would otherwise survive). Mirrors cashInCashOut.
|
|
21
|
+
customCashPosition: undefined,
|
|
22
|
+
customDataAvailable: undefined,
|
|
23
|
+
customFetchState: undefined,
|
|
17
24
|
firstMonthOfFY: 1,
|
|
18
25
|
reportId: 'cash_position',
|
|
19
26
|
reportTitle: '',
|
|
@@ -45,52 +52,54 @@ const cashPosition = createSlice({
|
|
|
45
52
|
initialState,
|
|
46
53
|
reducers: {
|
|
47
54
|
fetchCashPosition: {
|
|
48
|
-
prepare(fetchFullReport = false) {
|
|
49
|
-
return { payload: { fetchFullReport } };
|
|
55
|
+
prepare(fetchFullReport = false, startDate, endDate) {
|
|
56
|
+
return { payload: { fetchFullReport, startDate, endDate } };
|
|
50
57
|
},
|
|
51
|
-
reducer(draft) {
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
reducer(draft, action) {
|
|
59
|
+
// Route by the fetch's own dates, not the global flag: a custom fetch
|
|
60
|
+
// (carries start/end) marks customFetchState so the shared fetchState the
|
|
61
|
+
// dashboard reads is never left In-Progress by a detail-screen fetch.
|
|
62
|
+
const isCustomDateRange = action.payload.startDate != null && action.payload.endDate != null;
|
|
63
|
+
if (isCustomDateRange) {
|
|
64
|
+
draft.customFetchState = {
|
|
65
|
+
fetchState: 'In-Progress',
|
|
66
|
+
error: undefined,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
draft.fetchState = 'In-Progress';
|
|
71
|
+
draft.error = undefined;
|
|
72
|
+
}
|
|
54
73
|
},
|
|
55
74
|
},
|
|
56
|
-
updateCashPosition
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
draft.cashPosition =
|
|
64
|
-
mapCOABalanceGroupedWithAccountIDsPayloadToCOABalanceGroupedWithAccountIDs({
|
|
65
|
-
...cash_position,
|
|
66
|
-
account_ids: reportDataPayload.report.accounts.map((payload) => payload.account_id),
|
|
67
|
-
});
|
|
68
|
-
draft.insights = mapInsightsPayloadtoInsightsState(insights);
|
|
69
|
-
draft.dataAvailable = report.dataAvailable
|
|
70
|
-
? report.dataAvailable
|
|
71
|
-
: getDataAvailableDateRange(draft.cashPosition);
|
|
72
|
-
if (draft.dataAvailable != null) {
|
|
73
|
-
const timeframe = draft.uiState.timeframe;
|
|
74
|
-
const coaBalances = getAvailableCOABalancesWithMostRecentNonZeroBalance(draft.firstMonthOfFY, timeframe, [draft.cashPosition], draft.dataAvailable);
|
|
75
|
-
const selectionRanges = getInitialSelectedAndHighlightedRanges({
|
|
76
|
-
firstMonthOfFY: draft.firstMonthOfFY,
|
|
77
|
-
timeframe,
|
|
78
|
-
coaBalances,
|
|
79
|
-
orderBy: 'ascending_date',
|
|
80
|
-
maxNumOfPeriodsToSelect: defaultPeriods.numberOfPeriods,
|
|
81
|
-
maxNumOfPeriodsToHighlight: initialState.uiState.maxNumOfPeriodsToHighlight,
|
|
82
|
-
});
|
|
83
|
-
if (selectionRanges?.selectedCOABalancesRange != null) {
|
|
84
|
-
draft.uiState.selectedCOABalancesRange =
|
|
85
|
-
selectionRanges?.selectedCOABalancesRange;
|
|
86
|
-
}
|
|
87
|
-
draft.uiState.highlightedCOABalancesRange =
|
|
88
|
-
selectionRanges?.highlightedCOABalancesRange;
|
|
89
|
-
}
|
|
75
|
+
updateCashPosition: {
|
|
76
|
+
prepare(reportData, isCustomDateRange = false) {
|
|
77
|
+
return { payload: { reportData, isCustomDateRange } };
|
|
78
|
+
},
|
|
79
|
+
reducer(draft, action) {
|
|
80
|
+
doUpdateCashPosition(draft, action.payload.reportData, action.payload.isCustomDateRange);
|
|
81
|
+
},
|
|
90
82
|
},
|
|
91
|
-
updateCashPositionFailure
|
|
92
|
-
|
|
93
|
-
|
|
83
|
+
updateCashPositionFailure: {
|
|
84
|
+
prepare(error, isCustomDateRange = false) {
|
|
85
|
+
return { payload: { error, isCustomDateRange } };
|
|
86
|
+
},
|
|
87
|
+
reducer(draft, action) {
|
|
88
|
+
// Route the error like the request/success: a custom (dated) fetch's
|
|
89
|
+
// failure lands in customFetchState so the shared default the dashboard
|
|
90
|
+
// reads isn't marked Error, and the detail's customFetchState isn't left
|
|
91
|
+
// stuck In-Progress.
|
|
92
|
+
if (action.payload.isCustomDateRange === true) {
|
|
93
|
+
draft.customFetchState = {
|
|
94
|
+
fetchState: 'Error',
|
|
95
|
+
error: action.payload.error,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
draft.fetchState = 'Error';
|
|
100
|
+
draft.error = action.payload.error;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
94
103
|
},
|
|
95
104
|
updateCashPositionAdditionalBalancesSelection(draft, action) {
|
|
96
105
|
const { cashPosition, dataAvailable, uiState } = draft;
|
|
@@ -105,7 +114,7 @@ const cashPosition = createSlice({
|
|
|
105
114
|
action.payload.additionalBalances.length != 0
|
|
106
115
|
? true
|
|
107
116
|
: false;
|
|
108
|
-
if (dataAvailable != null) {
|
|
117
|
+
if (dataAvailable != null && draft.uiState.customTimeframe == null) {
|
|
109
118
|
const coaBalances = getAvailableCOABalancesWithMostRecentNonZeroBalance(draft.firstMonthOfFY, uiState.timeframe, [cashPosition], dataAvailable);
|
|
110
119
|
const thisPeriod = extractThisPeriod(draft.firstMonthOfFY, timeframe, selectedCOABalancesRange, coaBalances);
|
|
111
120
|
if (thisPeriod != null &&
|
|
@@ -134,6 +143,46 @@ const cashPosition = createSlice({
|
|
|
134
143
|
}
|
|
135
144
|
},
|
|
136
145
|
updateCashPositionCOABalancesRange(draft, action) {
|
|
146
|
+
if (action.payload.isCustomDateRange != null) {
|
|
147
|
+
if (action.payload.isCustomDateRange === true) {
|
|
148
|
+
// Collapse the two raw date strings into a single customTimeframe object.
|
|
149
|
+
// Presence of customTimeframe signals custom-mode active (replaces the
|
|
150
|
+
// old isCustomDateRangeActive boolean + customStartDate/customEndDate).
|
|
151
|
+
draft.uiState.customTimeframe =
|
|
152
|
+
action.payload.customStartDate != null &&
|
|
153
|
+
action.payload.customEndDate != null
|
|
154
|
+
? {
|
|
155
|
+
startDate: date(action.payload.customStartDate),
|
|
156
|
+
endDate: date(action.payload.customEndDate),
|
|
157
|
+
}
|
|
158
|
+
: undefined;
|
|
159
|
+
// Invalidate the shared default-window fetchState so dashboardEpic
|
|
160
|
+
// refetches the default data on the next dashboard visit — the custom
|
|
161
|
+
// fetch writes into the shared report store, and this lets the card
|
|
162
|
+
// self-heal without a reload. The detail screen reads customFetchState.
|
|
163
|
+
draft.fetchState = 'Not-Started';
|
|
164
|
+
draft.error = undefined;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
draft.uiState.customTimeframe = undefined;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// A custom date range drives the visible window directly: honor the exact
|
|
171
|
+
// numberOfPeriods + thisPeriod from the payload instead of re-deriving
|
|
172
|
+
// (which clamps the range to the available/non-zero periods).
|
|
173
|
+
if (draft.uiState.customTimeframe != null) {
|
|
174
|
+
draft.uiState.selectedCOABalancesRange =
|
|
175
|
+
action.payload.selectedCOABalancesRange;
|
|
176
|
+
if (action.payload.highlightedCOABalancesRange != null) {
|
|
177
|
+
// Clamp the highlight to the same cap the non-custom path enforces so
|
|
178
|
+
// a wide custom range doesn't highlight all N periods at once.
|
|
179
|
+
draft.uiState.highlightedCOABalancesRange = {
|
|
180
|
+
...action.payload.highlightedCOABalancesRange,
|
|
181
|
+
numberOfPeriods: Math.min(action.payload.highlightedCOABalancesRange.numberOfPeriods, draft.uiState.maxNumOfPeriodsToHighlight),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
137
186
|
const { cashPosition, dataAvailable, uiState } = draft;
|
|
138
187
|
if (dataAvailable != null &&
|
|
139
188
|
action.payload.selectedCOABalancesRange.thisPeriod != null) {
|
|
@@ -174,6 +223,13 @@ const cashPosition = createSlice({
|
|
|
174
223
|
}
|
|
175
224
|
},
|
|
176
225
|
updateCashPositionThisPeriod(draft, action) {
|
|
226
|
+
// In custom-date mode the picker drives the window; skip re-deriving the
|
|
227
|
+
// selected/highlighted ranges from the default dataAvailable (the epic
|
|
228
|
+
// re-dispatches the default thisPeriod on every response, which would
|
|
229
|
+
// otherwise clamp the custom window back to the default).
|
|
230
|
+
if (draft.uiState.customTimeframe != null) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
177
233
|
const { cashPosition, dataAvailable, uiState } = draft;
|
|
178
234
|
if (dataAvailable != null && action.payload.thisPeriod != null) {
|
|
179
235
|
const coaBalances = getAvailableCOABalancesWithMostRecentNonZeroBalance(draft.firstMonthOfFY, uiState.timeframe, [cashPosition], dataAvailable);
|
|
@@ -218,7 +274,7 @@ const cashPosition = createSlice({
|
|
|
218
274
|
draft.uiState.maxNumOfPeriodsToHighlight =
|
|
219
275
|
initialState.uiState.maxNumOfPeriodsToHighlight;
|
|
220
276
|
}
|
|
221
|
-
if (dataAvailable != null) {
|
|
277
|
+
if (dataAvailable != null && draft.uiState.customTimeframe == null) {
|
|
222
278
|
const timeframe = draft.uiState.timeframe;
|
|
223
279
|
const coaBalances = getAvailableCOABalancesWithMostRecentNonZeroBalance(draft.firstMonthOfFY, timeframe, [cashPosition], dataAvailable);
|
|
224
280
|
const selectionRanges = getInitialSelectedAndHighlightedRanges({
|
|
@@ -258,5 +314,60 @@ const cashPosition = createSlice({
|
|
|
258
314
|
});
|
|
259
315
|
},
|
|
260
316
|
});
|
|
317
|
+
const doUpdateCashPosition = (draft, reportDataPayload, isCustomDateRange) => {
|
|
318
|
+
const report = mapReportPayloadToReport(reportDataPayload.report);
|
|
319
|
+
const { cash_position, insights } = reportDataPayload.report;
|
|
320
|
+
const cashPosition = mapCOABalanceGroupedWithAccountIDsPayloadToCOABalanceGroupedWithAccountIDs({
|
|
321
|
+
...cash_position,
|
|
322
|
+
account_ids: reportDataPayload.report.accounts.map((payload) => payload.account_id),
|
|
323
|
+
});
|
|
324
|
+
const dataAvailable = report.dataAvailable
|
|
325
|
+
? report.dataAvailable
|
|
326
|
+
: getDataAvailableDateRange(cashPosition);
|
|
327
|
+
// Route custom-date results (identified by the fetch's own dates, not the
|
|
328
|
+
// global flag) to the separate slots so the dashboard card (which reads
|
|
329
|
+
// cashPosition / dataAvailable) keeps the default window.
|
|
330
|
+
if (isCustomDateRange) {
|
|
331
|
+
draft.customCashPosition = cashPosition;
|
|
332
|
+
// Don't let a custom slice shrink the shared dataAvailable the dashboard uses;
|
|
333
|
+
// a default fetch always updates it, a custom one only populates it on first
|
|
334
|
+
// load (when nothing has set it yet).
|
|
335
|
+
draft.customDataAvailable = dataAvailable;
|
|
336
|
+
if (draft.dataAvailable == null) {
|
|
337
|
+
draft.dataAvailable = dataAvailable;
|
|
338
|
+
}
|
|
339
|
+
draft.customFetchState = { fetchState: 'Completed', error: undefined };
|
|
340
|
+
// Skip insights on a custom fetch so the dashboard card keeps the
|
|
341
|
+
// default-window copy (matches cashInCashOut / opEx / revenue / netBurn).
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
Object.assign(draft, report);
|
|
345
|
+
draft.fetchState = 'Completed';
|
|
346
|
+
draft.error = undefined;
|
|
347
|
+
draft.cashPosition = cashPosition;
|
|
348
|
+
draft.insights = mapInsightsPayloadtoInsightsState(insights);
|
|
349
|
+
draft.dataAvailable = dataAvailable;
|
|
350
|
+
if (draft.dataAvailable != null &&
|
|
351
|
+
// A custom date range drives the window; don't reset it to the default on
|
|
352
|
+
// every report receipt (this is what clamped wide ranges back to 8).
|
|
353
|
+
draft.uiState.customTimeframe == null) {
|
|
354
|
+
const timeframe = draft.uiState.timeframe;
|
|
355
|
+
const coaBalances = getAvailableCOABalancesWithMostRecentNonZeroBalance(draft.firstMonthOfFY, timeframe, [draft.cashPosition], draft.dataAvailable);
|
|
356
|
+
const selectionRanges = getInitialSelectedAndHighlightedRanges({
|
|
357
|
+
firstMonthOfFY: draft.firstMonthOfFY,
|
|
358
|
+
timeframe,
|
|
359
|
+
coaBalances,
|
|
360
|
+
orderBy: 'ascending_date',
|
|
361
|
+
maxNumOfPeriodsToSelect: defaultPeriods.numberOfPeriods,
|
|
362
|
+
maxNumOfPeriodsToHighlight: initialState.uiState.maxNumOfPeriodsToHighlight,
|
|
363
|
+
});
|
|
364
|
+
if (selectionRanges?.selectedCOABalancesRange != null) {
|
|
365
|
+
draft.uiState.selectedCOABalancesRange =
|
|
366
|
+
selectionRanges?.selectedCOABalancesRange;
|
|
367
|
+
}
|
|
368
|
+
draft.uiState.highlightedCOABalancesRange =
|
|
369
|
+
selectionRanges?.highlightedCOABalancesRange;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
261
372
|
export const { fetchCashPosition, updateCashPosition, updateCashPositionFailure, updateCashPositionTimeFrame, updateCashPositionCOABalancesRange, updateCashPositionAdditionalBalancesSelection, updateCashPositionThisPeriod, updateCashPositionDownloadState, updateCashPositionUIState, } = cashPosition.actions;
|
|
262
373
|
export default cashPosition.reducer;
|
|
@@ -4,20 +4,30 @@ import { getCOABalances } from '../../commonStateTypes/coaBalance/getCOABalances
|
|
|
4
4
|
import { getNumberOfPeriods } from '../../commonStateTypes/dataAvailable';
|
|
5
5
|
import { toTimeframeTick, } from '../../commonStateTypes/timeframeTick';
|
|
6
6
|
import { getAccountsOrdered } from '../../entity/account/accountSelector';
|
|
7
|
-
export const getCashPosition = (accountState, cashPositionState, filter) => {
|
|
7
|
+
export const getCashPosition = (accountState, cashPositionState, filter, readCustomRangeSlot) => {
|
|
8
|
+
// The detail screen opts into the isolated custom slot when custom mode is
|
|
9
|
+
// active; the dashboard (no flag) reads the default slot, so a default fetch
|
|
10
|
+
// can't clobber the custom partial-period data the detail is showing.
|
|
11
|
+
const useCustomRange = readCustomRangeSlot === true &&
|
|
12
|
+
cashPositionState.uiState.customTimeframe != null;
|
|
13
|
+
const activeCashPosition = useCustomRange
|
|
14
|
+
? cashPositionState.customCashPosition
|
|
15
|
+
: cashPositionState.cashPosition;
|
|
16
|
+
const dataAvailable = useCustomRange
|
|
17
|
+
? cashPositionState.customDataAvailable
|
|
18
|
+
: cashPositionState.dataAvailable;
|
|
8
19
|
const cashPosition = {
|
|
9
20
|
itemOrder: 'last_balance_descending',
|
|
10
21
|
accountsOrdered: getAccountsOrdered(filter, accountState, {
|
|
11
|
-
accountIDs:
|
|
22
|
+
accountIDs: activeCashPosition?.accountIDs ?? [],
|
|
12
23
|
reportId: 'cash_position',
|
|
13
24
|
}, 'cashPosition'),
|
|
14
|
-
...getCOABalances(
|
|
25
|
+
...getCOABalances(activeCashPosition, filter),
|
|
15
26
|
};
|
|
16
27
|
let allTimeframeTicks = [];
|
|
17
|
-
const dataAvailable = cashPositionState.dataAvailable;
|
|
18
28
|
if (dataAvailable != null) {
|
|
19
29
|
const noFilter = newBalancesFilterForDateRange(cashPositionState.firstMonthOfFY, cashPositionState.uiState.timeframe, dataAvailable, 'ascending_date', []);
|
|
20
|
-
const cashPositionBalances = getCOABalances(
|
|
30
|
+
const cashPositionBalances = getCOABalances(activeCashPosition, noFilter);
|
|
21
31
|
allTimeframeTicks = cashPositionBalances.balances.map((balance) => toTimeframeTick(balance));
|
|
22
32
|
}
|
|
23
33
|
return {
|
|
@@ -31,10 +41,19 @@ export const getCashPosition = (accountState, cashPositionState, filter) => {
|
|
|
31
41
|
reportTitle: cashPositionState.reportTitle,
|
|
32
42
|
version: cashPositionState.version,
|
|
33
43
|
status: cashPositionState.status,
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
// The detail screen opts into customFetchState so its loading/complete state
|
|
45
|
+
// tracks the custom fetch; the dashboard (no flag) reads the shared
|
|
46
|
+
// fetchState, which custom-mode activation reset so dashboardEpic refetches.
|
|
47
|
+
fetchState: useCustomRange && cashPositionState.customFetchState != null
|
|
48
|
+
? cashPositionState.customFetchState.fetchState
|
|
49
|
+
: cashPositionState.fetchState,
|
|
50
|
+
error: useCustomRange && cashPositionState.customFetchState != null
|
|
51
|
+
? cashPositionState.customFetchState.error
|
|
52
|
+
: cashPositionState.error,
|
|
36
53
|
insights: cashPositionState.insights ?? [],
|
|
37
54
|
downloadState: cashPositionState.uiState.downloadState,
|
|
55
|
+
// Surface the customTimeframe so the detail picker can restore the selection.
|
|
56
|
+
customTimeframe: cashPositionState.uiState.customTimeframe,
|
|
38
57
|
};
|
|
39
58
|
};
|
|
40
59
|
export const getCashPositionUIState = createSelector((state) => state.cashPositionState, (state) => {
|
|
@@ -60,11 +79,11 @@ export const getCashPositionUIState = createSelector((state) => state.cashPositi
|
|
|
60
79
|
export const getCashPositionForSelectedRange = createSelector((state) => state.accountState, (state) => state.cashPositionState, (accountState, cashPositionState) => {
|
|
61
80
|
const { timeframe, selectedCOABalancesRange, selectedAdditionalBalances } = cashPositionState.uiState;
|
|
62
81
|
const noFilter = newBalancesFilter(cashPositionState.firstMonthOfFY, timeframe, selectedCOABalancesRange.numberOfPeriods, selectedCOABalancesRange.thisPeriod, 'ascending_date', selectedAdditionalBalances);
|
|
63
|
-
return getCashPosition(accountState, cashPositionState, noFilter);
|
|
82
|
+
return getCashPosition(accountState, cashPositionState, noFilter, true);
|
|
64
83
|
});
|
|
65
84
|
export const getCashPositionForHighlightedRange = createSelector((state) => state.accountState, (state) => state.cashPositionState, (accountState, cashPositionState) => {
|
|
66
85
|
const { timeframe, highlightedCOABalancesRange, selectedAdditionalBalances, maxNumOfPeriodsToHighlight, } = cashPositionState.uiState;
|
|
67
86
|
const filter = newBalancesFilter(cashPositionState.firstMonthOfFY, timeframe, highlightedCOABalancesRange?.numberOfPeriods ??
|
|
68
87
|
maxNumOfPeriodsToHighlight, highlightedCOABalancesRange?.thisPeriod, 'ascending_date', selectedAdditionalBalances);
|
|
69
|
-
return getCashPosition(accountState, cashPositionState, filter);
|
|
88
|
+
return getCashPosition(accountState, cashPositionState, filter, true);
|
|
70
89
|
});
|
|
@@ -44,9 +44,14 @@ export function toActualMatchingTransactionSearchResult(payload, fallbackCurrenc
|
|
|
44
44
|
accountDescription: payload.account_description ?? undefined,
|
|
45
45
|
accountingClassId: payload.accounting_class_id ?? undefined,
|
|
46
46
|
accountingClassName: payload.accounting_class_name ?? undefined,
|
|
47
|
+
categorizationStatus: payload.categorization_status == null ||
|
|
48
|
+
payload.categorization_status === ''
|
|
49
|
+
? undefined
|
|
50
|
+
: payload.categorization_status,
|
|
47
51
|
currencyCode,
|
|
48
52
|
currencySymbol: resolveMatchSearchCurrencySymbol(payload, currencyCode, fallbackCurrency),
|
|
49
53
|
transactionMemo: payload.transaction_memo ?? undefined,
|
|
54
|
+
lineDescription: payload.line_description ?? undefined,
|
|
50
55
|
transactionDescription: payload.transaction_description ?? undefined,
|
|
51
56
|
transactionTypeName: payload.transaction_type_name ?? undefined,
|
|
52
57
|
syncToken: payload.sync_token == null ? '' : String(payload.sync_token),
|
|
@@ -2,12 +2,12 @@ import { from } from 'rxjs';
|
|
|
2
2
|
import { filter, mergeMap } from 'rxjs/operators';
|
|
3
3
|
import { fetchNetBurnOrIncome, fetchNetBurnOrIncomeForTimeframe, getState, } from './netBurnOrIncomeReducer';
|
|
4
4
|
export const fetchNetBurnOrIncomeEpic = (actions$, state) => actions$.pipe(filter(fetchNetBurnOrIncome.match), mergeMap((action) => {
|
|
5
|
-
const { cacheOverride, forecastId } = action.payload;
|
|
5
|
+
const { cacheOverride, endDate, forecastId, startDate } = action.payload;
|
|
6
6
|
if (cacheOverride === true) {
|
|
7
7
|
return from([
|
|
8
|
-
fetchNetBurnOrIncomeForTimeframe('month', forecastId),
|
|
9
|
-
fetchNetBurnOrIncomeForTimeframe('quarter', forecastId),
|
|
10
|
-
fetchNetBurnOrIncomeForTimeframe('year', forecastId),
|
|
8
|
+
fetchNetBurnOrIncomeForTimeframe('month', forecastId, true, false, startDate, endDate),
|
|
9
|
+
fetchNetBurnOrIncomeForTimeframe('quarter', forecastId, true, false, startDate, endDate),
|
|
10
|
+
fetchNetBurnOrIncomeForTimeframe('year', forecastId, true, false, startDate, endDate),
|
|
11
11
|
]);
|
|
12
12
|
}
|
|
13
13
|
else {
|
|
@@ -16,15 +16,15 @@ export const fetchNetBurnOrIncomeEpic = (actions$, state) => actions$.pipe(filte
|
|
|
16
16
|
const timeframeFetchState = netBurnOrIncomeState.fetchState;
|
|
17
17
|
if (timeframeFetchState['month'].fetchState !== 'In-Progress' &&
|
|
18
18
|
timeframeFetchState['month'].fetchState !== 'Completed') {
|
|
19
|
-
fetchActions.push(fetchNetBurnOrIncomeForTimeframe('month', forecastId));
|
|
19
|
+
fetchActions.push(fetchNetBurnOrIncomeForTimeframe('month', forecastId, true, false, startDate, endDate));
|
|
20
20
|
}
|
|
21
21
|
if (timeframeFetchState['quarter'].fetchState !== 'In-Progress' &&
|
|
22
22
|
timeframeFetchState['quarter'].fetchState !== 'Completed') {
|
|
23
|
-
fetchActions.push(fetchNetBurnOrIncomeForTimeframe('quarter', forecastId));
|
|
23
|
+
fetchActions.push(fetchNetBurnOrIncomeForTimeframe('quarter', forecastId, true, false, startDate, endDate));
|
|
24
24
|
}
|
|
25
25
|
if (timeframeFetchState['year'].fetchState !== 'In-Progress' &&
|
|
26
26
|
timeframeFetchState['year'].fetchState !== 'Completed') {
|
|
27
|
-
fetchActions.push(fetchNetBurnOrIncomeForTimeframe('year', forecastId));
|
|
27
|
+
fetchActions.push(fetchNetBurnOrIncomeForTimeframe('year', forecastId, true, false, startDate, endDate));
|
|
28
28
|
}
|
|
29
29
|
return from(fetchActions);
|
|
30
30
|
}
|
|
@@ -5,9 +5,15 @@ import { updateCalculatedSectionAccountsView, updateSectionAccountsView, } from
|
|
|
5
5
|
import { createZeniAPIStatus, isSuccessResponse } from '../../responsePayload';
|
|
6
6
|
import { getTimeSeriesGroup } from '../common/reportsHelpers';
|
|
7
7
|
import { fetchNetBurnOrIncomeForTimeframe, updateNetBurnOrIncomeForTimeframe, updateNetBurnOrIncomeForTimeframeFailure, updateNetBurnOrIncomeThisPeriod, } from './netBurnOrIncomeReducer';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
// Reserved forecast-slot marker so custom-date section-accounts data lives under
|
|
9
|
+
// its own key (net_burn_or_income+<marker>) and a default fetch can never
|
|
10
|
+
// overwrite it. Real forecast ids are UUIDs, so this never collides.
|
|
11
|
+
export const CUSTOM_DATE_RANGE_SECTION_MARKER = '__custom_date_range__';
|
|
12
|
+
export const reportIdUniqueKey = (forecastId, isCustomDateRange) => isCustomDateRange === true
|
|
13
|
+
? `net_burn_or_income+${CUSTOM_DATE_RANGE_SECTION_MARKER}`
|
|
14
|
+
: forecastId != null
|
|
15
|
+
? `net_burn_or_income+${forecastId}`
|
|
16
|
+
: 'net_burn_or_income';
|
|
11
17
|
export const fetchNetBurnOrIncomeForTimeframeEpic = (actions$, state, zeniAPI) => actions$.pipe(filter(fetchNetBurnOrIncomeForTimeframe.match), mergeMap((action) => {
|
|
12
18
|
const queryValue = {
|
|
13
19
|
is_full_response: action.payload.fetchFullReport,
|
|
@@ -18,20 +24,27 @@ export const fetchNetBurnOrIncomeForTimeframeEpic = (actions$, state, zeniAPI) =
|
|
|
18
24
|
queryValue.forecast_id = action.payload.forecastId;
|
|
19
25
|
queryValue.is_forecast_vs_actual = true;
|
|
20
26
|
}
|
|
27
|
+
if (action.payload.startDate != null && action.payload.endDate != null) {
|
|
28
|
+
queryValue.custom_start_date = action.payload.startDate;
|
|
29
|
+
queryValue.custom_end_date = action.payload.endDate;
|
|
30
|
+
}
|
|
21
31
|
return zeniAPI
|
|
22
32
|
.getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/2.0/reports/net_burn_or_income?query=${encodeURIComponent(JSON.stringify(queryValue))}`)
|
|
23
33
|
.pipe(mergeMap((response) => {
|
|
24
34
|
if (response.data != null && isSuccessResponse(response)) {
|
|
25
35
|
const actions = [];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
actions.push(updateSectionAccountsView(action.payload.timeframe, '
|
|
29
|
-
actions.push(
|
|
30
|
-
actions.push(
|
|
36
|
+
const isCustomDateRange = action.payload.startDate != null &&
|
|
37
|
+
action.payload.endDate != null;
|
|
38
|
+
actions.push(updateSectionAccountsView(action.payload.timeframe, 'cogs', reportIdUniqueKey(action.payload.forecastId, isCustomDateRange), response.data.report.cogs, response.data.report.accounts, response.data.report.account_groups, response.data.report.currency));
|
|
39
|
+
actions.push(updateSectionAccountsView(action.payload.timeframe, 'expenses', reportIdUniqueKey(action.payload.forecastId, isCustomDateRange), response.data.report.expenses, response.data.report.accounts, response.data.report.account_groups, response.data.report.currency));
|
|
40
|
+
actions.push(updateSectionAccountsView(action.payload.timeframe, 'income', reportIdUniqueKey(action.payload.forecastId, isCustomDateRange), response.data.report.income, response.data.report.accounts, response.data.report.account_groups, response.data.report.currency));
|
|
41
|
+
actions.push(updateCalculatedSectionAccountsView(action.payload.timeframe, 'grossBurn', reportIdUniqueKey(action.payload.forecastId, isCustomDateRange), response.data.report.gross_burn, response.data.report.currency));
|
|
42
|
+
actions.push(updateCalculatedSectionAccountsView(action.payload.timeframe, 'netBurnOrIncome', reportIdUniqueKey(action.payload.forecastId, isCustomDateRange), response.data.report.net_burn_or_income, response.data.report.currency));
|
|
31
43
|
actions.push(updateNetBurnOrIncomeForTimeframe({
|
|
32
44
|
timeframe: action.payload.timeframe,
|
|
33
45
|
forecastId: action.payload.forecastId,
|
|
34
46
|
report: response.data,
|
|
47
|
+
isCustomDateRange,
|
|
35
48
|
}));
|
|
36
49
|
if (action.payload.forecastId == null &&
|
|
37
50
|
action.payload.timeframe === 'month') {
|
|
@@ -46,8 +59,10 @@ export const fetchNetBurnOrIncomeForTimeframeEpic = (actions$, state, zeniAPI) =
|
|
|
46
59
|
return from(actions);
|
|
47
60
|
}
|
|
48
61
|
else {
|
|
49
|
-
return of(updateNetBurnOrIncomeForTimeframeFailure(response.status, action.payload.forecastId, action.payload.timeframe
|
|
62
|
+
return of(updateNetBurnOrIncomeForTimeframeFailure(response.status, action.payload.forecastId, action.payload.timeframe, action.payload.startDate != null &&
|
|
63
|
+
action.payload.endDate != null));
|
|
50
64
|
}
|
|
51
65
|
}), catchError((error) => of(updateNetBurnOrIncomeForTimeframeFailure(createZeniAPIStatus('Unexpected Error', 'Net burn or income REST API call errored out' +
|
|
52
|
-
JSON.stringify(error)), action.payload.forecastId, action.payload.timeframe
|
|
66
|
+
JSON.stringify(error)), action.payload.forecastId, action.payload.timeframe, action.payload.startDate != null &&
|
|
67
|
+
action.payload.endDate != null))));
|
|
53
68
|
}));
|