@zeniai/client-epic-state 5.1.56 → 5.1.57-betaDI1
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/coreEpics.js +3 -2
- package/lib/entity/accountRecon/accountReconPayload.d.ts +1 -1
- package/lib/entity/accountRecon/accountReconReducer.js +15 -1
- package/lib/entity/chargeCard/chargeCard.d.ts +1 -1
- package/lib/entity/notificationRegistry/notificationRegistryPayload.d.ts +18 -0
- package/lib/entity/notificationRegistry/notificationRegistryPayload.js +28 -0
- package/lib/entity/notificationRegistry/notificationRegistryReducer.d.ts +6 -0
- package/lib/entity/notificationRegistry/notificationRegistryReducer.js +26 -0
- package/lib/entity/notificationRegistry/notificationRegistrySelector.d.ts +2 -0
- package/lib/entity/notificationRegistry/notificationRegistrySelector.js +10 -0
- package/lib/entity/notificationRegistry/notificationRegistryState.d.ts +31 -0
- package/lib/entity/notificationRegistry/notificationRegistryState.js +10 -0
- package/lib/entity/tenant/clearAllEpic.d.ts +3 -1
- package/lib/entity/tenant/clearAllEpic.js +4 -0
- package/lib/epic.d.ts +2 -1
- package/lib/epic.js +2 -1
- package/lib/esm/coreEpics.js +3 -2
- package/lib/esm/entity/accountRecon/accountReconReducer.js +15 -1
- package/lib/esm/entity/notificationRegistry/notificationRegistryPayload.js +24 -0
- package/lib/esm/entity/notificationRegistry/notificationRegistryReducer.js +22 -0
- package/lib/esm/entity/notificationRegistry/notificationRegistrySelector.js +6 -0
- package/lib/esm/entity/notificationRegistry/notificationRegistryState.js +5 -0
- package/lib/esm/entity/tenant/clearAllEpic.js +4 -0
- package/lib/esm/epic.js +2 -1
- package/lib/esm/index.js +6 -2
- package/lib/esm/reducer.js +6 -0
- package/lib/esm/view/expenseAutomationView/selectors/statementParseViewSelector.js +0 -224
- package/lib/esm/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +103 -0
- package/lib/esm/view/notificationPreferencesView/notificationPreferencesEndpoint.js +5 -0
- package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewPayload.js +36 -0
- package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewReducer.js +222 -0
- package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewSelector.js +25 -0
- package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewState.js +4 -0
- package/lib/esm/view/settingsView/epic/fetchNotificationSettingsEpic.js +21 -2
- package/lib/esm/view/settingsView/epic/saveNotificationSettingsEpic.js +2 -1
- package/lib/esm/view/settingsView/settingsViewHelpers.js +3 -2
- package/lib/index.d.ts +9 -4
- package/lib/index.js +53 -42
- package/lib/reducer.d.ts +6 -0
- package/lib/reducer.js +6 -0
- package/lib/view/common/recurringViewHelper.d.ts +2 -2
- package/lib/view/expenseAutomationView/selectors/statementParseViewSelector.d.ts +1 -15
- package/lib/view/expenseAutomationView/selectors/statementParseViewSelector.js +1 -233
- package/lib/view/expenseAutomationView/types/reconciliationViewState.d.ts +7 -44
- package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.d.ts +34 -0
- package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +107 -0
- package/lib/view/notificationPreferencesView/notificationPreferencesEndpoint.d.ts +2 -0
- package/lib/view/notificationPreferencesView/notificationPreferencesEndpoint.js +9 -0
- package/lib/view/notificationPreferencesView/notificationPreferencesViewPayload.d.ts +7 -0
- package/lib/view/notificationPreferencesView/notificationPreferencesViewPayload.js +41 -0
- package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.d.ts +30 -0
- package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.js +226 -0
- package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.d.ts +7 -0
- package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.js +32 -0
- package/lib/view/notificationPreferencesView/notificationPreferencesViewState.d.ts +14 -0
- package/lib/view/notificationPreferencesView/notificationPreferencesViewState.js +8 -0
- package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.d.ts +9 -1
- package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.js +21 -2
- package/lib/view/settingsView/epic/saveNotificationSettingsEpic.js +2 -1
- package/lib/view/settingsView/settingsViewHelpers.d.ts +1 -1
- package/lib/view/settingsView/settingsViewHelpers.js +3 -2
- package/lib/view/settingsView/settingsViewPayload.d.ts +4 -0
- package/lib/view/settingsView/settingsViewState.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.mergeStatementParseDeletedTransactionIds = exports.getDateFilteredDeletedTransactionIds = exports.getStatementTransactionPeriodById = exports.getParsedStatementDataForView = exports.filterStatementTransactionsByPeriod = exports.isStatementTransactionInPeriod = exports.getEffectiveStatementPeriod = void 0;
|
|
4
4
|
const zeniDayJS_1 = require("../../../zeniDayJS");
|
|
5
5
|
const getEffectiveStatementPeriod = (parsedMeta, localMeta) => ({
|
|
6
6
|
statementStartDate: localMeta?.statementStartDate != null && localMeta.statementStartDate !== ''
|
|
@@ -73,122 +73,6 @@ const getDateFilteredDeletedTransactionIds = (allParsedTransactions, statementPe
|
|
|
73
73
|
.map((transaction) => transaction.statementTransactionId);
|
|
74
74
|
};
|
|
75
75
|
exports.getDateFilteredDeletedTransactionIds = getDateFilteredDeletedTransactionIds;
|
|
76
|
-
const roundCurrency = (value) => Math.round(value * 100) / 100;
|
|
77
|
-
const toApiDateString = (value) => {
|
|
78
|
-
if (value == null || value === '') {
|
|
79
|
-
return '';
|
|
80
|
-
}
|
|
81
|
-
return (0, zeniDayJS_1.date)(value).format('YYYY-MM-DD');
|
|
82
|
-
};
|
|
83
|
-
const computeStatementTransactionTotals = (transactions) => {
|
|
84
|
-
const totalDeposits = roundCurrency(transactions
|
|
85
|
-
.filter((txn) => txn.transactionDirection === 'credit')
|
|
86
|
-
.reduce((sum, txn) => sum + Math.abs(txn.amount ?? 0), 0));
|
|
87
|
-
const totalPayments = roundCurrency(transactions
|
|
88
|
-
.filter((txn) => txn.transactionDirection === 'debit')
|
|
89
|
-
.reduce((sum, txn) => sum + Math.abs(txn.amount ?? 0), 0));
|
|
90
|
-
return { totalDeposits, totalPayments };
|
|
91
|
-
};
|
|
92
|
-
const computeStatementClosingBalance = (openingBalance, totalDeposits, totalPayments, accountType) => roundCurrency(accountType === 'credit_card'
|
|
93
|
-
? openingBalance + totalPayments - totalDeposits
|
|
94
|
-
: openingBalance + totalDeposits - totalPayments);
|
|
95
|
-
const filterFormTransactionsInPeriod = (transactions, statementStartDate, statementEndDate) => {
|
|
96
|
-
if (statementStartDate === '' || statementEndDate === '') {
|
|
97
|
-
return transactions;
|
|
98
|
-
}
|
|
99
|
-
const periodStart = (0, zeniDayJS_1.date)(statementStartDate).startOf('day');
|
|
100
|
-
const periodEnd = (0, zeniDayJS_1.date)(statementEndDate).startOf('day');
|
|
101
|
-
return transactions.filter((txn) => {
|
|
102
|
-
if (txn.transactionDate === '') {
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
const txnDate = (0, zeniDayJS_1.date)(txn.transactionDate).startOf('day');
|
|
106
|
-
return (txnDate.isSameOrAfter(periodStart, 'day') &&
|
|
107
|
-
txnDate.isSameOrBefore(periodEnd, 'day'));
|
|
108
|
-
});
|
|
109
|
-
};
|
|
110
|
-
const isStatementParseTransactionFieldChange = (changedField) => changedField === 'transactions' ||
|
|
111
|
-
changedField?.startsWith('transactions.') === true;
|
|
112
|
-
exports.isStatementParseTransactionFieldChange = isStatementParseTransactionFieldChange;
|
|
113
|
-
const isStatementParseBalanceAffectingTransactionFieldChange = (changedField) => {
|
|
114
|
-
if (changedField === 'transactions') {
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
if (changedField?.startsWith('transactions.') !== true) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
return !changedField.endsWith('.transactionMemo');
|
|
121
|
-
};
|
|
122
|
-
const isStatementParseManualBalanceField = (field) => field === 'totalDeposits' ||
|
|
123
|
-
field === 'totalPayments' ||
|
|
124
|
-
field === 'closingBalance';
|
|
125
|
-
exports.isStatementParseManualBalanceField = isStatementParseManualBalanceField;
|
|
126
|
-
const shouldRecalculateStatementParseBalances = (changedField) => isStatementParseBalanceAffectingTransactionFieldChange(changedField) ||
|
|
127
|
-
changedField === 'statementStartDate' ||
|
|
128
|
-
changedField === 'statementEndDate';
|
|
129
|
-
exports.shouldRecalculateStatementParseBalances = shouldRecalculateStatementParseBalances;
|
|
130
|
-
const resolveStatementParseBalanceFields = (formView, changedField, accountType) => {
|
|
131
|
-
const inPeriodTransactions = filterFormTransactionsInPeriod(formView.transactions, formView.statementStartDate, formView.statementEndDate);
|
|
132
|
-
const { totalDeposits, totalPayments } = computeStatementTransactionTotals(inPeriodTransactions);
|
|
133
|
-
if (!(0, exports.shouldRecalculateStatementParseBalances)(changedField)) {
|
|
134
|
-
return {
|
|
135
|
-
openingBalance: formView.openingBalance,
|
|
136
|
-
closingBalance: formView.closingBalance,
|
|
137
|
-
totalDeposits: formView.totalDeposits,
|
|
138
|
-
totalPayments: formView.totalPayments,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
return {
|
|
142
|
-
openingBalance: formView.openingBalance,
|
|
143
|
-
totalDeposits,
|
|
144
|
-
totalPayments,
|
|
145
|
-
closingBalance: computeStatementClosingBalance(formView.openingBalance, totalDeposits, totalPayments, accountType),
|
|
146
|
-
};
|
|
147
|
-
};
|
|
148
|
-
exports.resolveStatementParseBalanceFields = resolveStatementParseBalanceFields;
|
|
149
|
-
const areStatementParseBalancesConsistent = (formView, accountType) => {
|
|
150
|
-
// Validate the four balance fields against each other (what the user sees),
|
|
151
|
-
// not against recomputed transaction totals. Manual deposits/payments edits
|
|
152
|
-
// are allowed as long as closing matches the account-type formula.
|
|
153
|
-
return (roundCurrency(formView.closingBalance) ===
|
|
154
|
-
computeStatementClosingBalance(formView.openingBalance, formView.totalDeposits, formView.totalPayments, accountType));
|
|
155
|
-
};
|
|
156
|
-
exports.areStatementParseBalancesConsistent = areStatementParseBalancesConsistent;
|
|
157
|
-
const mapParsedTransactionToFormRow = (transaction, index) => ({
|
|
158
|
-
id: transaction.statementTransactionId || `parsed-${index}`,
|
|
159
|
-
transactionDate: toApiDateString(transaction.transactionDate),
|
|
160
|
-
transactionMemo: transaction.transactionMemo,
|
|
161
|
-
amount: transaction.amount,
|
|
162
|
-
transactionDirection: transaction.transactionDirection,
|
|
163
|
-
statementTransactionId: transaction.statementTransactionId,
|
|
164
|
-
});
|
|
165
|
-
const mapLocalTransactionToFormRow = (transaction, id) => ({
|
|
166
|
-
id,
|
|
167
|
-
transactionDate: toApiDateString(transaction.transactionDate),
|
|
168
|
-
transactionMemo: transaction.transactionMemo,
|
|
169
|
-
amount: transaction.amount,
|
|
170
|
-
transactionDirection: transaction.transactionDirection,
|
|
171
|
-
statementTransactionId: transaction.statementTransactionId,
|
|
172
|
-
});
|
|
173
|
-
const buildStatementParseFormTransactions = (originalTransactions, localData) => {
|
|
174
|
-
const deletedIdSet = new Set(localData.statementTransactions?.deletedIds ?? []);
|
|
175
|
-
const updatedById = new Map((localData.statementTransactions?.updated ?? [])
|
|
176
|
-
.filter((transaction) => transaction.statementTransactionId != null)
|
|
177
|
-
.map((transaction) => [
|
|
178
|
-
transaction.statementTransactionId,
|
|
179
|
-
transaction,
|
|
180
|
-
]));
|
|
181
|
-
const persistedTransactions = originalTransactions
|
|
182
|
-
.filter((transaction) => !deletedIdSet.has(transaction.statementTransactionId))
|
|
183
|
-
.map((transaction, index) => {
|
|
184
|
-
const updated = updatedById.get(transaction.statementTransactionId);
|
|
185
|
-
return updated != null
|
|
186
|
-
? mapLocalTransactionToFormRow(updated, transaction.statementTransactionId)
|
|
187
|
-
: mapParsedTransactionToFormRow(transaction, index);
|
|
188
|
-
});
|
|
189
|
-
const addedTransactions = (localData.statementTransactions?.added ?? []).map((transaction, index) => mapLocalTransactionToFormRow(transaction, `added-${index}`));
|
|
190
|
-
return [...persistedTransactions, ...addedTransactions];
|
|
191
|
-
};
|
|
192
76
|
const mergeStatementParseDeletedTransactionIds = (originalTransactions, statementPeriod, currentTransactionIds) => {
|
|
193
77
|
const dateFilteredDeletedIds = (0, exports.getDateFilteredDeletedTransactionIds)(originalTransactions, statementPeriod);
|
|
194
78
|
const dateFilteredDeletedIdSet = new Set(dateFilteredDeletedIds);
|
|
@@ -200,119 +84,3 @@ const mergeStatementParseDeletedTransactionIds = (originalTransactions, statemen
|
|
|
200
84
|
return [...new Set([...userDeletedIds, ...dateFilteredDeletedIds])];
|
|
201
85
|
};
|
|
202
86
|
exports.mergeStatementParseDeletedTransactionIds = mergeStatementParseDeletedTransactionIds;
|
|
203
|
-
const resolveStatementMetaDates = (parsedStatementMeta, localMeta) => ({
|
|
204
|
-
statementStartDate: localMeta?.statementStartDate != null && localMeta.statementStartDate !== ''
|
|
205
|
-
? localMeta.statementStartDate
|
|
206
|
-
: (parsedStatementMeta?.statementStartDate ?? ''),
|
|
207
|
-
statementEndDate: localMeta?.statementEndDate != null && localMeta.statementEndDate !== ''
|
|
208
|
-
? localMeta.statementEndDate
|
|
209
|
-
: (parsedStatementMeta?.statementEndDate ?? ''),
|
|
210
|
-
});
|
|
211
|
-
const getStatementParseFormView = ({ accountType, parsedStatementMeta, originalTransactions, statementUpdateLocalData, }) => {
|
|
212
|
-
const transactions = statementUpdateLocalData != null
|
|
213
|
-
? buildStatementParseFormTransactions(originalTransactions, statementUpdateLocalData)
|
|
214
|
-
: originalTransactions.map(mapParsedTransactionToFormRow);
|
|
215
|
-
const localMeta = statementUpdateLocalData?.statementMeta;
|
|
216
|
-
const { statementStartDate, statementEndDate } = resolveStatementMetaDates(parsedStatementMeta, localMeta);
|
|
217
|
-
const { totalDeposits, totalPayments } = computeStatementTransactionTotals(filterFormTransactionsInPeriod(transactions, statementStartDate, statementEndDate));
|
|
218
|
-
const openingBalance = localMeta?.openingBalance ?? parsedStatementMeta?.openingBalance ?? 0;
|
|
219
|
-
const computedClosingBalance = computeStatementClosingBalance(openingBalance, totalDeposits, totalPayments, accountType);
|
|
220
|
-
return {
|
|
221
|
-
statementStartDate,
|
|
222
|
-
statementEndDate,
|
|
223
|
-
openingBalance,
|
|
224
|
-
closingBalance: localMeta?.closingBalance ?? computedClosingBalance,
|
|
225
|
-
totalDeposits: localMeta?.totalDeposits ?? totalDeposits,
|
|
226
|
-
totalPayments: localMeta?.totalPayments ?? totalPayments,
|
|
227
|
-
transactions: filterFormTransactionsInPeriod(transactions, statementStartDate, statementEndDate),
|
|
228
|
-
};
|
|
229
|
-
};
|
|
230
|
-
exports.getStatementParseFormView = getStatementParseFormView;
|
|
231
|
-
const toStatementUpdateLocalDataFromFormView = ({ accountType, formView, parsedStatementMeta, originalTransactions, changedField, }) => {
|
|
232
|
-
const balanceFields = (0, exports.resolveStatementParseBalanceFields)(formView, changedField, accountType);
|
|
233
|
-
const currentTransactionIds = new Set(formView.transactions
|
|
234
|
-
.map((transaction) => transaction.statementTransactionId)
|
|
235
|
-
.filter((id) => id != null && id !== ''));
|
|
236
|
-
const added = [];
|
|
237
|
-
const updated = [];
|
|
238
|
-
formView.transactions.forEach((transaction) => {
|
|
239
|
-
const transactionDate = toApiDateString(transaction.transactionDate);
|
|
240
|
-
if (transaction.statementTransactionId == null) {
|
|
241
|
-
if (transaction.transactionDirection != null) {
|
|
242
|
-
added.push({
|
|
243
|
-
amount: transaction.amount,
|
|
244
|
-
transactionDate,
|
|
245
|
-
transactionDirection: transaction.transactionDirection,
|
|
246
|
-
transactionMemo: transaction.transactionMemo,
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
const original = originalTransactions.find((candidate) => candidate.statementTransactionId === transaction.statementTransactionId);
|
|
252
|
-
if (original != null &&
|
|
253
|
-
(original.amount !== transaction.amount ||
|
|
254
|
-
toApiDateString(original.transactionDate) !== transactionDate ||
|
|
255
|
-
original.transactionMemo !== transaction.transactionMemo)) {
|
|
256
|
-
updated.push({
|
|
257
|
-
amount: transaction.amount,
|
|
258
|
-
transactionDate,
|
|
259
|
-
transactionDirection: transaction.transactionDirection,
|
|
260
|
-
transactionMemo: transaction.transactionMemo,
|
|
261
|
-
statementTransactionId: transaction.statementTransactionId,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
const statementPeriod = {
|
|
266
|
-
statementStartDate: toApiDateString(formView.statementStartDate),
|
|
267
|
-
statementEndDate: toApiDateString(formView.statementEndDate),
|
|
268
|
-
};
|
|
269
|
-
const deletedIds = (0, exports.mergeStatementParseDeletedTransactionIds)(originalTransactions, statementPeriod, currentTransactionIds);
|
|
270
|
-
const deletedIdSet = new Set(deletedIds);
|
|
271
|
-
const inPeriodAdded = filterFormTransactionsInPeriod(added, statementPeriod.statementStartDate, statementPeriod.statementEndDate);
|
|
272
|
-
const inPeriodUpdated = updated.filter((transaction) => transaction.statementTransactionId != null &&
|
|
273
|
-
!deletedIdSet.has(transaction.statementTransactionId));
|
|
274
|
-
return {
|
|
275
|
-
statementMeta: {
|
|
276
|
-
closingBalance: balanceFields.closingBalance,
|
|
277
|
-
openingBalance: balanceFields.openingBalance,
|
|
278
|
-
statementDataStatus: parsedStatementMeta?.statementDataStatus ?? {
|
|
279
|
-
code: '',
|
|
280
|
-
label: '',
|
|
281
|
-
},
|
|
282
|
-
statementEndDate: statementPeriod.statementEndDate,
|
|
283
|
-
statementStartDate: statementPeriod.statementStartDate,
|
|
284
|
-
statementStatus: parsedStatementMeta?.statementStatus ?? {
|
|
285
|
-
code: '',
|
|
286
|
-
label: '',
|
|
287
|
-
},
|
|
288
|
-
statementUploadId: parsedStatementMeta?.statementUploadId ?? '',
|
|
289
|
-
totalDeposits: balanceFields.totalDeposits,
|
|
290
|
-
totalPayments: balanceFields.totalPayments,
|
|
291
|
-
},
|
|
292
|
-
statementTransactions: {
|
|
293
|
-
added: inPeriodAdded,
|
|
294
|
-
deletedIds,
|
|
295
|
-
updated: inPeriodUpdated,
|
|
296
|
-
},
|
|
297
|
-
};
|
|
298
|
-
};
|
|
299
|
-
exports.toStatementUpdateLocalDataFromFormView = toStatementUpdateLocalDataFromFormView;
|
|
300
|
-
const applyStatementParseFormChange = ({ accountType, formView, changedField, parsedStatementMeta, originalTransactions, }) => {
|
|
301
|
-
const balanceFields = (0, exports.resolveStatementParseBalanceFields)(formView, changedField, accountType);
|
|
302
|
-
const shouldRecalculate = (0, exports.shouldRecalculateStatementParseBalances)(changedField);
|
|
303
|
-
const mergedFormView = shouldRecalculate
|
|
304
|
-
? { ...formView, ...balanceFields }
|
|
305
|
-
: formView;
|
|
306
|
-
return {
|
|
307
|
-
didRecalculateBalances: shouldRecalculate,
|
|
308
|
-
formView: mergedFormView,
|
|
309
|
-
localData: (0, exports.toStatementUpdateLocalDataFromFormView)({
|
|
310
|
-
accountType,
|
|
311
|
-
formView: mergedFormView,
|
|
312
|
-
parsedStatementMeta,
|
|
313
|
-
originalTransactions,
|
|
314
|
-
changedField,
|
|
315
|
-
}),
|
|
316
|
-
};
|
|
317
|
-
};
|
|
318
|
-
exports.applyStatementParseFormChange = applyStatementParseFormChange;
|
|
@@ -2,7 +2,7 @@ import { Amount } from '../../../commonStateTypes/amount';
|
|
|
2
2
|
import { FetchStateAndError, ID } from '../../../commonStateTypes/common';
|
|
3
3
|
import { SortOrder } from '../../../commonStateTypes/selectorTypes/sortOrderTypes';
|
|
4
4
|
import { MonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
|
|
5
|
-
import {
|
|
5
|
+
import { ReconciliationAccountSourceType } from '../../../entity/account/accountState';
|
|
6
6
|
import { BankStatusCodeType } from '../../../entity/accountRecon/accountReconState';
|
|
7
7
|
import { CustomerBase } from '../../../entity/customer/customerState';
|
|
8
8
|
import { VendorBase } from '../../../entity/vendor/vendorState';
|
|
@@ -251,6 +251,12 @@ export interface StatementTransactionForUpdate {
|
|
|
251
251
|
transactionDate: string;
|
|
252
252
|
transactionDirection: 'debit' | 'credit';
|
|
253
253
|
transactionMemo: string;
|
|
254
|
+
/**
|
|
255
|
+
* Client-only stable id for unsaved added rows. Preserved across local-data
|
|
256
|
+
* rebuilds so form rows survive add/delete without index remapping. Not sent
|
|
257
|
+
* to the API.
|
|
258
|
+
*/
|
|
259
|
+
id?: string;
|
|
254
260
|
statementTransactionId?: string;
|
|
255
261
|
}
|
|
256
262
|
export interface StatementTransactionsUpdate {
|
|
@@ -258,49 +264,6 @@ export interface StatementTransactionsUpdate {
|
|
|
258
264
|
deletedIds: string[];
|
|
259
265
|
updated: StatementTransactionForUpdate[];
|
|
260
266
|
}
|
|
261
|
-
export type StatementParseManualBalanceField = 'totalDeposits' | 'totalPayments' | 'closingBalance';
|
|
262
|
-
export interface StatementParseFormTransaction {
|
|
263
|
-
amount: number;
|
|
264
|
-
id: string;
|
|
265
|
-
transactionDate: string;
|
|
266
|
-
transactionDirection: 'debit' | 'credit';
|
|
267
|
-
transactionMemo: string;
|
|
268
|
-
statementTransactionId?: string;
|
|
269
|
-
}
|
|
270
|
-
export interface StatementParseFormView {
|
|
271
|
-
closingBalance: number;
|
|
272
|
-
openingBalance: number;
|
|
273
|
-
statementEndDate: string;
|
|
274
|
-
statementStartDate: string;
|
|
275
|
-
totalDeposits: number;
|
|
276
|
-
totalPayments: number;
|
|
277
|
-
transactions: StatementParseFormTransaction[];
|
|
278
|
-
}
|
|
279
|
-
export interface GetStatementParseFormViewInput {
|
|
280
|
-
originalTransactions: StatementTransaction[];
|
|
281
|
-
parsedStatementMeta: StatementMeta | undefined;
|
|
282
|
-
statementUpdateLocalData: StatementUpdateLocalData | undefined;
|
|
283
|
-
accountType?: AccountType;
|
|
284
|
-
}
|
|
285
|
-
export interface ToStatementUpdateLocalDataFromFormViewInput {
|
|
286
|
-
formView: StatementParseFormView;
|
|
287
|
-
originalTransactions: StatementTransaction[];
|
|
288
|
-
parsedStatementMeta: StatementMeta | undefined;
|
|
289
|
-
accountType?: AccountType;
|
|
290
|
-
changedField?: string;
|
|
291
|
-
}
|
|
292
|
-
export interface ApplyStatementParseFormChangeInput {
|
|
293
|
-
formView: StatementParseFormView;
|
|
294
|
-
originalTransactions: StatementTransaction[];
|
|
295
|
-
parsedStatementMeta: StatementMeta | undefined;
|
|
296
|
-
accountType?: AccountType;
|
|
297
|
-
changedField?: string;
|
|
298
|
-
}
|
|
299
|
-
export interface ApplyStatementParseFormChangeResult {
|
|
300
|
-
didRecalculateBalances: boolean;
|
|
301
|
-
formView: StatementParseFormView;
|
|
302
|
-
localData: StatementUpdateLocalData;
|
|
303
|
-
}
|
|
304
267
|
export interface StatementUpdateLocalData {
|
|
305
268
|
statementMeta: StatementMeta;
|
|
306
269
|
statementTransactions: StatementTransactionsUpdate;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ActionsObservable, StateObservable } from 'redux-observable';
|
|
2
|
+
import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
3
|
+
import { RootState } from '../../../reducer';
|
|
4
|
+
import { ZeniAPI } from '../../../zeniAPI';
|
|
5
|
+
import { saveNotificationPreferences, saveNotificationPreferencesFailure, saveNotificationPreferencesSuccess, setGroupFrequency, toggleEventChannel } from '../notificationPreferencesViewReducer';
|
|
6
|
+
export type ActionType = ReturnType<typeof saveNotificationPreferences> | ReturnType<typeof saveNotificationPreferencesFailure> | ReturnType<typeof saveNotificationPreferencesSuccess> | ReturnType<typeof setGroupFrequency> | ReturnType<typeof toggleEventChannel> | ReturnType<typeof openSnackbar>;
|
|
7
|
+
export declare const saveNotificationPreferencesEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => import("rxjs").Observable<{
|
|
8
|
+
payload: {
|
|
9
|
+
messageSection: import("../../..").SnackbarMessageSections;
|
|
10
|
+
messageText: import("../../..").SnackbarMessageSectionTexts;
|
|
11
|
+
type: import("../../..").SnackbarMessageType;
|
|
12
|
+
showStatusIcon?: boolean;
|
|
13
|
+
variables?: {
|
|
14
|
+
variableName: string;
|
|
15
|
+
variableValue: string;
|
|
16
|
+
}[];
|
|
17
|
+
};
|
|
18
|
+
type: "snackbar/openSnackbar";
|
|
19
|
+
} | {
|
|
20
|
+
payload: undefined;
|
|
21
|
+
type: "notificationPreferencesView/saveNotificationPreferences";
|
|
22
|
+
} | {
|
|
23
|
+
payload: import("../notificationPreferencesViewReducer").SaveNotificationPreferencesFailurePayload;
|
|
24
|
+
type: "notificationPreferencesView/saveNotificationPreferencesFailure";
|
|
25
|
+
} | {
|
|
26
|
+
payload: import("../notificationPreferencesViewReducer").SaveNotificationPreferencesSuccessPayload;
|
|
27
|
+
type: "notificationPreferencesView/saveNotificationPreferencesSuccess";
|
|
28
|
+
} | {
|
|
29
|
+
payload: import("../notificationPreferencesViewReducer").SetFrequencyPayload;
|
|
30
|
+
type: "notificationPreferencesView/setGroupFrequency";
|
|
31
|
+
} | {
|
|
32
|
+
payload: import("../notificationPreferencesViewReducer").TogglePreferencePayload;
|
|
33
|
+
type: "notificationPreferencesView/toggleEventChannel";
|
|
34
|
+
}>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.saveNotificationPreferencesEpic = void 0;
|
|
4
|
+
const rxjs_1 = require("rxjs");
|
|
5
|
+
const operators_1 = require("rxjs/operators");
|
|
6
|
+
const snackbarReducer_1 = require("../../../entity/snackbar/snackbarReducer");
|
|
7
|
+
const responsePayload_1 = require("../../../responsePayload");
|
|
8
|
+
const notificationPreferencesEndpoint_1 = require("../notificationPreferencesEndpoint");
|
|
9
|
+
const notificationPreferencesViewPayload_1 = require("../notificationPreferencesViewPayload");
|
|
10
|
+
const notificationPreferencesViewReducer_1 = require("../notificationPreferencesViewReducer");
|
|
11
|
+
const notificationPreferencesViewSelector_1 = require("../notificationPreferencesViewSelector");
|
|
12
|
+
const DEBOUNCE_MS = 300;
|
|
13
|
+
const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)((action) => notificationPreferencesViewReducer_1.toggleEventChannel.match(action) || notificationPreferencesViewReducer_1.setGroupFrequency.match(action)),
|
|
14
|
+
// Coalesce bursts of toggles into a single PUT. Trade-off: rapid clickers
|
|
15
|
+
// defer saves until they pause. Consider a max-wait wrapper if this
|
|
16
|
+
// becomes a UX problem.
|
|
17
|
+
(0, operators_1.debounceTime)(DEBOUNCE_MS), (0, operators_1.withLatestFrom)(state$),
|
|
18
|
+
// `switchMap` unsubscribes from any in-flight PUT observable when a new
|
|
19
|
+
// debounced batch fires. The prior HTTP request has already left the
|
|
20
|
+
// client — the network call is NOT cancelled — but its response is
|
|
21
|
+
// ignored, so stale success/failure actions cannot clobber the newer
|
|
22
|
+
// batch's state. Server-side ordering is arrival-order LWW.
|
|
23
|
+
(0, operators_1.switchMap)(([, state]) => {
|
|
24
|
+
const overrides = (0, notificationPreferencesViewSelector_1.getNotificationLocalOverrides)(state);
|
|
25
|
+
const preferencesPatch = (0, notificationPreferencesViewPayload_1.mapPreferencesToPayload)(overrides);
|
|
26
|
+
if (Object.keys(preferencesPatch).length === 0) {
|
|
27
|
+
// No diff → do not dispatch a synthetic success (that would wipe
|
|
28
|
+
// stored preferences via the reducer). Simply no-op.
|
|
29
|
+
return rxjs_1.EMPTY;
|
|
30
|
+
}
|
|
31
|
+
// Snapshot the Cancel epoch at PUT-dispatch time. Every Cancel bumps
|
|
32
|
+
// `cancelEpoch`; the reducer compares the captured value on Success/
|
|
33
|
+
// Failure and skips the merge when the value has advanced (user
|
|
34
|
+
// pressed Cancel since this save started). See Cursor Bugbot 4aa021f7
|
|
35
|
+
// — the earlier counter model failed when `switchMap` abandoned an
|
|
36
|
+
// in-flight save's response (Success/Failure never dispatched, so
|
|
37
|
+
// the counter never drained and future saves got their merge dropped).
|
|
38
|
+
const cancelEpochAtDispatch = state.notificationPreferencesViewState.cancelEpoch;
|
|
39
|
+
const body = { task_checklist_preferences: preferencesPatch };
|
|
40
|
+
// Suppress the error snackbar when the response lands AFTER the user
|
|
41
|
+
// pressed Cancel — the Failure reducer already swallows the state
|
|
42
|
+
// update, so the toast would be a stale error for a save the user
|
|
43
|
+
// abandoned. `StateObservable#value` reads the current cancelEpoch at
|
|
44
|
+
// response time (not at dispatch time — that's what
|
|
45
|
+
// `cancelEpochAtDispatch` captures). Cursor Bugbot da2f4fe2 + Nikunj.
|
|
46
|
+
const errorSnackbar = (0, snackbarReducer_1.openSnackbar)({
|
|
47
|
+
messageSection: 'notification_settings_saved',
|
|
48
|
+
messageText: 'failed',
|
|
49
|
+
type: 'error',
|
|
50
|
+
});
|
|
51
|
+
const isCancelledSince = () => cancelEpochAtDispatch <
|
|
52
|
+
state$.value.notificationPreferencesViewState.cancelEpoch;
|
|
53
|
+
// Abort the in-flight HTTP request when `switchMap` disposes this
|
|
54
|
+
// observable (new debounced batch fires, tenant switch, unmount).
|
|
55
|
+
// Without an AbortSignal, `switchMap` would drop the response
|
|
56
|
+
// client-side but the older PUT would still land on the server —
|
|
57
|
+
// under patch-semantic LWW, a slower older PUT arriving after a
|
|
58
|
+
// newer one silently reverts the user's latest choice on the
|
|
59
|
+
// server. Cursor Bugbot e3861a76. `AbortController.abort()` is
|
|
60
|
+
// idempotent so it's safe if the request already completed.
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
const request$ = zeniAPI
|
|
63
|
+
.putAndGetJSON((0, notificationPreferencesEndpoint_1.notificationPreferencesUrl)(zeniAPI), body, undefined, controller.signal)
|
|
64
|
+
.pipe((0, operators_1.mergeMap)((response) => {
|
|
65
|
+
if ((0, responsePayload_1.isSuccessResponse)(response)) {
|
|
66
|
+
return (0, rxjs_1.of)((0, notificationPreferencesViewReducer_1.saveNotificationPreferencesSuccess)({
|
|
67
|
+
cancelEpochAtDispatch,
|
|
68
|
+
savedOverrides: overrides,
|
|
69
|
+
serverEcho: response.data?.task_checklist_preferences,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
const actions = [
|
|
73
|
+
(0, notificationPreferencesViewReducer_1.saveNotificationPreferencesFailure)({
|
|
74
|
+
cancelEpochAtDispatch,
|
|
75
|
+
error: response.status,
|
|
76
|
+
}),
|
|
77
|
+
];
|
|
78
|
+
if (!isCancelledSince()) {
|
|
79
|
+
// Surface a user-visible error — without it the UI reverts to
|
|
80
|
+
// server truth after debounce and the failed toggle looks like
|
|
81
|
+
// a mystery UI bug.
|
|
82
|
+
actions.push(errorSnackbar);
|
|
83
|
+
}
|
|
84
|
+
return (0, rxjs_1.of)(...actions);
|
|
85
|
+
}), (0, operators_1.catchError)((error) => {
|
|
86
|
+
const actions = [
|
|
87
|
+
(0, notificationPreferencesViewReducer_1.saveNotificationPreferencesFailure)({
|
|
88
|
+
cancelEpochAtDispatch,
|
|
89
|
+
error: (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Save notification preferences failed: ' +
|
|
90
|
+
JSON.stringify(error)),
|
|
91
|
+
}),
|
|
92
|
+
];
|
|
93
|
+
if (!isCancelledSince()) {
|
|
94
|
+
actions.push(errorSnackbar);
|
|
95
|
+
}
|
|
96
|
+
return (0, rxjs_1.of)(...actions);
|
|
97
|
+
}), (0, operators_1.finalize)(() => {
|
|
98
|
+
// switchMap disposal (new batch fires, cancel, unmount) → abort
|
|
99
|
+
// the still-in-flight HTTP request. No-op if the request has
|
|
100
|
+
// already completed successfully or errored.
|
|
101
|
+
controller.abort();
|
|
102
|
+
}));
|
|
103
|
+
// Flip savePreferencesState to In-Progress BEFORE the network call so
|
|
104
|
+
// any "saving…" UI can render.
|
|
105
|
+
return (0, rxjs_1.concat)((0, rxjs_1.of)((0, notificationPreferencesViewReducer_1.saveNotificationPreferences)()), request$);
|
|
106
|
+
}));
|
|
107
|
+
exports.saveNotificationPreferencesEpic = saveNotificationPreferencesEpic;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.notificationPreferencesUrl = void 0;
|
|
4
|
+
// Shared endpoint used by both the GET (envelope refresh in
|
|
5
|
+
// `fetchNotificationSettingsEpic`) and the PUT (patch in
|
|
6
|
+
// `saveNotificationPreferencesEpic`). Kept as a helper so an endpoint rename
|
|
7
|
+
// can't drift between the two callers.
|
|
8
|
+
const notificationPreferencesUrl = (zeniAPI) => `${zeniAPI.apiEndPoints.notificationMicroServiceBaseUrl}/1.0/notification-preferences`;
|
|
9
|
+
exports.notificationPreferencesUrl = notificationPreferencesUrl;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { NotificationPreferences } from './notificationPreferencesViewState';
|
|
2
|
+
export interface NotificationPreferencesPayload {
|
|
3
|
+
event_enabled_by_channel?: Record<string, Record<string, boolean>>;
|
|
4
|
+
group_frequency_by_group_id?: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export declare const mapPayloadToPreferences: (payload?: NotificationPreferencesPayload) => NotificationPreferences;
|
|
7
|
+
export declare const mapPreferencesToPayload: (preferences: NotificationPreferences) => Record<string, unknown>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mapPreferencesToPayload = exports.mapPayloadToPreferences = void 0;
|
|
4
|
+
const notificationRegistryState_1 = require("../../entity/notificationRegistry/notificationRegistryState");
|
|
5
|
+
const mapEventEnabledByChannel = (raw) => {
|
|
6
|
+
if (raw == null) {
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
const out = {};
|
|
10
|
+
Object.entries(raw).forEach(([eventId, channelMap]) => {
|
|
11
|
+
const mapped = {};
|
|
12
|
+
Object.entries(channelMap).forEach(([channel, enabled]) => {
|
|
13
|
+
mapped[(0, notificationRegistryState_1.toNotificationChannel)(channel)] = enabled;
|
|
14
|
+
});
|
|
15
|
+
out[eventId] = mapped;
|
|
16
|
+
});
|
|
17
|
+
return out;
|
|
18
|
+
};
|
|
19
|
+
const mapPayloadToPreferences = (payload) => {
|
|
20
|
+
const groupFrequencyRaw = payload?.group_frequency_by_group_id ?? {};
|
|
21
|
+
const groupFrequencyByGroupId = Object.fromEntries(Object.entries(groupFrequencyRaw).map(([groupId, frequency]) => [
|
|
22
|
+
groupId,
|
|
23
|
+
(0, notificationRegistryState_1.toNotificationFrequency)(frequency),
|
|
24
|
+
]));
|
|
25
|
+
return {
|
|
26
|
+
eventEnabledByChannel: mapEventEnabledByChannel(payload?.event_enabled_by_channel),
|
|
27
|
+
groupFrequencyByGroupId,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
exports.mapPayloadToPreferences = mapPayloadToPreferences;
|
|
31
|
+
const mapPreferencesToPayload = (preferences) => {
|
|
32
|
+
const payload = {};
|
|
33
|
+
if (Object.keys(preferences.eventEnabledByChannel).length > 0) {
|
|
34
|
+
payload.event_enabled_by_channel = preferences.eventEnabledByChannel;
|
|
35
|
+
}
|
|
36
|
+
if (Object.keys(preferences.groupFrequencyByGroupId).length > 0) {
|
|
37
|
+
payload.group_frequency_by_group_id = preferences.groupFrequencyByGroupId;
|
|
38
|
+
}
|
|
39
|
+
return payload;
|
|
40
|
+
};
|
|
41
|
+
exports.mapPreferencesToPayload = mapPreferencesToPayload;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { NotificationChannel, NotificationFrequency } from '../../entity/notificationRegistry/notificationRegistryState';
|
|
2
|
+
import { ZeniAPIStatus } from '../../responsePayload';
|
|
3
|
+
import { NotificationPreferencesPayload } from './notificationPreferencesViewPayload';
|
|
4
|
+
import { NotificationPreferences, NotificationPreferencesViewState } from './notificationPreferencesViewState';
|
|
5
|
+
export declare const initialState: NotificationPreferencesViewState;
|
|
6
|
+
export interface SetFrequencyPayload {
|
|
7
|
+
frequency: NotificationFrequency;
|
|
8
|
+
groupId: string;
|
|
9
|
+
}
|
|
10
|
+
export interface TogglePreferencePayload {
|
|
11
|
+
channel: NotificationChannel;
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
eventId: string;
|
|
14
|
+
}
|
|
15
|
+
export interface FetchNotificationPreferencesSuccessPayload {
|
|
16
|
+
preferences: NotificationPreferencesPayload | undefined;
|
|
17
|
+
saveEpochAtDispatch: number;
|
|
18
|
+
}
|
|
19
|
+
export interface SaveNotificationPreferencesSuccessPayload {
|
|
20
|
+
cancelEpochAtDispatch: number;
|
|
21
|
+
savedOverrides: NotificationPreferences;
|
|
22
|
+
serverEcho: NotificationPreferencesPayload | undefined;
|
|
23
|
+
}
|
|
24
|
+
export interface SaveNotificationPreferencesFailurePayload {
|
|
25
|
+
cancelEpochAtDispatch: number;
|
|
26
|
+
error?: ZeniAPIStatus;
|
|
27
|
+
}
|
|
28
|
+
export declare const clearAllNotificationPreferencesView: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"notificationPreferencesView/clearAllNotificationPreferencesView">, clearNotificationPreferencesLocalOverrides: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"notificationPreferencesView/clearNotificationPreferencesLocalOverrides">, fetchNotificationPreferencesSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<FetchNotificationPreferencesSuccessPayload, "notificationPreferencesView/fetchNotificationPreferencesSuccess">, saveNotificationPreferences: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"notificationPreferencesView/saveNotificationPreferences">, saveNotificationPreferencesFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<SaveNotificationPreferencesFailurePayload, "notificationPreferencesView/saveNotificationPreferencesFailure">, saveNotificationPreferencesSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<SaveNotificationPreferencesSuccessPayload, "notificationPreferencesView/saveNotificationPreferencesSuccess">, setGroupFrequency: import("@reduxjs/toolkit").ActionCreatorWithPayload<SetFrequencyPayload, "notificationPreferencesView/setGroupFrequency">, toggleEventChannel: import("@reduxjs/toolkit").ActionCreatorWithPayload<TogglePreferencePayload, "notificationPreferencesView/toggleEventChannel">;
|
|
29
|
+
declare const _default: import("redux").Reducer<NotificationPreferencesViewState>;
|
|
30
|
+
export default _default;
|