@zeniai/client-epic-state 5.2.75 → 5.2.76-betaNB1

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.
Files changed (47) hide show
  1. package/lib/commonStateTypes/common.d.ts +3 -0
  2. package/lib/commonStateTypes/common.js +10 -1
  3. package/lib/entity/reconciliationReport/reconciliationReportPayload.d.ts +124 -0
  4. package/lib/entity/reconciliationReport/reconciliationReportPayload.js +2 -0
  5. package/lib/entity/reconciliationReport/reconciliationReportReducer.d.ts +7 -0
  6. package/lib/entity/reconciliationReport/reconciliationReportReducer.js +173 -0
  7. package/lib/entity/reconciliationReport/reconciliationReportSelector.d.ts +3 -0
  8. package/lib/entity/reconciliationReport/reconciliationReportSelector.js +10 -0
  9. package/lib/entity/reconciliationReport/reconciliationReportState.d.ts +133 -0
  10. package/lib/entity/reconciliationReport/reconciliationReportState.js +15 -0
  11. package/lib/epic.d.ts +2 -1
  12. package/lib/epic.js +2 -0
  13. package/lib/esm/commonStateTypes/common.js +8 -0
  14. package/lib/esm/entity/reconciliationReport/reconciliationReportPayload.js +1 -0
  15. package/lib/esm/entity/reconciliationReport/reconciliationReportReducer.js +168 -0
  16. package/lib/esm/entity/reconciliationReport/reconciliationReportSelector.js +4 -0
  17. package/lib/esm/entity/reconciliationReport/reconciliationReportState.js +10 -0
  18. package/lib/esm/epic.js +2 -0
  19. package/lib/esm/index.js +5 -2
  20. package/lib/esm/reducer.js +7 -1
  21. package/lib/esm/view/companyTaskManagerView/epics/fetchCompanyTaskManagerViewEpic.js +140 -9
  22. package/lib/esm/view/reconciliationReportView/epic/fetchReconciliationReportEpic.js +29 -0
  23. package/lib/esm/view/reconciliationReportView/reconciliationReportViewPayload.js +1 -0
  24. package/lib/esm/view/reconciliationReportView/reconciliationReportViewReducer.js +44 -0
  25. package/lib/esm/view/reconciliationReportView/reconciliationReportViewSelector.js +10 -0
  26. package/lib/esm/view/reconciliationReportView/reconciliationReportViewState.js +1 -0
  27. package/lib/esm/view/taskManager/taskListView/taskListSelector.js +70 -19
  28. package/lib/index.d.ts +6 -2
  29. package/lib/index.js +40 -34
  30. package/lib/reducer.d.ts +2 -0
  31. package/lib/reducer.js +7 -1
  32. package/lib/rootStateTypes.d.ts +5 -1
  33. package/lib/view/companyTaskManagerView/epics/fetchCompanyTaskManagerViewEpic.d.ts +1 -1
  34. package/lib/view/companyTaskManagerView/epics/fetchCompanyTaskManagerViewEpic.js +140 -9
  35. package/lib/view/reconciliationReportView/epic/fetchReconciliationReportEpic.d.ts +19 -0
  36. package/lib/view/reconciliationReportView/epic/fetchReconciliationReportEpic.js +33 -0
  37. package/lib/view/reconciliationReportView/reconciliationReportViewPayload.d.ts +3 -0
  38. package/lib/view/reconciliationReportView/reconciliationReportViewPayload.js +2 -0
  39. package/lib/view/reconciliationReportView/reconciliationReportViewReducer.d.ts +13 -0
  40. package/lib/view/reconciliationReportView/reconciliationReportViewReducer.js +48 -0
  41. package/lib/view/reconciliationReportView/reconciliationReportViewSelector.d.ts +9 -0
  42. package/lib/view/reconciliationReportView/reconciliationReportViewSelector.js +13 -0
  43. package/lib/view/reconciliationReportView/reconciliationReportViewState.d.ts +4 -0
  44. package/lib/view/reconciliationReportView/reconciliationReportViewState.js +2 -0
  45. package/lib/view/taskManager/taskListView/taskListSelector.d.ts +13 -2
  46. package/lib/view/taskManager/taskListView/taskListSelector.js +70 -19
  47. package/package.json +1 -1
