@zeniai/client-epic-state 5.2.93 → 5.2.94
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/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/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
|
@@ -9,6 +9,7 @@ export interface TimeframeTick {
|
|
|
9
9
|
export interface TimeframeTickWithMetaData extends TimeframeTick {
|
|
10
10
|
balanceType: COABalanceType;
|
|
11
11
|
currencySymbol: string;
|
|
12
|
+
label?: string;
|
|
12
13
|
}
|
|
13
14
|
export declare const toTimeframeTick: (balance: COABalance) => TimeframeTickWithMetaData;
|
|
14
15
|
export declare const toTimePeriod: (tick: TimeframeTick) => TimePeriod;
|
|
@@ -2,6 +2,7 @@ import { COABalanceMonthQuarterYearTimeframe } from '../coaBalance/coaBalance';
|
|
|
2
2
|
import { COABalanceType } from '../coaBalance/coaBalanceType';
|
|
3
3
|
import { COABalancesOrder, COABalancesRange, COABalancesRangeWithThisPeriod } from '../coaBalance/coaBalancesFilter';
|
|
4
4
|
import { FetchState, ID } from '../common';
|
|
5
|
+
import { TimeframeTick } from '../timeframeTick';
|
|
5
6
|
import { ReportFormat } from './viewAndReport';
|
|
6
7
|
export interface ReportUIState {
|
|
7
8
|
displayOrder: COABalancesOrder;
|
|
@@ -12,6 +13,7 @@ export interface ReportUIState {
|
|
|
12
13
|
selectedAdditionalBalances: COABalanceType[];
|
|
13
14
|
selectedCOABalancesRange: COABalancesRange;
|
|
14
15
|
timeframe: COABalanceMonthQuarterYearTimeframe;
|
|
16
|
+
customTimeframe?: TimeframeTick;
|
|
15
17
|
highlightedCOABalancesRange?: COABalancesRangeWithThisPeriod;
|
|
16
18
|
isForecastModeOn?: boolean;
|
|
17
19
|
selectedForecast?: {
|
|
@@ -3,6 +3,7 @@ import { FetchStateAndError, ID } from '../common';
|
|
|
3
3
|
import { DataAvailable } from '../dataAvailable';
|
|
4
4
|
import { Status } from '../status';
|
|
5
5
|
import { Month } from '../timePeriod';
|
|
6
|
+
import { TimeframeTick } from '../timeframeTick';
|
|
6
7
|
export declare const ALL_REPORT_IDS: readonly ["cash_balance", "card_balance", "expense_automation_card", "operating_expenses", "operating_expenses_by_classes", "operating_expenses_by_projects", "revenue", "revenue_by_classes", "revenue_by_projects", "net_burn_or_income", "net_burn_or_income_story_card", "net_burn_or_income_by_classes", "net_burn_or_income_by_projects", "cash_position", "top_expenses", "profit_and_loss", "profit_and_loss_by_classes", "profit_and_loss_by_projects", "balance_sheet", "balance_sheet_by_classes", "balance_sheet_by_projects", "cash_flow", "cash_flow_by_classes", "cash_flow_by_projects", "dashboard", "finance_statement", "transaction_list_of_account", "transaction_list_missing_receipt", "transaction_list_of_account_by_class", "transaction_list_of_account_by_project", "transaction_detail", "cash_in_cash_out", "insights_dashboard", "company_details", "class_list", "account_list", "account_list_by_type", "vendor", "task_card", "bill_pay", "reimbursement", "bill_pay_card", "reimbursement_card", "bill_pay_promo_card", "zeni_accounts", "zeni_accounts_promo_card", "zeni_treasury", "charge_cards", "other_connection", "reimbursement_promo_card", "onboarding", "operating_expenses_by_vendor", "operating_expenses_by_vendor_summary", "vendors_tab", "accounts_payable_aging", "accounts_receivable_aging", "vendor_global_review", "prepaid_expenses", "fixed_assets", "notification", "rewards_card", "billing", "flux_analysis_operating_expense", "account_reconciliation", "zeni_credit_promo_card", "treasury_promo_card", "excluded_account_reconciliation", "transfer_accounts", "credit_card_transfer_accounts"];
|
|
7
8
|
export declare const toReportID: (v: string) => "cash_balance" | "card_balance" | "expense_automation_card" | "operating_expenses" | "operating_expenses_by_classes" | "operating_expenses_by_projects" | "revenue" | "revenue_by_classes" | "revenue_by_projects" | "net_burn_or_income" | "net_burn_or_income_story_card" | "net_burn_or_income_by_classes" | "net_burn_or_income_by_projects" | "cash_position" | "top_expenses" | "profit_and_loss" | "profit_and_loss_by_classes" | "profit_and_loss_by_projects" | "balance_sheet" | "balance_sheet_by_classes" | "balance_sheet_by_projects" | "cash_flow" | "cash_flow_by_classes" | "cash_flow_by_projects" | "dashboard" | "finance_statement" | "transaction_list_of_account" | "transaction_list_missing_receipt" | "transaction_list_of_account_by_class" | "transaction_list_of_account_by_project" | "transaction_detail" | "cash_in_cash_out" | "insights_dashboard" | "company_details" | "class_list" | "account_list" | "account_list_by_type" | "vendor" | "task_card" | "bill_pay" | "reimbursement" | "bill_pay_card" | "reimbursement_card" | "bill_pay_promo_card" | "zeni_accounts" | "zeni_accounts_promo_card" | "zeni_treasury" | "charge_cards" | "other_connection" | "reimbursement_promo_card" | "onboarding" | "operating_expenses_by_vendor" | "operating_expenses_by_vendor_summary" | "vendors_tab" | "accounts_payable_aging" | "accounts_receivable_aging" | "vendor_global_review" | "prepaid_expenses" | "fixed_assets" | "notification" | "rewards_card" | "billing" | "flux_analysis_operating_expense" | "account_reconciliation" | "zeni_credit_promo_card" | "treasury_promo_card" | "excluded_account_reconciliation" | "transfer_accounts" | "credit_card_transfer_accounts";
|
|
8
9
|
export type ReportID = ReturnType<typeof toReportID>;
|
|
@@ -30,6 +31,7 @@ export interface Report extends View {
|
|
|
30
31
|
*/
|
|
31
32
|
export interface SelectorReport extends Report, FetchStateAndError {
|
|
32
33
|
availableNumberOfPeriods?: number;
|
|
34
|
+
customTimeframe?: TimeframeTick;
|
|
33
35
|
[key: string]: unknown;
|
|
34
36
|
}
|
|
35
37
|
export type PageToken = string | null;
|
|
@@ -5,6 +5,7 @@ export const toTimeframeTick = (balance) => ({
|
|
|
5
5
|
endDate: balance.endDate,
|
|
6
6
|
currencySymbol: balance.balance.currencySymbol,
|
|
7
7
|
balanceType: balance.type,
|
|
8
|
+
label: balance.label,
|
|
8
9
|
});
|
|
9
10
|
export const toTimePeriod = (tick) => ({
|
|
10
11
|
start: toAbsoluteDay(tick.startDate.format('YYYY-MM-DD')), // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
@@ -20,6 +20,10 @@ export const fetchBalanceSheetForTimeframeEpic = (actions$, _, zeniAPI) => actio
|
|
|
20
20
|
is_full_response: action.payload.fetchFullReport,
|
|
21
21
|
time_series_groups: [getTimeSeriesGroup(action.payload.timeframe)],
|
|
22
22
|
};
|
|
23
|
+
if (action.payload.startDate != null && action.payload.endDate != null) {
|
|
24
|
+
queryValue.custom_start_date = action.payload.startDate;
|
|
25
|
+
queryValue.custom_end_date = action.payload.endDate;
|
|
26
|
+
}
|
|
23
27
|
return zeniAPI
|
|
24
28
|
.getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/2.0/reports/balance_sheet?query=${encodeURIComponent(JSON.stringify(queryValue))}`)
|
|
25
29
|
.pipe(mergeMap((response) => {
|
|
@@ -45,9 +45,15 @@ const balanceSheet = createSlice({
|
|
|
45
45
|
},
|
|
46
46
|
},
|
|
47
47
|
fetchBalanceSheetForTimeframe: {
|
|
48
|
-
prepare(timeframe, fetchFullReport = true, cacheOverride = false) {
|
|
48
|
+
prepare(timeframe, fetchFullReport = true, cacheOverride = false, startDate, endDate) {
|
|
49
49
|
return {
|
|
50
|
-
payload: {
|
|
50
|
+
payload: {
|
|
51
|
+
timeframe,
|
|
52
|
+
fetchFullReport,
|
|
53
|
+
cacheOverride,
|
|
54
|
+
startDate,
|
|
55
|
+
endDate,
|
|
56
|
+
},
|
|
51
57
|
};
|
|
52
58
|
},
|
|
53
59
|
reducer(draft, action) {
|
|
@@ -20,6 +20,10 @@ export const fetchCashFlowForTimeframeEpic = (actions$, _, zeniAPI) => actions$.
|
|
|
20
20
|
is_full_response: action.payload.fetchFullReport,
|
|
21
21
|
time_series_groups: [getTimeSeriesGroup(action.payload.timeframe)],
|
|
22
22
|
};
|
|
23
|
+
if (action.payload.startDate != null && action.payload.endDate != null) {
|
|
24
|
+
queryValue.custom_start_date = action.payload.startDate;
|
|
25
|
+
queryValue.custom_end_date = action.payload.endDate;
|
|
26
|
+
}
|
|
23
27
|
return zeniAPI
|
|
24
28
|
.getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/2.0/reports/cash_flow?query=${encodeURIComponent(JSON.stringify(queryValue))}`)
|
|
25
29
|
.pipe(mergeMap((response) => {
|
|
@@ -43,9 +43,9 @@ const cashFlow = createSlice({
|
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
45
|
fetchCashFlowForTimeframe: {
|
|
46
|
-
prepare(timeframe, fetchFullReport = true) {
|
|
46
|
+
prepare(timeframe, fetchFullReport = true, startDate, endDate) {
|
|
47
47
|
return {
|
|
48
|
-
payload: { timeframe, fetchFullReport },
|
|
48
|
+
payload: { timeframe, fetchFullReport, startDate, endDate },
|
|
49
49
|
};
|
|
50
50
|
},
|
|
51
51
|
reducer(draft, action) {
|
|
@@ -4,17 +4,25 @@ import { updateAccounts } from '../../entity/account/accountReducer';
|
|
|
4
4
|
import { updateAccountGroups } from '../../entity/accountGroup/accountGroupReducer';
|
|
5
5
|
import { createZeniAPIStatus, isSuccessResponse } from '../../responsePayload';
|
|
6
6
|
import { fetchCashInCashOut, updateCashInCashOut, updateCashInCashOutFailure, updateCashInCashOutThisPeriod, } from './cashInCashOutReducer';
|
|
7
|
-
export const fetchCashInCashOutEpic = (actions$, state, zeniAPI) => actions$.pipe(filter(fetchCashInCashOut.match), switchMap(() =>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}))
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
7
|
+
export const fetchCashInCashOutEpic = (actions$, state, zeniAPI) => actions$.pipe(filter(fetchCashInCashOut.match), switchMap((action) => {
|
|
8
|
+
const { startDate, endDate } = action.payload;
|
|
9
|
+
// Route by the fetch's own dates, not a global flag: a custom fetch carries
|
|
10
|
+
// start/end, so its results land in the isolated custom slots and the
|
|
11
|
+
// dashboard's default window is untouched.
|
|
12
|
+
const isCustomDateRange = startDate != null && endDate != null;
|
|
13
|
+
const url = `${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/reports/cash_in_cash_out${isCustomDateRange
|
|
14
|
+
? `?query=${encodeURIComponent(JSON.stringify({ custom_end_date: endDate, custom_start_date: startDate }))}`
|
|
15
|
+
: ''}`;
|
|
16
|
+
return zeniAPI.getJSON(url).pipe(mergeMap((response) => {
|
|
17
|
+
if (isSuccessResponse(response)) {
|
|
18
|
+
return of(updateAccounts(response.data?.report.accounts ?? [], 'cash_in_cash_out'), updateAccountGroups(response.data?.report.account_groups ?? [], 'cash_in_cash_out'), updateCashInCashOut(response.data, isCustomDateRange), // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
19
|
+
updateCashInCashOutThisPeriod({
|
|
20
|
+
thisPeriod: state.value.reportUIOptionsState.thisPeriod,
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
return of(updateCashInCashOutFailure(response.status, isCustomDateRange));
|
|
25
|
+
}
|
|
26
|
+
}), catchError((error) => of(updateCashInCashOutFailure(createZeniAPIStatus('Unexpected error', 'CashInCashOut REST API call errored out' +
|
|
27
|
+
JSON.stringify(error)), isCustomDateRange))));
|
|
28
|
+
}));
|
|
@@ -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: 5,
|
|
@@ -15,6 +16,10 @@ export const initialState = {
|
|
|
15
16
|
cashIn: undefined,
|
|
16
17
|
cashOut: undefined,
|
|
17
18
|
netCashFlow: undefined,
|
|
19
|
+
customCashIn: undefined,
|
|
20
|
+
customCashOut: undefined,
|
|
21
|
+
customNetCashFlow: undefined,
|
|
22
|
+
customFetchState: undefined,
|
|
18
23
|
dataAvailable: undefined,
|
|
19
24
|
firstMonthOfFY: 1,
|
|
20
25
|
reportId: 'cash_in_cash_out',
|
|
@@ -49,49 +54,118 @@ const cashInCashOut = createSlice({
|
|
|
49
54
|
name: 'cashInCashOut',
|
|
50
55
|
initialState,
|
|
51
56
|
reducers: {
|
|
52
|
-
fetchCashInCashOut
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
fetchCashInCashOut: {
|
|
58
|
+
prepare(startDate, endDate) {
|
|
59
|
+
return { payload: { endDate, startDate } };
|
|
60
|
+
},
|
|
61
|
+
reducer(draft, action) {
|
|
62
|
+
// Route by per-fetch dates: a custom (dated) fetch marks only the
|
|
63
|
+
// isolated customFetchState so the shared fetchState the dashboard reads
|
|
64
|
+
// stays as custom-mode activation left it (self-heal), and the shared
|
|
65
|
+
// error isn't cleared by a detail-only fetch.
|
|
66
|
+
if (action.payload.startDate != null &&
|
|
67
|
+
action.payload.endDate != null) {
|
|
68
|
+
draft.customFetchState = 'In-Progress';
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
draft.fetchState = 'In-Progress';
|
|
72
|
+
draft.error = undefined;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
55
75
|
},
|
|
56
|
-
updateCashInCashOut
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
mapCOABalanceGroupedPayloadToCOABalanceGrouped(net_cash_flow);
|
|
68
|
-
draft.insights = mapInsightsPayloadtoInsightsState(insights);
|
|
69
|
-
const cashInOrCashOut = draft.cashIn != null ? draft.cashIn : draft.cashOut;
|
|
70
|
-
draft.dataAvailable = report.dataAvailable
|
|
71
|
-
? report.dataAvailable
|
|
72
|
-
: getDataAvailableDateRange(cashInOrCashOut);
|
|
73
|
-
if (draft.dataAvailable != null) {
|
|
74
|
-
const timeframe = draft.uiState.timeframe;
|
|
75
|
-
const coaBalances = getAvailableCOABalancesWithMostRecentNonZeroBalance(draft.firstMonthOfFY, timeframe, [draft.cashIn, draft.cashOut], draft.dataAvailable);
|
|
76
|
-
const selectionRanges = getInitialSelectedAndHighlightedRanges({
|
|
77
|
-
firstMonthOfFY: draft.firstMonthOfFY,
|
|
78
|
-
timeframe,
|
|
79
|
-
coaBalances,
|
|
80
|
-
orderBy: 'ascending_date',
|
|
81
|
-
maxNumOfPeriodsToSelect: defaultPeriods.numberOfPeriods,
|
|
82
|
-
maxNumOfPeriodsToHighlight: initialState.uiState.maxNumOfPeriodsToHighlight,
|
|
83
|
-
});
|
|
84
|
-
if (selectionRanges?.selectedCOABalancesRange != null) {
|
|
85
|
-
draft.uiState.selectedCOABalancesRange =
|
|
86
|
-
selectionRanges?.selectedCOABalancesRange;
|
|
76
|
+
updateCashInCashOut: {
|
|
77
|
+
prepare(data, isCustomDateRange = false) {
|
|
78
|
+
return { payload: { data, isCustomDateRange } };
|
|
79
|
+
},
|
|
80
|
+
reducer(draft, action) {
|
|
81
|
+
const isCustomDateRange = action.payload.isCustomDateRange === true;
|
|
82
|
+
// A custom (dated) fetch marks the isolated scalar customFetchState so the
|
|
83
|
+
// shared fetchState the dashboard reads via hasValidState() stays as
|
|
84
|
+
// custom-mode activation left it (Not-Started), letting the card self-heal.
|
|
85
|
+
if (isCustomDateRange) {
|
|
86
|
+
draft.customFetchState = 'Completed';
|
|
87
87
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
else {
|
|
89
|
+
draft.fetchState = 'Completed';
|
|
90
|
+
draft.error = undefined;
|
|
91
|
+
}
|
|
92
|
+
const report = mapReportPayloadToReport(action.payload.data.report);
|
|
93
|
+
// A custom (dated) fetch must not overwrite the shared report fields the
|
|
94
|
+
// dashboard card reads (firstMonthOfFY, bookCloseDate, reportTitle,
|
|
95
|
+
// status, version) — only its isolated custom* slots below.
|
|
96
|
+
if (!isCustomDateRange) {
|
|
97
|
+
Object.assign(draft, report);
|
|
98
|
+
}
|
|
99
|
+
const { cash_in, cash_out, net_cash_flow, insights } = action.payload.data.report;
|
|
100
|
+
const mappedCashIn = mapCOABalanceGroupedWithIDsPayloadToCOABalanceGroupedWithIDs(cash_in);
|
|
101
|
+
const mappedCashOut = mapCOABalanceGroupedWithIDsPayloadToCOABalanceGroupedWithIDs(cash_out);
|
|
102
|
+
const mappedNetCashFlow = mapCOABalanceGroupedPayloadToCOABalanceGrouped(net_cash_flow);
|
|
103
|
+
// Route custom-date results to the separate slots so the dashboard card
|
|
104
|
+
// (which reads cashIn/cashOut/netCashFlow) keeps the default window.
|
|
105
|
+
if (isCustomDateRange) {
|
|
106
|
+
draft.customCashIn = mappedCashIn;
|
|
107
|
+
draft.customCashOut = mappedCashOut;
|
|
108
|
+
draft.customNetCashFlow = mappedNetCashFlow;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
draft.cashIn = mappedCashIn;
|
|
112
|
+
draft.cashOut = mappedCashOut;
|
|
113
|
+
draft.netCashFlow = mappedNetCashFlow;
|
|
114
|
+
}
|
|
115
|
+
// Insights are shared (read by the dashboard card); a custom slice must
|
|
116
|
+
// not overwrite them.
|
|
117
|
+
if (!isCustomDateRange) {
|
|
118
|
+
draft.insights = mapInsightsPayloadtoInsightsState(insights);
|
|
119
|
+
}
|
|
120
|
+
// Derive dataAvailable from the slot this fetch actually populated.
|
|
121
|
+
const cashInOrCashOut = mappedCashIn != null ? mappedCashIn : mappedCashOut;
|
|
122
|
+
// Don't let a custom slice shrink the shared dataAvailable the dashboard
|
|
123
|
+
// uses for its period count; a default fetch always updates it, a custom
|
|
124
|
+
// one only populates it on first load (when nothing has set it yet).
|
|
125
|
+
if (!isCustomDateRange || draft.dataAvailable == null) {
|
|
126
|
+
draft.dataAvailable = report.dataAvailable
|
|
127
|
+
? report.dataAvailable
|
|
128
|
+
: getDataAvailableDateRange(cashInOrCashOut);
|
|
129
|
+
}
|
|
130
|
+
if (draft.dataAvailable != null &&
|
|
131
|
+
// A custom date range drives the window; don't reset it to the default on
|
|
132
|
+
// every report receipt (this is what clamped wide ranges back to 8).
|
|
133
|
+
draft.uiState.customTimeframe == null) {
|
|
134
|
+
const timeframe = draft.uiState.timeframe;
|
|
135
|
+
const coaBalances = getAvailableCOABalancesWithMostRecentNonZeroBalance(draft.firstMonthOfFY, timeframe, [draft.cashIn, draft.cashOut], draft.dataAvailable);
|
|
136
|
+
const selectionRanges = getInitialSelectedAndHighlightedRanges({
|
|
137
|
+
firstMonthOfFY: draft.firstMonthOfFY,
|
|
138
|
+
timeframe,
|
|
139
|
+
coaBalances,
|
|
140
|
+
orderBy: 'ascending_date',
|
|
141
|
+
maxNumOfPeriodsToSelect: defaultPeriods.numberOfPeriods,
|
|
142
|
+
maxNumOfPeriodsToHighlight: initialState.uiState.maxNumOfPeriodsToHighlight,
|
|
143
|
+
});
|
|
144
|
+
if (selectionRanges?.selectedCOABalancesRange != null) {
|
|
145
|
+
draft.uiState.selectedCOABalancesRange =
|
|
146
|
+
selectionRanges?.selectedCOABalancesRange;
|
|
147
|
+
}
|
|
148
|
+
draft.uiState.highlightedCOABalancesRange =
|
|
149
|
+
selectionRanges?.highlightedCOABalancesRange;
|
|
150
|
+
}
|
|
151
|
+
},
|
|
91
152
|
},
|
|
92
|
-
updateCashInCashOutFailure
|
|
93
|
-
|
|
94
|
-
|
|
153
|
+
updateCashInCashOutFailure: {
|
|
154
|
+
prepare(error, isCustomDateRange = false) {
|
|
155
|
+
return { payload: { error, isCustomDateRange } };
|
|
156
|
+
},
|
|
157
|
+
reducer(draft, action) {
|
|
158
|
+
// Route the error like the request/success: a custom (dated) fetch's
|
|
159
|
+
// failure lands in customFetchState so the shared default the dashboard
|
|
160
|
+
// reads isn't marked Error.
|
|
161
|
+
if (action.payload.isCustomDateRange === true) {
|
|
162
|
+
draft.customFetchState = 'Error';
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
draft.fetchState = 'Error';
|
|
166
|
+
draft.error = action.payload.error;
|
|
167
|
+
}
|
|
168
|
+
},
|
|
95
169
|
},
|
|
96
170
|
updateCashInCashOutAdditionalBalancesSelection(draft, action) {
|
|
97
171
|
const { cashIn, cashOut, dataAvailable } = draft;
|
|
@@ -107,7 +181,7 @@ const cashInCashOut = createSlice({
|
|
|
107
181
|
action.payload.additionalBalances.length != 0
|
|
108
182
|
? true
|
|
109
183
|
: false;
|
|
110
|
-
if (dataAvailable != null) {
|
|
184
|
+
if (dataAvailable != null && draft.uiState.customTimeframe == null) {
|
|
111
185
|
const coaBalances = getAvailableCOABalancesWithMostRecentNonZeroBalance(draft.firstMonthOfFY, timeframe, [cashIn, cashOut], dataAvailable);
|
|
112
186
|
const thisPeriod = extractThisPeriod(draft.firstMonthOfFY, timeframe, selectedCOABalancesRange, coaBalances);
|
|
113
187
|
if (thisPeriod != null &&
|
|
@@ -136,6 +210,45 @@ const cashInCashOut = createSlice({
|
|
|
136
210
|
}
|
|
137
211
|
},
|
|
138
212
|
updateCashInCashOutCOABalancesRange(draft, action) {
|
|
213
|
+
if (action.payload.isCustomDateRange != null) {
|
|
214
|
+
if (action.payload.isCustomDateRange === true) {
|
|
215
|
+
// Collapse the two raw date strings into a single customTimeframe object.
|
|
216
|
+
// Presence of customTimeframe signals custom-mode active (replaces the
|
|
217
|
+
// old isCustomDateRangeActive boolean + customStartDate/customEndDate).
|
|
218
|
+
draft.uiState.customTimeframe =
|
|
219
|
+
action.payload.customStartDate != null &&
|
|
220
|
+
action.payload.customEndDate != null
|
|
221
|
+
? {
|
|
222
|
+
startDate: date(action.payload.customStartDate),
|
|
223
|
+
endDate: date(action.payload.customEndDate),
|
|
224
|
+
}
|
|
225
|
+
: undefined;
|
|
226
|
+
// Invalidate the shared default-window fetchState so dashboardEpic
|
|
227
|
+
// refetches the default data on the next dashboard visit — the custom
|
|
228
|
+
// fetch writes into the custom slots, and this lets the card self-heal
|
|
229
|
+
// without a reload. The detail screen reads customFetchState.
|
|
230
|
+
draft.fetchState = 'Not-Started';
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
draft.uiState.customTimeframe = undefined;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// A custom date range drives the visible window directly: honor the exact
|
|
237
|
+
// numberOfPeriods + thisPeriod from the payload instead of re-deriving
|
|
238
|
+
// (which clamps the range to the available/non-zero periods).
|
|
239
|
+
if (draft.uiState.customTimeframe != null) {
|
|
240
|
+
draft.uiState.selectedCOABalancesRange =
|
|
241
|
+
action.payload.selectedCOABalancesRange;
|
|
242
|
+
if (action.payload.highlightedCOABalancesRange != null) {
|
|
243
|
+
// Clamp the highlight to the same cap the non-custom path enforces so
|
|
244
|
+
// a wide custom range doesn't highlight all N periods at once.
|
|
245
|
+
draft.uiState.highlightedCOABalancesRange = {
|
|
246
|
+
...action.payload.highlightedCOABalancesRange,
|
|
247
|
+
numberOfPeriods: Math.min(action.payload.highlightedCOABalancesRange.numberOfPeriods, draft.uiState.maxNumOfPeriodsToHighlight),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
139
252
|
const { cashIn, cashOut, dataAvailable } = draft;
|
|
140
253
|
const { timeframe, selectedCOABalancesRange, maxNumOfPeriodsToHighlight, highlightedCOABalancesRange, } = draft.uiState;
|
|
141
254
|
if (dataAvailable != null &&
|
|
@@ -180,6 +293,13 @@ const cashInCashOut = createSlice({
|
|
|
180
293
|
draft.uiState.downloadState = action.payload;
|
|
181
294
|
},
|
|
182
295
|
updateCashInCashOutThisPeriod(draft, action) {
|
|
296
|
+
// In custom-date mode the picker drives the window; skip re-deriving the
|
|
297
|
+
// selected/highlighted ranges from the default dataAvailable (the epic
|
|
298
|
+
// re-dispatches the default thisPeriod on every response, which would
|
|
299
|
+
// otherwise clamp the custom window back to the default).
|
|
300
|
+
if (draft.uiState.customTimeframe != null) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
183
303
|
const { cashIn, cashOut, dataAvailable } = draft;
|
|
184
304
|
const { timeframe, selectedCOABalancesRange, highlightedCOABalancesRange, maxNumOfPeriodsToHighlight, } = draft.uiState;
|
|
185
305
|
if (dataAvailable != null && action.payload.thisPeriod != null) {
|
|
@@ -5,39 +5,53 @@ import { getNumberOfPeriods } from '../../commonStateTypes/dataAvailable';
|
|
|
5
5
|
import { toTimeframeTick, } from '../../commonStateTypes/timeframeTick';
|
|
6
6
|
import { getAccountsOrdered } from '../../entity/account/accountSelector';
|
|
7
7
|
import { getAccountGroupsOrdered } from '../../entity/accountGroup/accountGroupSelector';
|
|
8
|
-
export const getCashInCashOut = (accountState, accountGroupState, cashInCashOutState, filter) => {
|
|
8
|
+
export const getCashInCashOut = (accountState, accountGroupState, cashInCashOutState, filter, readCustomRangeSlot) => {
|
|
9
9
|
const reportId = 'cash_in_cash_out';
|
|
10
|
+
// The detail (in custom mode) reads the isolated custom slots so a default
|
|
11
|
+
// fetch can't overwrite the partial-period data; the dashboard (no flag) reads
|
|
12
|
+
// the default slots, so the two can never clobber each other.
|
|
13
|
+
const useCustomRange = readCustomRangeSlot === true &&
|
|
14
|
+
cashInCashOutState.uiState.customTimeframe != null;
|
|
15
|
+
const cashInSource = useCustomRange
|
|
16
|
+
? cashInCashOutState.customCashIn
|
|
17
|
+
: cashInCashOutState.cashIn;
|
|
18
|
+
const cashOutSource = useCustomRange
|
|
19
|
+
? cashInCashOutState.customCashOut
|
|
20
|
+
: cashInCashOutState.cashOut;
|
|
21
|
+
const netCashFlowSource = useCustomRange
|
|
22
|
+
? cashInCashOutState.customNetCashFlow
|
|
23
|
+
: cashInCashOutState.netCashFlow;
|
|
10
24
|
const cashIn = {
|
|
11
25
|
itemOrder: 'last_balance_descending',
|
|
12
|
-
accountGroupsOrdered: getAccountGroupsOrdered(filter, accountState, accountGroupState,
|
|
13
|
-
accountsOrdered:
|
|
26
|
+
accountGroupsOrdered: getAccountGroupsOrdered(filter, accountState, accountGroupState, cashInSource?.accountGroupIDs ?? [], 'cash_in_cash_out', 'cash_in_cash_out', 'cashIn'),
|
|
27
|
+
accountsOrdered: cashInSource?.accountGroupIDs.length === 0
|
|
14
28
|
? getAccountsOrdered(filter, accountState, {
|
|
15
|
-
accountIDs:
|
|
29
|
+
accountIDs: cashInSource?.accountIDs ?? [],
|
|
16
30
|
reportId,
|
|
17
31
|
}, 'cashIn')
|
|
18
32
|
: [],
|
|
19
|
-
...getCOABalances(
|
|
33
|
+
...getCOABalances(cashInSource, filter),
|
|
20
34
|
};
|
|
21
35
|
const cashOut = {
|
|
22
36
|
itemOrder: 'last_balance_descending',
|
|
23
|
-
accountGroupsOrdered: getAccountGroupsOrdered(filter, accountState, accountGroupState,
|
|
24
|
-
accountsOrdered:
|
|
37
|
+
accountGroupsOrdered: getAccountGroupsOrdered(filter, accountState, accountGroupState, cashOutSource?.accountGroupIDs ?? [], 'cash_in_cash_out', 'cash_in_cash_out', 'cashOut'),
|
|
38
|
+
accountsOrdered: cashOutSource?.accountGroupIDs.length === 0
|
|
25
39
|
? getAccountsOrdered(filter, accountState, {
|
|
26
|
-
accountIDs:
|
|
40
|
+
accountIDs: cashOutSource?.accountIDs ?? [],
|
|
27
41
|
reportId,
|
|
28
42
|
}, 'cashOut')
|
|
29
43
|
: [],
|
|
30
|
-
...getCOABalances(
|
|
44
|
+
...getCOABalances(cashOutSource, filter),
|
|
31
45
|
};
|
|
32
|
-
const netCashFlow = getCOABalances(
|
|
46
|
+
const netCashFlow = getCOABalances(netCashFlowSource, filter);
|
|
33
47
|
let allTimeframeTicks = [];
|
|
34
48
|
const dataAvailable = cashInCashOutState.dataAvailable;
|
|
35
49
|
const firstMonthOfFY = cashInCashOutState.firstMonthOfFY;
|
|
36
50
|
if (dataAvailable != null) {
|
|
37
51
|
const noFilter = newBalancesFilterForDateRange(firstMonthOfFY, cashInCashOutState.uiState.timeframe, dataAvailable, 'ascending_date', []);
|
|
38
52
|
const cashInCashOutBalances = cashIn.balances.length > 0
|
|
39
|
-
? getCOABalances(
|
|
40
|
-
: getCOABalances(
|
|
53
|
+
? getCOABalances(cashInSource, noFilter)
|
|
54
|
+
: getCOABalances(cashOutSource, noFilter);
|
|
41
55
|
allTimeframeTicks = cashInCashOutBalances.balances.map((balance) => toTimeframeTick(balance));
|
|
42
56
|
}
|
|
43
57
|
return {
|
|
@@ -53,10 +67,17 @@ export const getCashInCashOut = (accountState, accountGroupState, cashInCashOutS
|
|
|
53
67
|
reportTitle: cashInCashOutState.reportTitle,
|
|
54
68
|
version: cashInCashOutState.version,
|
|
55
69
|
status: cashInCashOutState.status,
|
|
56
|
-
|
|
70
|
+
// The detail reads the isolated customFetchState so its loading state follows
|
|
71
|
+
// the custom fetch; the dashboard (no flag) reads the shared fetchState, which
|
|
72
|
+
// custom-mode activation reset to Not-Started so dashboardEpic refetches.
|
|
73
|
+
fetchState: useCustomRange && cashInCashOutState.customFetchState != null
|
|
74
|
+
? cashInCashOutState.customFetchState
|
|
75
|
+
: cashInCashOutState.fetchState,
|
|
57
76
|
error: cashInCashOutState.error,
|
|
58
77
|
insights: cashInCashOutState.insights ?? [],
|
|
59
78
|
downloadState: cashInCashOutState.uiState.downloadState,
|
|
79
|
+
// Surface the customTimeframe so the detail picker can restore the selection.
|
|
80
|
+
customTimeframe: cashInCashOutState.uiState.customTimeframe,
|
|
60
81
|
};
|
|
61
82
|
};
|
|
62
83
|
export const getCashInCashOutUIState = createSelector((state) => state.cashInCashOutState, (state) => {
|
|
@@ -82,11 +103,17 @@ export const getCashInCashOutUIState = createSelector((state) => state.cashInCas
|
|
|
82
103
|
export const getCashInCashOutForSelectedRange = createSelector((state) => state.accountState, (state) => state.accountGroupState, (state) => state.cashInCashOutState, (accountState, accountGroupState, cashInCashOutState) => {
|
|
83
104
|
const { timeframe, selectedCOABalancesRange, selectedAdditionalBalances } = cashInCashOutState.uiState;
|
|
84
105
|
const noFilter = newBalancesFilter(cashInCashOutState.firstMonthOfFY, timeframe, selectedCOABalancesRange.numberOfPeriods, selectedCOABalancesRange.thisPeriod, 'ascending_date', selectedAdditionalBalances);
|
|
85
|
-
return getCashInCashOut(accountState, accountGroupState, cashInCashOutState, noFilter
|
|
106
|
+
return getCashInCashOut(accountState, accountGroupState, cashInCashOutState, noFilter,
|
|
107
|
+
// Detail selectors read the custom slot when the detail is in custom mode;
|
|
108
|
+
// the dashboard's getCashInCashOutLast4Periods does not.
|
|
109
|
+
cashInCashOutState.uiState.customTimeframe != null);
|
|
86
110
|
});
|
|
87
111
|
export const getCashInCashOutForHighlightedRange = createSelector((state) => state.accountState, (state) => state.accountGroupState, (state) => state.cashInCashOutState, (accountState, accountGroupState, cashInCashOutState) => {
|
|
88
112
|
const { timeframe, highlightedCOABalancesRange, selectedAdditionalBalances, maxNumOfPeriodsToHighlight, } = cashInCashOutState.uiState;
|
|
89
113
|
const filter = newBalancesFilter(cashInCashOutState.firstMonthOfFY, timeframe, highlightedCOABalancesRange?.numberOfPeriods ??
|
|
90
114
|
maxNumOfPeriodsToHighlight, highlightedCOABalancesRange?.thisPeriod, 'ascending_date', selectedAdditionalBalances);
|
|
91
|
-
return getCashInCashOut(accountState, accountGroupState, cashInCashOutState, filter
|
|
115
|
+
return getCashInCashOut(accountState, accountGroupState, cashInCashOutState, filter,
|
|
116
|
+
// Detail selectors read the custom slot when the detail is in custom mode;
|
|
117
|
+
// the dashboard's getCashInCashOutLast4Periods does not.
|
|
118
|
+
cashInCashOutState.uiState.customTimeframe != null);
|
|
92
119
|
});
|
|
@@ -3,17 +3,25 @@ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
|
3
3
|
import { updateAccounts } from '../../entity/account/accountReducer';
|
|
4
4
|
import { createZeniAPIStatus, isSuccessResponse } from '../../responsePayload';
|
|
5
5
|
import { fetchCashPosition, updateCashPosition, updateCashPositionFailure, updateCashPositionThisPeriod, } from './cashPositionReducer';
|
|
6
|
-
export const fetchCashPositionEpic = (actions$, state, zeniAPI) => actions$.pipe(filter(fetchCashPosition.match), switchMap((action) =>
|
|
7
|
-
|
|
8
|
-
.
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
thisPeriod: state.value.reportUIOptionsState.thisPeriod,
|
|
13
|
-
}));
|
|
6
|
+
export const fetchCashPositionEpic = (actions$, state, zeniAPI) => actions$.pipe(filter(fetchCashPosition.match), switchMap((action) => {
|
|
7
|
+
const queryValue = { is_full_response: action.payload.fetchFullReport };
|
|
8
|
+
const isCustomDateRange = action.payload.startDate != null && action.payload.endDate != null;
|
|
9
|
+
if (isCustomDateRange) {
|
|
10
|
+
queryValue.custom_start_date = action.payload.startDate;
|
|
11
|
+
queryValue.custom_end_date = action.payload.endDate;
|
|
14
12
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
return zeniAPI
|
|
14
|
+
.getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/reports/cash_position?query=${encodeURIComponent(JSON.stringify(queryValue))}`)
|
|
15
|
+
.pipe(mergeMap((response) => {
|
|
16
|
+
if (isSuccessResponse(response)) {
|
|
17
|
+
return of(updateAccounts(response.data?.report.accounts ?? [], 'cash_position'), updateCashPosition(response.data, isCustomDateRange), // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
18
|
+
updateCashPositionThisPeriod({
|
|
19
|
+
thisPeriod: state.value.reportUIOptionsState.thisPeriod,
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
return of(updateCashPositionFailure(response.status, isCustomDateRange));
|
|
24
|
+
}
|
|
25
|
+
}), catchError((error) => of(updateCashPositionFailure(createZeniAPIStatus('Unexpected error', 'Cash Position REST API call errored out' +
|
|
26
|
+
JSON.stringify(error)), isCustomDateRange))));
|
|
27
|
+
}));
|