package/lib/epic.js CHANGED
@@ -445,6 +445,7 @@ const fetchEntityRecommendationsForLineUpdateEpic_1 = require("./view/recommenda
445
445
  const fetchRecommendationByEntityIdEpic_1 = require("./view/recommendation/fetchRecommendationByEntityIdEpic");
446
446
  const fetchRecommendationByEntityNameEpic_1 = require("./view/recommendation/fetchRecommendationByEntityNameEpic");
447
447
  const submitRecommendationFeedbackEpic_1 = require("./view/recommendation/submitRecommendationFeedbackEpic");
448
+ const fetchReconciliationReportEpic_1 = require("./view/reconciliationReportView/epic/fetchReconciliationReportEpic");
448
449
  const fetchReferralsEpic_1 = require("./view/referralView/epics/fetchReferralsEpic");
449
450
  const fetchRewardsPlanEpic_1 = require("./view/referralView/epics/fetchRewardsPlanEpic");
450
451
  const resendReferralInviteEpic_1 = require("./view/referralView/epics/resendReferralInviteEpic");
@@ -1174,6 +1175,7 @@ const ALL_EPICS = [
1174
1175
  fetchRecommendationForAccountTypeEpic_1.fetchRecommendationForAccountTypeEpic,
1175
1176
  fetchRecommendationsAndUpdateMerchantRecommendationsEpic_1.fetchRecommendationsAndUpdateMerchantRecommendationsEpic,
1176
1177
  fetchRecommendedTransactionRowIndexEpic_1.fetchRecommendedTransactionRowIndexEpic,
1178
+ fetchReconciliationReportEpic_1.fetchReconciliationReportEpic,
1177
1179
  fetchReferralsEpic_1.fetchReferralsEpic,
1178
1180
  fetchRegisteredInterestsEpic_1.fetchRegisteredInterestsEpic,
1179
1181
  fetchReimbursementCardEpic_1.fetchReimbursementCardEpic,
@@ -2,3 +2,11 @@ import { stringToUnion } from './stringToUnion';
2
2
  const COMPLETION_STATUS_TYPES = ['incomplete', 'complete'];
3
3
  export const toCompletionStatusType = (v) => stringToUnion(v, COMPLETION_STATUS_TYPES);
4
4
  export const getUnderScoreKey = (keys) => keys.join('_');
5
+ /** Early-exit emptiness check: `Object.keys(x).length > 0` allocates an array
6
+ * on every call, and this one runs inside selector inputs. */
7
+ export const hasAnyKey = (record) => {
8
+ for (const _key in record) {
9
+ return true;
10
+ }
11
+ return false;
12
+ };
@@ -0,0 +1,168 @@
1
+ import { createSlice } from '@reduxjs/toolkit';
2
+ import { mapCurrencyPayloadToCurrency } from '../../commonPayloadTypes/v2/viewAndReportPayloadv2';
3
+ import { toAmountWC } from '../../commonStateTypes/amount';
4
+ import { clearAll } from '../../rootActions';
5
+ import { date } from '../../zeniDayJS';
6
+ import { toReconciliationStatusCodeType } from '../accountRecon/accountReconState';
7
+ import { toReconciliationReportDataSource, toReconciliationReportMatchType, } from './reconciliationReportState';
8
+ export const initialState = {
9
+ reportsByReconciliationId: {},
10
+ };
11
+ const reconciliationReport = createSlice({
12
+ name: 'reconciliationReport',
13
+ initialState,
14
+ reducers: {
15
+ updateReconciliationReport(draft, action) {
16
+ const report = mapReconciliationReportPayloadToReconciliationReport(action.payload);
17
+ draft.reportsByReconciliationId[report.reconciliationId] = report;
18
+ },
19
+ removeReconciliationReport(draft, action) {
20
+ delete draft.reportsByReconciliationId[action.payload];
21
+ },
22
+ },
23
+ extraReducers: (builder) => {
24
+ builder.addCase(clearAll, (draft) => {
25
+ Object.assign(draft, initialState);
26
+ });
27
+ },
28
+ });
29
+ export const { updateReconciliationReport, removeReconciliationReport } = reconciliationReport.actions;
30
+ export default reconciliationReport.reducer;
31
+ function toOptionalAmount(amount, currency) {
32
+ if (amount == null) {
33
+ return undefined;
34
+ }
35
+ return toAmountWC(amount, currency);
36
+ }
37
+ function toOptionalDate(value) {
38
+ if (value == null || value === '') {
39
+ return undefined;
40
+ }
41
+ return date(value);
42
+ }
43
+ function mapCountAmount(payload, currency) {
44
+ return {
45
+ count: payload.count,
46
+ amount: toAmountWC(payload.amount, currency),
47
+ };
48
+ }
49
+ function mapTransactionRow(payload, currency) {
50
+ return {
51
+ transactionId: payload.transaction_id ?? undefined,
52
+ date: toOptionalDate(payload.date),
53
+ type: payload.type,
54
+ payee: payload.payee,
55
+ match: toReconciliationReportMatchType(payload.match),
56
+ amount: toAmountWC(payload.amount, currency),
57
+ clearedBalance: payload.cleared_balance != null
58
+ ? toAmountWC(payload.cleared_balance, currency)
59
+ : undefined,
60
+ glAccount: payload.gl_account ?? undefined,
61
+ referenceNumber: payload.reference_number ?? undefined,
62
+ issueDate: toOptionalDate(payload.issue_date),
63
+ ageDays: payload.age_days ?? undefined,
64
+ };
65
+ }
66
+ function mapBucket(payload, currency) {
67
+ return {
68
+ count: payload.count,
69
+ amount: toAmountWC(payload.amount, currency),
70
+ transactions: payload.transactions.map((transaction) => mapTransactionRow(transaction, currency)),
71
+ };
72
+ }
73
+ function mapPaymentsDeposits(payload, currency) {
74
+ return {
75
+ payments: mapBucket(payload.payments, currency),
76
+ deposits: mapBucket(payload.deposits, currency),
77
+ };
78
+ }
79
+ export function mapReconciliationReportPayloadToReconciliationReport(payload) {
80
+ const currency = mapCurrencyPayloadToCurrency(payload.currency);
81
+ return {
82
+ reconciliationId: payload.reconciliation_id,
83
+ companyName: payload.company_name ?? undefined,
84
+ account: {
85
+ accountId: payload.account.account_id,
86
+ accountName: payload.account.account_name ?? undefined,
87
+ accountType: payload.account.account_type ?? undefined,
88
+ accountSubType: payload.account.account_sub_type ?? undefined,
89
+ last4Digits: payload.account.last_4_digits ?? undefined,
90
+ maskedAccountNumber: payload.account.masked_account_number ?? undefined,
91
+ financialInstitution: payload.account.financial_institution ?? undefined,
92
+ glAccountName: payload.account.gl_account_name ?? undefined,
93
+ coaId: payload.account.coa_id ?? undefined,
94
+ },
95
+ period: {
96
+ startDate: toOptionalDate(payload.period.start_date),
97
+ endDate: toOptionalDate(payload.period.end_date),
98
+ },
99
+ statementDate: toOptionalDate(payload.statement_date),
100
+ dataSource: toReconciliationReportDataSource(payload.data_source),
101
+ currency,
102
+ status: {
103
+ code: toReconciliationStatusCodeType(payload.status.code),
104
+ label: payload.status.label,
105
+ },
106
+ reconciledAt: toOptionalDate(payload.reconciled_at),
107
+ reconciledBy: {
108
+ userId: payload.reconciled_by.user_id ?? undefined,
109
+ name: payload.reconciled_by.name ?? undefined,
110
+ },
111
+ disclaimer: payload.disclaimer,
112
+ reportReference: payload.report_reference ?? undefined,
113
+ locked: payload.locked,
114
+ difference: toOptionalAmount(payload.difference.amount, currency),
115
+ aiMatch: {
116
+ autoMatchedCount: payload.ai_match.auto_matched_count,
117
+ manuallyMatchedCount: payload.ai_match.manually_matched_count,
118
+ clearedCount: payload.ai_match.cleared_count,
119
+ },
120
+ afterPeriodEnd: {
121
+ unclearedAmount: toAmountWC(payload.after_period_end.uncleared_amount, currency),
122
+ clearedAmount: toAmountWC(payload.after_period_end.cleared_amount, currency),
123
+ },
124
+ statement: {
125
+ openingBalance: toOptionalAmount(payload.statement.opening_balance, currency),
126
+ paymentsCleared: mapCountAmount(payload.statement.payments_cleared, currency),
127
+ depositsCleared: mapCountAmount(payload.statement.deposits_cleared, currency),
128
+ closingBalance: toOptionalAmount(payload.statement.closing_balance, currency),
129
+ },
130
+ register: {
131
+ balanceAsOfPeriodEnd: toOptionalAmount(payload.register.balance_as_of_period_end, currency),
132
+ outstandingAsOfPeriodEnd: toAmountWC(payload.register.outstanding_as_of_period_end, currency),
133
+ balanceAsOfReconciledAt: toOptionalAmount(payload.register.balance_as_of_reconciled_at, currency),
134
+ },
135
+ summary: {
136
+ openingBalance: toOptionalAmount(payload.summary.opening_balance, currency),
137
+ clearedPayments: mapCountAmount(payload.summary.cleared_payments, currency),
138
+ clearedDeposits: mapCountAmount(payload.summary.cleared_deposits, currency),
139
+ closingBalance: toOptionalAmount(payload.summary.closing_balance, currency),
140
+ unclearedAsOfStatementDate: {
141
+ payments: mapCountAmount(payload.summary.uncleared_as_of_statement_date.payments, currency),
142
+ deposits: mapCountAmount(payload.summary.uncleared_as_of_statement_date.deposits, currency),
143
+ },
144
+ ledgerClosingAsOfStatementDate: toOptionalAmount(payload.summary.ledger_closing_as_of_statement_date, currency),
145
+ clearedAfterStatementDate: payload.summary.cleared_after_statement_date != null
146
+ ? mapCountAmount(payload.summary.cleared_after_statement_date, currency)
147
+ : undefined,
148
+ unclearedAfterStatementDate: payload.summary.uncleared_after_statement_date != null
149
+ ? mapCountAmount(payload.summary.uncleared_after_statement_date, currency)
150
+ : undefined,
151
+ ledgerBalanceAsOfReconciledOn: toOptionalAmount(payload.summary.ledger_balance_as_of_reconciled_on, currency),
152
+ bridge: {
153
+ bankClosing: toOptionalAmount(payload.summary.bridge.bank_closing, currency),
154
+ unclearedDeposits: toAmountWC(payload.summary.bridge.uncleared_deposits, currency),
155
+ unclearedPayments: toAmountWC(payload.summary.bridge.uncleared_payments, currency),
156
+ computedLedgerClosing: toOptionalAmount(payload.summary.bridge.computed_ledger_closing, currency),
157
+ ledgerClosing: toOptionalAmount(payload.summary.bridge.ledger_closing, currency),
158
+ variance: toOptionalAmount(payload.summary.bridge.variance, currency),
159
+ },
160
+ variance: toOptionalAmount(payload.summary.variance, currency),
161
+ },
162
+ details: {
163
+ cleared: mapPaymentsDeposits(payload.details.cleared, currency),
164
+ carriedForward: mapPaymentsDeposits(payload.details.carried_forward, currency),
165
+ unclearedAsOfStatementDate: mapPaymentsDeposits(payload.details.uncleared_as_of_statement_date, currency),
166
+ },
167
+ };
168
+ }
@@ -0,0 +1,4 @@
1
+ import recordGet from 'lodash/get';
2
+ export function getReconciliationReportByReconciliationId(reconciliationReportState, reconciliationId) {
3
+ return recordGet(reconciliationReportState.reportsByReconciliationId, reconciliationId, undefined);
4
+ }
@@ -0,0 +1,10 @@
1
+ import { stringToUnion } from '../../commonStateTypes/stringToUnion';
2
+ const RECONCILIATION_REPORT_MATCH_TYPE = ['automatic', 'manual'];
3
+ export const toReconciliationReportMatchType = (value) => {
4
+ if (value === 'auto') {
5
+ return 'automatic';
6
+ }
7
+ return stringToUnion(value, RECONCILIATION_REPORT_MATCH_TYPE);
8
+ };
9
+ const RECONCILIATION_REPORT_DATA_SOURCE = ['Statement', 'Plaid'];
10
+ export const toReconciliationReportDataSource = (value) => stringToUnion(value, RECONCILIATION_REPORT_DATA_SOURCE);
package/lib/esm/epic.js CHANGED
@@ -442,6 +442,7 @@ import { fetchEntityRecommendationsForLineUpdateEpic, } from './view/recommendat
442
442
  import { fetchRecommendationByEntityIdEpic, } from './view/recommendation/fetchRecommendationByEntityIdEpic';
443
443
  import { fetchRecommendationByEntityNameEpic, } from './view/recommendation/fetchRecommendationByEntityNameEpic';
444
444
  import { submitRecommendationFeedbackEpic, } from './view/recommendation/submitRecommendationFeedbackEpic';
445
+ import { fetchReconciliationReportEpic, } from './view/reconciliationReportView/epic/fetchReconciliationReportEpic';
445
446
  import { fetchReferralsEpic, } from './view/referralView/epics/fetchReferralsEpic';
446
447
  import { fetchRewardsPlanEpic, } from './view/referralView/epics/fetchRewardsPlanEpic';
447
448
  import { resendReferralInviteEpic, } from './view/referralView/epics/resendReferralInviteEpic';
@@ -1171,6 +1172,7 @@ const ALL_EPICS = [
1171
1172
  fetchRecommendationForAccountTypeEpic,
1172
1173
  fetchRecommendationsAndUpdateMerchantRecommendationsEpic,
1173
1174
  fetchRecommendedTransactionRowIndexEpic,
1175
+ fetchReconciliationReportEpic,
1174
1176
  fetchReferralsEpic,
1175
1177
  fetchRegisteredInterestsEpic,
1176
1178
  fetchReimbursementCardEpic,
package/lib/esm/index.js CHANGED
@@ -274,6 +274,8 @@ import { fetchProjectList } from './view/projectList/projectListReducer';
274
274
  import { getProjectList, } from './view/projectList/projectListSelector';
275
275
  import { fetchEntityRecommendationsByTransactionId, fetchEntityRecommendationsForLineUpdate, fetchRecommendationByEntityId, fetchRecommendationByEntityName, submitRecommendationFeedback, } from './view/recommendation/recommendationReducer';
276
276
  import { getRecommendationFeedbackSubmitState } from './view/recommendation/recommendationSelector';
277
+ import { fetchReconciliationReport } from './view/reconciliationReportView/reconciliationReportViewReducer';
278
+ import { getReconciliationReport } from './view/reconciliationReportView/reconciliationReportViewSelector';
277
279
  import { DEFAULT_REFERRER_AMOUNT, clearReferrals, fetchReferrals, fetchRewardsPlan, resendReferralInvite, saveReferralFormDataInLocalStore, sendReferralInvite, updateReferViewed, updateReferralListSortUiState, } from './view/referralView/referralReducer';
278
280
  import { getInviteFormView, getReferralListView, getRewardsPlanCard, } from './view/referralView/referralSelector';
279
281
  import { AmountStatusTypes, StatusTypes, toReferralListViewSortKeyType, } from './view/referralView/referralState';
@@ -416,7 +418,7 @@ import { allTaskPriority, allTaskStatus, getTaskDetail, } from './view/taskManag
416
418
  import { createTaskFromTaskGroupTemplate } from './view/taskManager/taskGroupTemplateView/taskGroupTemplateViewReducer';
417
419
  import { createNewTaskGroup, deleteTaskGroup, fetchAllTaskGroups, updateTaskGroupName, } from './view/taskManager/taskGroupView/taskGroupViewReducer';
418
420
  import { ALL_TASK_LIST_TABS, TASK_LIST_FILTER_CATEGORIES, TASK_LIST_GROUP_BY_CATEGORIES, toDueDateGroupKeyType, toTaskListGroupByKeyType, toTaskListGroupByKeyTypeStrict, } from './view/taskManager/taskListView/taskList';
419
- import { bulkUpdateTaskList, dragNDropTasks, fetchTaskListPage, initiateTaskListLocalData, removeTaskFromList, updateTaskFilters, updateTaskFromListView, updateTaskListLocalData, updateTaskListSearchText, updateTaskListTab, updateTaskListUIState, } from './view/taskManager/taskListView/taskListReducer';
421
+ import { bulkUpdateTaskList, dragNDropTasks, fetchTaskList, fetchTaskListPage, initiateTaskListLocalData, removeTaskFromList, updateTaskFilters, updateTaskFromListView, updateTaskListLocalData, updateTaskListSearchText, updateTaskListTab, updateTaskListUIState, } from './view/taskManager/taskListView/taskListReducer';
420
422
  import { getAllTasks, } from './view/taskManager/taskListView/taskListSelector';
421
423
  import { getDueDateValueFromDueDateGroupId, getTaskUpdates, sortSubtasks, } from './view/taskManager/taskListView/taskListViewHelpers';
422
424
  import { fetchTasksCard } from './view/tasksCard/tasksCardReducer';
@@ -576,6 +578,7 @@ export { getEntityAutoCompleteResults };
576
578
  export { updateEntities };
577
579
  export { getEntityByEntityIDs };
578
580
  export { getAddressByAddressId };
581
+ export { fetchReconciliationReport, getReconciliationReport, };
579
582
  export { ALL_ACCOUNTING_CONNECTION_CREATION_MODES, isCompanyEligibleForTreasury, isCardsProductEnabledOnCompany, filterChargeCardRolesWhenProductDisabled, };
580
583
  export { getCompanyByCompanyId, getControllersForCompany, getCompanyInfoForAllCockpitCompaniesInState, getFirstControllerEmail, getFirstControllerId, getControllersName, };
581
584
  export { getCompanyManagementView, getPlanListZeniSku, getAddonListZeniSku, };
@@ -671,7 +674,7 @@ export { fetchGlobalMerchantRecommendation, createGlobalMerchant, updateCreateGl
671
674
  export { approveVendorGlobalReview, rejectVendorGlobalReview, fetchVendorGlobalReviewView, updateSelectedGlobalMerchant, updateVendorGlobalReviewViewUIState, getVendorGlobalReviewView, getTenantMerchantByMerchantId, toVendorGlobalReviewColumnSortKeyType, updateVendorGlobalReviewViewLocalData, };
672
675
  export { fetchArAging, updateArAgingUIState, fetchArAgingDetail, updateArAgingDetailUIState, getArAgingReport, getArAgingDetailForCustomer, updateArAgingNodeCollapseState, };
673
676
  export { toRecurringBillFrequency, toRecurringBillFrequencyStrict, };
674
- export { getTaskGroupById, getAllTasks, fetchTaskListPage, getTaskDetail, fetchTaskDetailPage, saveTaskUpdatesToLocalStore, saveTaskDetail, createSubTask, resetSubTaskCreateStatus, fetchSubTasks, archiveTask, fetchCannedResponses, saveCannedResponse, deleteCannedResponse, getCannedResponsesView, updateTaskListSearchText, updateTaskListUIState, allTaskStatus, allTaskPriority, updateTaskFilters, TASK_LIST_FILTER_CATEGORIES, TASK_LIST_GROUP_BY_CATEGORIES, deleteTask, discardTaskUpdatesInLocalStore, bulkUpdateTaskList, createNewTaskGroup, initiateTaskListLocalData, deleteTaskGroup, updateTaskListLocalData, dragNDropTasks, updateTaskGroupName, toTaskListGroupByKeyType, toTaskListGroupByKeyTypeStrict, getTaskUpdates, sortSubtasks, fetchAllTaskGroups, initialTaskDetail, getDueDateValueFromDueDateGroupId, toPriorityCodeType, toTaskStatusCodeType, toDueDateGroupKeyType, updateTaskFromListView, updateTaskListTab, removeTaskFromList, snoozeTask, unsnoozeTask, convertHHMMStrToMinutes, initialTaskDetailLocalData, ALL_TASK_LIST_TABS, };
677
+ export { getTaskGroupById, getAllTasks, fetchTaskList, fetchTaskListPage, getTaskDetail, fetchTaskDetailPage, saveTaskUpdatesToLocalStore, saveTaskDetail, createSubTask, resetSubTaskCreateStatus, fetchSubTasks, archiveTask, fetchCannedResponses, saveCannedResponse, deleteCannedResponse, getCannedResponsesView, updateTaskListSearchText, updateTaskListUIState, allTaskStatus, allTaskPriority, updateTaskFilters, TASK_LIST_FILTER_CATEGORIES, TASK_LIST_GROUP_BY_CATEGORIES, deleteTask, discardTaskUpdatesInLocalStore, bulkUpdateTaskList, createNewTaskGroup, initiateTaskListLocalData, deleteTaskGroup, updateTaskListLocalData, dragNDropTasks, updateTaskGroupName, toTaskListGroupByKeyType, toTaskListGroupByKeyTypeStrict, getTaskUpdates, sortSubtasks, fetchAllTaskGroups, initialTaskDetail, getDueDateValueFromDueDateGroupId, toPriorityCodeType, toTaskStatusCodeType, toDueDateGroupKeyType, updateTaskFromListView, updateTaskListTab, removeTaskFromList, snoozeTask, unsnoozeTask, convertHHMMStrToMinutes, initialTaskDetailLocalData, ALL_TASK_LIST_TABS, };
675
678
  export { getAllTags, fetchTagList, createTag, deleteTag, };
676
679
  export { getAllCardsAndBankPaymentMethods, createCardSetupIntent, confirmCardSetupIntent, addCardPaymentSource, fetchPaymentSources, resetCardPaymentErrorStatuses, clearCardPaymentView, };
677
680
  export { getAuditReportGroupViewSelectorView, getAuditRuleGroupViewSelectorView, getUserFromAllUsers, fetchAuditRuleGroupView, fetchAuditReportGroupView, saveReasonForAuditRule, clearAuditReportGroupViewByCompanyId, };
@@ -38,10 +38,10 @@ import invoicingCoupon, { initialState as initialInvoicingCouponState, } from '.
38
38
  import invoicingCreditNote, { initialState as initialInvoicingCreditNoteState, } from './entity/invoicing/creditNote/creditNoteReducer';
39
39
  import invoicingDunningCase, { initialState as initialInvoicingDunningCaseState, } from './entity/invoicing/dunningCase/dunningCaseReducer';
40
40
  import invoicingInvoice, { initialState as initialInvoicingInvoiceState, } from './entity/invoicing/invoice/invoiceReducer';
41
+ import invoicingCatalog, { initialState as initialInvoicingCatalogState, } from './entity/invoicing/invoicingCatalog/invoicingCatalogReducer';
41
42
  import invoicingCustomer, { initialState as initialInvoicingCustomerState, } from './entity/invoicing/invoicingCustomer/invoicingCustomerReducer';
42
43
  import invoicingSubscription, { initialState as initialInvoicingSubscriptionState, } from './entity/invoicing/invoicingSubscription/invoicingSubscriptionReducer';
43
44
  import invoicingTransaction, { initialState as initialInvoicingTransactionState, } from './entity/invoicing/invoicingTransaction/invoicingTransactionReducer';
44
- import invoicingCatalog, { initialState as initialInvoicingCatalogState, } from './entity/invoicing/invoicingCatalog/invoicingCatalogReducer';
45
45
  import jeSchedules, { initialState as initialJESchedulesState, } from './entity/jeSchedules/jeSchedulesReducer';
46
46
  import merchant, { initialState as initialMerchantState, } from './entity/merchant/merchantReducer';
47
47
  import monthEndCloseChecks, { initialState as initialMonthEndCloseChecksState, } from './entity/monthEndCloseChecks/monthEndCloseChecksReducer';
@@ -52,6 +52,7 @@ import paymentAccount, { initialState as initialPaymentAccountState, } from './e
52
52
  import paymentInstrument, { initialState as initialPaymentInstrumentState, } from './entity/paymentInstrument/paymentInstrumentReducer';
53
53
  import accountingSummary, { initialState as initialAccountingSummaryState, } from './entity/portfolio/accountingSummary/accountingSummaryReducer';
54
54
  import project, { initialState as initialProjectState, } from './entity/project/projectReducer';
55
+ import reconciliationReport, { initialState as initialReconciliationReportState, } from './entity/reconciliationReport/reconciliationReportReducer';
55
56
  import reimbursement, { initialState as initialReimbursementState, } from './entity/reimbursement/reimbursementReducer';
56
57
  import sectionAccountsView, { initialState as initialSectionAccountsViewState, } from './entity/sectionAccountsView/sectionAccountsViewReducer';
57
58
  import sectionClassesViewV2, { initialState as initialSectionClassesViewStateV2, } from './entity/sectionClassesViewV2/sectionClassesViewReducer';
@@ -179,6 +180,7 @@ import profitAndLossClassesView, { initialState as initialProfitAndLossClassesVi
179
180
  import profitAndLossProjectView, { initialState as initialProfitAndLossProjectViewState, } from './view/profitAndLossProjectView/profitAndLossProjectViewReducer';
180
181
  import projectList, { initialState as initialProjectListState, } from './view/projectList/projectListReducer';
181
182
  import recommendation, { initialState as initialEntityRecommendationState, } from './view/recommendation/recommendationReducer';
183
+ import reconciliationReportView, { initialState as initialReconciliationReportViewState, } from './view/reconciliationReportView/reconciliationReportViewReducer';
182
184
  import referralView, { initialState as initialReferralViewState, } from './view/referralView/referralReducer';
183
185
  import reimbursementCard, { initialState as initialReimbursementCardState, } from './view/reimbursementCard/reimbursementCardReducer';
184
186
  import reportUIOptionsReducer, { initialState as initialReportUIOptionsState, } from './view/reportUIOptions/reportUIOptionsReducer';
@@ -325,6 +327,7 @@ const initialEntitiesState = {
325
327
  onboardingCustomerState: initialOnboardingCustomerState,
326
328
  paymentAccountState: initialPaymentAccountState,
327
329
  paymentInstrumentState: initialPaymentInstrumentState,
330
+ reconciliationReportState: initialReconciliationReportState,
328
331
  recurringBillState: initialRecurringBillState,
329
332
  reimbursementConfigState: initialReimbursementConfigState,
330
333
  reimbursementState: initialReimbursementState,
@@ -493,6 +496,7 @@ const initialViewsState = {
493
496
  profitAndLossState: initialProfitAndLossState,
494
497
  projectListState: initialProjectListState,
495
498
  realTimeApprovalState: initialRealTimeApprovalState,
499
+ reconciliationReportViewState: initialReconciliationReportViewState,
496
500
  referralViewState: initialReferralViewState,
497
501
  reimbursementCardState: initialReimbursementCardState,
498
502
  reimbursementConfigState: initialReimbursementConfigState,
@@ -611,6 +615,7 @@ const entityReducers = {
611
615
  onboardingCustomerState: onboardingCustomer,
612
616
  paymentAccountState: paymentAccount,
613
617
  paymentInstrumentState: paymentInstrument,
618
+ reconciliationReportState: reconciliationReport,
614
619
  recurringBillState: recurringBill,
615
620
  reimbursementConfigState: reimbursementConfig,
616
621
  reimbursementState: reimbursement,
@@ -779,6 +784,7 @@ const viewReducers = {
779
784
  profitAndLossState: profitAndLoss,
780
785
  projectListState: projectList,
781
786
  realTimeApprovalState: realTimeApproval,
787
+ reconciliationReportViewState: reconciliationReportView,
782
788
  referralViewState: referralView,
783
789
  reimbursementCardState: reimbursementCard,
784
790
  reimbursementConfigState: reimbursementConfig,
@@ -4,19 +4,41 @@ import { getSortOrder } from '../../../commonPayloadTypes/sortOrderPayload';
4
4
  import { updateTags } from '../../../entity/tag/tagReducer';
5
5
  import { updateTasks } from '../../../entity/task/taskReducer';
6
6
  import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
7
+ import { date as toZeniDate } from '../../../zeniDayJS';
7
8
  import { fetchCompanyTaskManagerView, updateCompanyTaskManagerViewOnFailure, updateCompanyTaskManagerViewOnSuccess, } from '../companyTaskManagerViewReducer';
8
9
  const DEFAULT_PAGE_SIZE = 100;
9
- export const fetchCompanyTaskManagerViewEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchCompanyTaskManagerView.match), switchMap((action) => {
10
+ // Mutates the accumulator rather than spreading it: spreading rebuilds the whole
11
+ // map on every entry, which is O(n^2) over a tenant's tag list.
12
+ const toTagNameByID = (state) => Object.entries(state.tagState.tagByID).reduce((result, [tagId, tag]) => {
13
+ const name = tag?.name;
14
+ if (name != null && name !== '') {
15
+ result[tagId] = name;
16
+ }
17
+ return result;
18
+ }, {});
19
+ const hasTagCategory = (filters) => (filters?.categories ?? []).some((category) => category.field === 'tag');
20
+ export const fetchCompanyTaskManagerViewEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchCompanyTaskManagerView.match), switchMap((action) => {
10
21
  const { from: fromOffset, searchString, sortKey, sortOrder, filters, } = action.payload;
11
22
  const queryValue = {
12
23
  size: DEFAULT_PAGE_SIZE,
13
24
  from: fromOffset,
14
25
  global_search: searchString ?? '',
26
+ // TasksEsSearch defaults this to true, which AND-injects an
27
+ // `assignees = <caller>` filter. Cockpit's task list is a cross-tenant
28
+ // view grouped by company (and offers an Assignee filter), so it must
29
+ // NOT be scoped to the operator — otherwise every category filter
30
+ // AND's down to their own tasks and returns nothing. The metrics panel
31
+ // deliberately passes `true`; this list deliberately passes `false`.
32
+ is_filter_by_logged_in_user: false,
15
33
  sort_by: toSortKeyPayload(sortKey),
16
34
  sort_order: getSortOrder(sortOrder),
17
35
  filter_join: filters?.categoryCombinationOperator === 'AND' ? 'and' : 'or',
18
36
  ...(filters?.categories != null && filters.categories.length > 0
19
- ? { filter_by: toFilterByPayload(filters) }
37
+ ? {
38
+ // Only walk the tag map when a tag filter is actually in play;
39
+ // otherwise every dispatch pays for a map nothing reads.
40
+ filter_by: toFilterByPayload(filters, hasTagCategory(filters) ? toTagNameByID(state$.value) : {}),
41
+ }
20
42
  : {}),
21
43
  task_group_scopes: filters?.taskGroupScopes != null && filters.taskGroupScopes.length > 0
22
44
  ? filters.taskGroupScopes
@@ -63,14 +85,123 @@ const toSortKeyPayload = (sortKey) => {
63
85
  return sortKey;
64
86
  }
65
87
  };
66
- const toFilterByPayload = (filters) => {
88
+ // Cockpit filter-category keys are UI identifiers; the ES index uses its own
89
+ // field names (see TasksEsSearch.get). Anything not listed here already matches
90
+ // the contract (assignees, priority, status, company_id).
91
+ const ES_FIELD_BY_CATEGORY = {
92
+ creationDate: 'create_time',
93
+ dueDate: 'due_date',
94
+ tag: 'tags',
95
+ };
96
+ // ES rejects `is` / `is_not` on date fields — they only accept `between`, whose
97
+ // values are the [start, end] pair the dateRange picker already emits.
98
+ const ES_DATE_FIELDS = new Set(['create_time', 'due_date']);
99
+ // The cockpit date picker hands over ZeniDate objects; once filters have round-
100
+ // tripped through redux they can arrive as ISO or date-only strings instead.
101
+ // Both have to reach ES as instants: `create_time`/`update_time` carry a real
102
+ // timestamp, so a date-only upper bound lands on midnight and drops everything
103
+ // created later that day — a single-day Created filter would return almost
104
+ // nothing, which is the empty state this epic exists to fix.
105
+ // A real check rather than `value as ZeniDate`: the cast would silently lie
106
+ // about a raw Date, a number, or anything else redux hands back, leaving the
107
+ // runtime guard as the only thing standing between us and a crash.
108
+ const isZeniDateLike = (value) => value != null &&
109
+ typeof value.isValid === 'function' &&
110
+ typeof value.format === 'function';
111
+ const CALENDAR_DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
112
+ const toDateBound = (value, edge) => {
113
+ // A bare `YYYY-MM-DD` carries no instant to misread — use it verbatim. This
114
+ // is the only genuinely timezone-proof shape, which is why it is worth a
115
+ // fast path of its own.
116
+ if (typeof value === 'string' && CALENDAR_DATE_ONLY.test(value)) {
117
+ return edge === 'start'
118
+ ? `${value}T00:00:00.000Z`
119
+ : `${value}T23:59:59.999Z`;
120
+ }
121
+ // Everything else came from the picker, and the picker emits LOCAL midnight:
122
+ // `ZeniDatePicker` hands react-datepicker's local-midnight Date to
123
+ // `zeniDate(date.toISOString())`. So the instant encodes "midnight on the day
124
+ // the user clicked, in the user's zone" — and the day they clicked is
125
+ // recovered by reading it locally, never in UTC.
126
+ //
127
+ // Reading it in UTC subtracts the offset for every UTC+ operator: clicking
128
+ // "Mar 4" in Asia/Kolkata stores 2026-03-03T18:30:00Z, which is the 3rd in
129
+ // UTC. A single-day filter then returns the wrong day's tasks rather than
130
+ // none, and US-based testing never sees it.
131
+ const parsed = typeof value === 'string' ? toZeniDate(value) : value;
132
+ if (!isZeniDateLike(parsed) || !parsed.isValid()) {
133
+ // Drop the row rather than send garbage: ES rejects the whole request on
134
+ // one bad bound, taking the other filters down with it.
135
+ return '';
136
+ }
137
+ const ymd = parsed.format('YYYY-MM-DD');
138
+ return edge === 'start' ? `${ymd}T00:00:00.000Z` : `${ymd}T23:59:59.999Z`;
139
+ };
140
+ const toFilterByPayload = (filters, tagNameByID) => {
67
141
  if (filters.categories == null) {
68
142
  return [];
69
143
  }
70
- return filters.categories.map((category) => ({
71
- field: category.field ?? '',
72
- operator: category.matchingOperator === 'equal' ? 'is' : 'is_not',
73
- values: category.values.map((v) => String(v)),
74
- join: category.valuesCombinationOperator === 'ALL' ? 'and' : 'or',
75
- }));
144
+ return filters.categories.flatMap((category) => {
145
+ const categoryField = category.field ?? '';
146
+ const field = ES_FIELD_BY_CATEGORY[categoryField] ?? categoryField;
147
+ // The dropdown is keyed by tagId, but ES matches `tags` on the tag NAME,
148
+ // so resolve through the tag entity this epic itself populates. Unknown ids
149
+ // fall through unchanged rather than being dropped.
150
+ const isDateField = ES_DATE_FIELDS.has(field);
151
+ // `between` is exactly [from, to]. A half-filled picker (one value) makes ES
152
+ // reject the query outright, and a longer array would quietly bind extras to
153
+ // `end` — both land the user back on an unexplained empty page, which is the
154
+ // failure this epic exists to remove. Drop the row instead.
155
+ if (isDateField && category.values.length !== 2) {
156
+ return [];
157
+ }
158
+ const values = category.values.map((value, index) => {
159
+ if (isDateField) {
160
+ // [from, to] — widen the upper bound to the end of its day.
161
+ return toDateBound(value, index === 0 ? 'start' : 'end');
162
+ }
163
+ const raw = String(value);
164
+ if (field !== 'tags') {
165
+ return raw;
166
+ }
167
+ // An id we cannot resolve would reach ES verbatim and match nothing —
168
+ // another silent empty page. Can happen when a filter is hydrated from the
169
+ // URL before `updateTags` has populated `tagByID`, so keep the value
170
+ // (dropping it would widen the result set) but make it visible in logs.
171
+ const tagName = tagNameByID[raw];
172
+ if (tagName == null) {
173
+ console.debug(`[taskManager] unresolved tagId "${raw}" sent to ES verbatim; expect no matches`);
174
+ }
175
+ return tagName ?? raw;
176
+ });
177
+ // `toDateBound` returns '' for a value it cannot parse. Honour that here:
178
+ // emitting a `between` with an empty bound fails the whole ES query, which
179
+ // takes every OTHER filter in the same request down with it — the silent
180
+ // empty page this epic exists to remove, from one unparseable value that a
181
+ // URL-hydrated filter can supply.
182
+ if (isDateField && values.some((value) => value === '')) {
183
+ return [];
184
+ }
185
+ // ES has no date-exclusion operator: `is`/`is_not` are rejected on date
186
+ // fields, and `between` can only select a range, never exclude one. The
187
+ // operator dropdown still offers "is not" for every category, so an excluded
188
+ // date range would otherwise be sent as a plain `between` — returning exactly
189
+ // the rows the user asked to hide. Drop the row instead: a superset is
190
+ // wrong-but-obvious, an inverted result is wrong-and-invisible.
191
+ if (isDateField && category.matchingOperator !== 'equal') {
192
+ return [];
193
+ }
194
+ return [
195
+ {
196
+ field,
197
+ operator: isDateField
198
+ ? 'between'
199
+ : category.matchingOperator === 'equal'
200
+ ? 'is'
201
+ : 'is_not',
202
+ values,
203
+ join: category.valuesCombinationOperator === 'ALL' ? 'and' : 'or',
204
+ },
205
+ ];
206
+ });
76
207
  };
@@ -0,0 +1,29 @@
1
+ import { of } from 'rxjs';
2
+ import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
3
+ import { updateReconciliationReport } from '../../../entity/reconciliationReport/reconciliationReportReducer';
4
+ import { getReconciliationReportByReconciliationId } from '../../../entity/reconciliationReport/reconciliationReportSelector';
5
+ import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
6
+ import { fetchReconciliationReport, fetchReconciliationReportFailure, fetchReconciliationReportSuccess, } from '../reconciliationReportViewReducer';
7
+ export const fetchReconciliationReportEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchReconciliationReport.match), switchMap((action) => {
8
+ const { reconciliationId, cacheOverride } = action.payload;
9
+ const cachedReport = getReconciliationReportByReconciliationId(state$.value.reconciliationReportState, reconciliationId);
10
+ if (cacheOverride === true || cachedReport == null) {
11
+ return zeniAPI
12
+ .getJSON(`${zeniAPI.apiEndPoints.reconciliationMicroServiceBaseUrl}/2.0/reconciliation/${reconciliationId}/report`)
13
+ .pipe(mergeMap((response) => {
14
+ if (isSuccessResponse(response) === true &&
15
+ response.data != null) {
16
+ return of(updateReconciliationReport(response.data), fetchReconciliationReportSuccess(reconciliationId));
17
+ }
18
+ return of(fetchReconciliationReportFailure({
19
+ reconciliationId,
20
+ status: response.status,
21
+ }));
22
+ }), catchError((error) => of(fetchReconciliationReportFailure({
23
+ reconciliationId,
24
+ status: createZeniAPIStatus('Unexpected Error', 'Fetch Reconciliation Report REST API call errored out' +
25
+ JSON.stringify(error)),
26
+ }))));
27
+ }
28
+ return of(fetchReconciliationReportSuccess(reconciliationId));
29
+ }));
@@ -0,0 +1,44 @@
1
+ import { createSlice } from '@reduxjs/toolkit';
2
+ import { clearAll } from '../../rootActions';
3
+ export const initialState = {
4
+ fetchReconciliationReportState: {},
5
+ };
6
+ const reconciliationReportView = createSlice({
7
+ name: 'reconciliationReportView',
8
+ initialState,
9
+ reducers: {
10
+ fetchReconciliationReport: {
11
+ prepare(reconciliationId, cacheOverride = false) {
12
+ return { payload: { reconciliationId, cacheOverride } };
13
+ },
14
+ reducer(draft, action) {
15
+ const { reconciliationId } = action.payload;
16
+ draft.fetchReconciliationReportState[reconciliationId] = {
17
+ fetchState: 'In-Progress',
18
+ error: undefined,
19
+ };
20
+ },
21
+ },
22
+ fetchReconciliationReportSuccess(draft, action) {
23
+ const reconciliationId = action.payload;
24
+ draft.fetchReconciliationReportState[reconciliationId] = {
25
+ fetchState: 'Completed',
26
+ error: undefined,
27
+ };
28
+ },
29
+ fetchReconciliationReportFailure(draft, action) {
30
+ const { reconciliationId, status } = action.payload;
31
+ draft.fetchReconciliationReportState[reconciliationId] = {
32
+ fetchState: 'Error',
33
+ error: status,
34
+ };
35
+ },
36
+ },
37
+ extraReducers: (builder) => {
38
+ builder.addCase(clearAll, (draft) => {
39
+ Object.assign(draft, initialState);
40
+ });
41
+ },
42
+ });
43
+ export const { fetchReconciliationReport, fetchReconciliationReportSuccess, fetchReconciliationReportFailure, } = reconciliationReportView.actions;
44
+ export default reconciliationReportView.reducer;
@@ -0,0 +1,10 @@
1
+ import { getReconciliationReportByReconciliationId } from '../../entity/reconciliationReport/reconciliationReportSelector';
2
+ export function getReconciliationReport(state, reconciliationId) {
3
+ const fetchReconciliationReportState = state.reconciliationReportViewState.fetchReconciliationReportState[reconciliationId];
4
+ const report = getReconciliationReportByReconciliationId(state.reconciliationReportState, reconciliationId);
5
+ return {
6
+ fetchState: fetchReconciliationReportState?.fetchState ?? 'Not-Started',
7
+ fetchError: fetchReconciliationReportState?.error,
8
+ report,
9
+ };
10
+ }