@zeniai/client-epic-state 5.1.82 → 5.1.83-betaRD1
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/entity/jeSchedules/jeSchedulesPayload.d.ts +2 -0
- package/lib/entity/jeSchedules/jeSchedulesPayload.js +2 -0
- package/lib/entity/jeSchedules/jeSchedulesSelector.d.ts +4 -0
- package/lib/entity/jeSchedules/jeSchedulesSelector.js +13 -2
- package/lib/entity/jeSchedules/jeSchedulesState.d.ts +2 -0
- package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
- package/lib/entity/snackbar/snackbarTypes.js +29 -0
- package/lib/entity/transaction/payloadTypes/scheduleTransactionPayload.d.ts +2 -0
- package/lib/entity/transaction/payloadTypes/scheduleTransactionPayload.js +2 -0
- package/lib/entity/transaction/stateTypes/scheduleTransaction.d.ts +2 -0
- package/lib/epic.d.ts +5 -1
- package/lib/epic.js +5 -1
- package/lib/esm/entity/jeSchedules/jeSchedulesPayload.js +2 -0
- package/lib/esm/entity/jeSchedules/jeSchedulesSelector.js +13 -2
- package/lib/esm/entity/snackbar/snackbarTypes.js +29 -0
- package/lib/esm/entity/transaction/payloadTypes/scheduleTransactionPayload.js +2 -0
- package/lib/esm/epic.js +5 -1
- package/lib/esm/index.js +11 -5
- package/lib/esm/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +6 -3
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +26 -3
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/newSchedule/initializeNewScheduleEpic.js +21 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/newSchedule/saveNewScheduleEpic.js +110 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/newSchedule/searchTransactionsForNewScheduleEpic.js +73 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +22 -0
- package/lib/esm/view/expenseAutomationView/helpers/newScheduleLocalDataHelper.js +140 -0
- package/lib/esm/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +157 -28
- package/lib/esm/view/expenseAutomationView/selectorTypes/jeNewScheduleViewSelectorTypes.js +1 -0
- package/lib/esm/view/expenseAutomationView/selectors/jeNewScheduleViewSelector.js +70 -0
- package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +159 -22
- package/lib/esm/view/expenseAutomationView/types/jeSchedulesViewState.js +5 -6
- package/lib/esm/view/invoicing/createCreditNote/createCreditNoteEpic.js +24 -4
- package/lib/esm/view/invoicing/createInvoice/createInvoiceEpic.js +24 -4
- package/lib/esm/view/invoicing/createInvoice/createInvoiceSelector.js +37 -10
- package/lib/esm/view/invoicing/discountAction/discountActionEpic.js +33 -9
- package/lib/esm/view/invoicing/dunningAction/dunningActionEpic.js +35 -9
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.js +5 -1
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +32 -5
- package/lib/esm/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +23 -3
- package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +24 -4
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +24 -4
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCouponsEpic.js +20 -3
- package/lib/esm/view/invoicing/invoiceAction/invoiceActionEpic.js +60 -17
- package/lib/esm/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.js +30 -0
- package/lib/esm/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.js +4 -29
- package/lib/esm/view/invoicing/invoicingQboView/epics/retryInvoicingQboSyncTaskEpic.js +29 -10
- package/lib/esm/view/invoicing/invoicingQboView/epics/startInvoicingQboBackfillEpic.js +28 -5
- package/lib/esm/view/invoicing/issueCreditNote/issueCreditNoteEpic.js +24 -4
- package/lib/esm/view/invoicing/issueCreditNote/issueCreditNoteSelector.js +22 -14
- package/lib/esm/view/invoicing/recordPayment/recordPaymentEpic.js +46 -7
- package/lib/esm/view/invoicing/settingsView/settingsViewEpics.js +26 -8
- package/lib/esm/view/invoicing/settingsView/settingsViewReducer.js +1 -1
- package/lib/esm/view/invoicing/settingsView/settingsViewSelector.js +4 -1
- package/lib/esm/view/invoicing/subscriptionAction/subscriptionActionEpic.js +41 -9
- package/lib/index.d.ts +15 -10
- package/lib/index.js +71 -50
- package/lib/view/expenseAutomationView/epics/common/fetchAllExpenseAutomationTabsEpic.js +6 -3
- package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.d.ts +2 -2
- package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +24 -1
- package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/initializeNewScheduleEpic.d.ts +8 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/initializeNewScheduleEpic.js +25 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/saveNewScheduleEpic.d.ts +45 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/saveNewScheduleEpic.js +114 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/searchTransactionsForNewScheduleEpic.d.ts +8 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/searchTransactionsForNewScheduleEpic.js +77 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.d.ts +6 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +26 -0
- package/lib/view/expenseAutomationView/helpers/newScheduleLocalDataHelper.d.ts +36 -0
- package/lib/view/expenseAutomationView/helpers/newScheduleLocalDataHelper.js +145 -0
- package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
- package/lib/view/expenseAutomationView/payload/jeSchedulesPayload.d.ts +7 -1
- package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +44 -10
- package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +158 -28
- package/lib/view/expenseAutomationView/selectorTypes/jeNewScheduleViewSelectorTypes.d.ts +25 -0
- package/lib/view/expenseAutomationView/selectorTypes/jeNewScheduleViewSelectorTypes.js +2 -0
- package/lib/view/expenseAutomationView/selectorTypes/jeSchedulesSelectorTypes.d.ts +34 -8
- package/lib/view/expenseAutomationView/selectors/jeNewScheduleViewSelector.d.ts +10 -0
- package/lib/view/expenseAutomationView/selectors/jeNewScheduleViewSelector.js +75 -0
- package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.d.ts +4 -1
- package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +161 -21
- package/lib/view/expenseAutomationView/types/jeSchedulesViewState.d.ts +58 -8
- package/lib/view/expenseAutomationView/types/jeSchedulesViewState.js +8 -8
- package/lib/view/invoicing/createCreditNote/createCreditNoteEpic.d.ts +2 -1
- package/lib/view/invoicing/createCreditNote/createCreditNoteEpic.js +23 -3
- package/lib/view/invoicing/createInvoice/createInvoiceEpic.d.ts +2 -1
- package/lib/view/invoicing/createInvoice/createInvoiceEpic.js +23 -3
- package/lib/view/invoicing/createInvoice/createInvoiceSelector.d.ts +22 -8
- package/lib/view/invoicing/createInvoice/createInvoiceSelector.js +37 -10
- package/lib/view/invoicing/discountAction/discountActionEpic.d.ts +2 -1
- package/lib/view/invoicing/discountAction/discountActionEpic.js +32 -8
- package/lib/view/invoicing/dunningAction/dunningActionEpic.d.ts +2 -1
- package/lib/view/invoicing/dunningAction/dunningActionEpic.js +34 -8
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.d.ts +3 -1
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.js +5 -1
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +31 -4
- package/lib/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +22 -2
- package/lib/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +23 -3
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +23 -3
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCouponsEpic.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/syncInvoicingSubscriptionCouponsEpic.js +19 -2
- package/lib/view/invoicing/invoiceAction/invoiceActionEpic.d.ts +2 -1
- package/lib/view/invoicing/invoiceAction/invoiceActionEpic.js +59 -16
- package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewPayload.d.ts +13 -0
- package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.d.ts +1 -1
- package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewReducer.js +30 -0
- package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.d.ts +11 -31
- package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewSelector.js +4 -29
- package/lib/view/invoicing/invoicingConfigView/invoicingConfigViewState.d.ts +15 -0
- package/lib/view/invoicing/invoicingQboView/epics/invoicingQboViewActionType.d.ts +2 -1
- package/lib/view/invoicing/invoicingQboView/epics/retryInvoicingQboSyncTaskEpic.js +28 -9
- package/lib/view/invoicing/invoicingQboView/epics/startInvoicingQboBackfillEpic.js +27 -4
- package/lib/view/invoicing/issueCreditNote/issueCreditNoteEpic.d.ts +2 -1
- package/lib/view/invoicing/issueCreditNote/issueCreditNoteEpic.js +23 -3
- package/lib/view/invoicing/issueCreditNote/issueCreditNoteSelector.d.ts +24 -7
- package/lib/view/invoicing/issueCreditNote/issueCreditNoteSelector.js +23 -16
- package/lib/view/invoicing/recordPayment/recordPaymentEpic.d.ts +2 -1
- package/lib/view/invoicing/recordPayment/recordPaymentEpic.js +45 -6
- package/lib/view/invoicing/settingsView/settingsViewEpics.d.ts +2 -1
- package/lib/view/invoicing/settingsView/settingsViewEpics.js +25 -7
- package/lib/view/invoicing/settingsView/settingsViewReducer.d.ts +10 -1
- package/lib/view/invoicing/settingsView/settingsViewReducer.js +1 -1
- package/lib/view/invoicing/settingsView/settingsViewSelector.d.ts +4 -2
- package/lib/view/invoicing/settingsView/settingsViewSelector.js +4 -1
- package/lib/view/invoicing/subscriptionAction/subscriptionActionEpic.d.ts +2 -1
- package/lib/view/invoicing/subscriptionAction/subscriptionActionEpic.js +40 -8
- package/package.json +1 -1
|
@@ -3,7 +3,8 @@ import orderBy from 'lodash/orderBy';
|
|
|
3
3
|
import { reduceAnyFetchState } from '../../../commonStateTypes/reduceFetchState';
|
|
4
4
|
import { LAST_SCHEDULE_DAY_OF_MONTH, toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
|
|
5
5
|
import { getAccountIdsForTypes } from '../../../entity/account/accountSelector';
|
|
6
|
-
import { getJEScheduledTransactionByJEScheduleTransactionKey, } from '../../../entity/jeSchedules/jeSchedulesSelector';
|
|
6
|
+
import { getJEAccruedScheduleByJEScheduleKey, getJEScheduledTransactionByJEScheduleTransactionKey, } from '../../../entity/jeSchedules/jeSchedulesSelector';
|
|
7
|
+
import { ALL_SCHEDULES_TYPES, } from '../../../entity/jeSchedules/jeSchedulesTypes';
|
|
7
8
|
import { getCurrentTenant } from '../../../entity/tenant/tenantSelector';
|
|
8
9
|
import { getAccountIdsForLabel, getAccountList, getNestedAccountListHierarchy, getUncategorizedAccounts, } from '../../accountList/accountListSelector';
|
|
9
10
|
import { getClassList, getNestedClassListHierarchy, } from '../../classList/classListSelector';
|
|
@@ -11,7 +12,38 @@ import { getAccountsTypesForScheduleDetails } from '../../scheduleView/scheduleD
|
|
|
11
12
|
import { filterNestedAccountHierarchyById } from '../../scheduleView/scheduleDetailView/scheduleDetailSelector';
|
|
12
13
|
import { getAccountLabelForScheduleType, getAccountsTypesForScheduleList, } from '../../scheduleView/scheduleListView/scheduleListHelper';
|
|
13
14
|
import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
|
|
15
|
+
import { ALL_JE_PAGE_TABS, getJESchedulePeriodTabKey, } from '../types/jeSchedulesViewState';
|
|
14
16
|
import { getJEAccountSettingsView } from './jeAccountSettingsViewSelector';
|
|
17
|
+
import { getNewScheduleView } from './jeNewScheduleViewSelector';
|
|
18
|
+
// Matches the AI-automation user addresses webc's schedule list row already
|
|
19
|
+
// checks (JEScheduleReadOnlyListRow/JEScheduleDraftListRow "Method" column,
|
|
20
|
+
// strings.ts aiBotEmail/botEmail) to determine whether a schedule was
|
|
21
|
+
// created by the AI accountant vs. a human.
|
|
22
|
+
const AI_BOT_EMAIL = 'ai@zeni.ai';
|
|
23
|
+
const BOT_EMAIL = 'bot@zeni.ai';
|
|
24
|
+
// Exported (rather than kept as a closure inside the selector) so it can be
|
|
25
|
+
// unit-tested directly — confidenceScore is a separate, more narrowly
|
|
26
|
+
// populated field that can be null on a schedule the bot user created,
|
|
27
|
+
// which previously caused these schedules to display as "AI Accountant" in
|
|
28
|
+
// the list but count under "Manual" in the KPI summary.
|
|
29
|
+
export const isAiAccountantSchedule = (schedule) => schedule.createdByUser?.email === AI_BOT_EMAIL ||
|
|
30
|
+
schedule.createdByUser?.email === BOT_EMAIL;
|
|
31
|
+
// Accrued schedules are structurally distinct — no baseTransaction, carrying recurringAmount and
|
|
32
|
+
// memo in its place — so anything reading baseTransaction has to branch on this first.
|
|
33
|
+
//
|
|
34
|
+
// Discriminates on jeAccruedScheduleKey, which only JEAccruedSchedule carries. jeScheduleType
|
|
35
|
+
// cannot be used: it is a data field present on BOTH row shapes, so an accrued-typed row that
|
|
36
|
+
// arrived through the prepaid/fixed DTO (failed_je_postings, legacy fetches) would be
|
|
37
|
+
// misclassified and then crash on recurringAmount / jeAccruedScheduleKey.
|
|
38
|
+
export function isAccruedScheduleRow(schedule) {
|
|
39
|
+
return 'jeAccruedScheduleKey' in schedule;
|
|
40
|
+
}
|
|
41
|
+
function getScheduleAmount(schedule) {
|
|
42
|
+
if (isAccruedScheduleRow(schedule)) {
|
|
43
|
+
return schedule.recurringAmount.amount;
|
|
44
|
+
}
|
|
45
|
+
return schedule.baseTransaction.amount.amount;
|
|
46
|
+
}
|
|
15
47
|
function getJEScheduleSortAccessor(sortKey) {
|
|
16
48
|
switch (sortKey) {
|
|
17
49
|
case 'vendor':
|
|
@@ -23,7 +55,9 @@ function getJEScheduleSortAccessor(sortKey) {
|
|
|
23
55
|
case 'depCategory':
|
|
24
56
|
return (s) => s.jeCredit.account?.accountName?.toLowerCase();
|
|
25
57
|
case 'scheduleCategory':
|
|
26
|
-
return (s) => s
|
|
58
|
+
return (s) => isAccruedScheduleRow(s)
|
|
59
|
+
? undefined
|
|
60
|
+
: s.baseTransaction.account?.accountName?.toLowerCase();
|
|
27
61
|
case 'startMonth':
|
|
28
62
|
return (s) => s.startDate?.valueOf();
|
|
29
63
|
case 'type':
|
|
@@ -51,11 +85,13 @@ function getJEScheduleSortAccessor(sortKey) {
|
|
|
51
85
|
return s.balanceAsOfToday?.amount ?? null;
|
|
52
86
|
};
|
|
53
87
|
case 'totalAmount':
|
|
54
|
-
return (s) => s
|
|
88
|
+
return (s) => getScheduleAmount(s);
|
|
55
89
|
case 'transactionDate':
|
|
56
|
-
return (s) => s.
|
|
90
|
+
return (s) => s.createTime?.valueOf();
|
|
57
91
|
case 'memo':
|
|
58
|
-
return (s) => s
|
|
92
|
+
return (s) => isAccruedScheduleRow(s)
|
|
93
|
+
? s.memo.toLowerCase()
|
|
94
|
+
: s.baseTransaction.memo?.toLowerCase();
|
|
59
95
|
default:
|
|
60
96
|
return (s) => s.startDate?.valueOf();
|
|
61
97
|
}
|
|
@@ -67,9 +103,14 @@ function sortJEScheduledTransactions(transactions, sortKey, sortOrder) {
|
|
|
67
103
|
export function getExpenseAutomationJESchedulesView(state) {
|
|
68
104
|
const jeScheduledTransaction = [];
|
|
69
105
|
const failedJeScheduledTransaction = [];
|
|
106
|
+
const accruedSchedules = [];
|
|
70
107
|
const { accountState, accountListState, classState, classListState, expenseAutomationJESchedulesViewState, expenseAutomationViewState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
|
|
71
|
-
const {
|
|
108
|
+
const { jeScheduleTransactionKeysByPeriodAndTab, jeScheduleAccruedKeysByPeriodAndTab, failedJeScheduleTransactionKeysByPeriodAndTab, jeScheduleLocalDataById, postStatusById, ignoreStatusById, uiState, error, fetchState, refreshStatus, newSchedule, } = expenseAutomationJESchedulesViewState;
|
|
72
109
|
const accountSettingsView = getJEAccountSettingsView(state);
|
|
110
|
+
const newScheduleView = newSchedule.selectedDetailScheduleType === 'fixed_assets' ||
|
|
111
|
+
newSchedule.selectedDetailScheduleType === 'prepaid_expenses'
|
|
112
|
+
? getNewScheduleView(state, newSchedule.selectedDetailScheduleType)
|
|
113
|
+
: undefined;
|
|
73
114
|
const currentTenant = getCurrentTenant(state);
|
|
74
115
|
const { selectedPeriodByTenantId } = expenseAutomationViewState;
|
|
75
116
|
const selectedPeriod = selectedPeriodByTenantId[currentTenant.tenantId];
|
|
@@ -97,27 +138,39 @@ export function getExpenseAutomationJESchedulesView(state) {
|
|
|
97
138
|
const filteredDepAccountListNestedAccountHierarchy = filterNestedAccountHierarchyById(depAccountListNestedAccountHierarchy, filteredDepAccounts.map((account) => account.accountId));
|
|
98
139
|
const classListNestedAccountHierarchy = getNestedClassListHierarchy(classListState);
|
|
99
140
|
const monthYearPeriodId = selectedPeriod != null ? toMonthYearPeriodId(selectedPeriod) : null;
|
|
100
|
-
const
|
|
101
|
-
?
|
|
141
|
+
const selectedPeriodTabKey = monthYearPeriodId != null
|
|
142
|
+
? getJESchedulePeriodTabKey(uiState.selectedPageTab, monthYearPeriodId)
|
|
102
143
|
: null;
|
|
103
|
-
const
|
|
104
|
-
?
|
|
105
|
-
:
|
|
106
|
-
const
|
|
107
|
-
|
|
144
|
+
const jeScheduleTransactionKeysForSelectedTab = selectedPeriodTabKey != null
|
|
145
|
+
? jeScheduleTransactionKeysByPeriodAndTab[selectedPeriodTabKey]
|
|
146
|
+
: undefined;
|
|
147
|
+
const failedJeScheduleTransactionKeysForSelectedTab = selectedPeriodTabKey != null
|
|
148
|
+
? failedJeScheduleTransactionKeysByPeriodAndTab[selectedPeriodTabKey]
|
|
149
|
+
: undefined;
|
|
150
|
+
// Distinguishes "this tab has never been fetched for this period" from "fetched and genuinely
|
|
151
|
+
// empty". Without it the page cannot tell a first visit from a real empty state, and renders an
|
|
152
|
+
// empty table where it should render a skeleton.
|
|
153
|
+
const hasLoadedForSelectedTab = jeScheduleTransactionKeysForSelectedTab != null;
|
|
154
|
+
const jeScheduleTransactionKeys = jeScheduleTransactionKeysForSelectedTab ?? [];
|
|
155
|
+
const failedJeScheduleTransactionKeys = failedJeScheduleTransactionKeysForSelectedTab ?? [];
|
|
156
|
+
const jeScheduleAccruedKeys = (selectedPeriodTabKey != null
|
|
157
|
+
? jeScheduleAccruedKeysByPeriodAndTab[selectedPeriodTabKey]
|
|
158
|
+
: undefined) ?? [];
|
|
108
159
|
jeScheduleTransactionKeys.forEach((jeScheduleKey) => {
|
|
109
160
|
const jeScheduleDetails = getJEScheduledTransactionByJEScheduleTransactionKey(jeScheduleKey, state);
|
|
110
161
|
jeScheduledTransaction.push(jeScheduleDetails);
|
|
111
162
|
});
|
|
163
|
+
jeScheduleAccruedKeys.forEach((jeAccruedScheduleKey) => {
|
|
164
|
+
const accruedScheduleDetails = getJEAccruedScheduleByJEScheduleKey(jeAccruedScheduleKey, state);
|
|
165
|
+
if (accruedScheduleDetails != null) {
|
|
166
|
+
accruedSchedules.push(accruedScheduleDetails);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
112
169
|
failedJeScheduleTransactionKeys.forEach((jeScheduleKey) => {
|
|
113
170
|
const jeScheduleDetails = getJEScheduledTransactionByJEScheduleTransactionKey(jeScheduleKey, state);
|
|
114
171
|
failedJeScheduledTransaction.push(jeScheduleDetails);
|
|
115
172
|
});
|
|
116
|
-
const { sortKey, sortOrder } = uiState;
|
|
117
|
-
const draftSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'draft'), sortKey, sortOrder);
|
|
118
|
-
const ongoingSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'ongoing'), sortKey, sortOrder);
|
|
119
|
-
const completedSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'completed' ||
|
|
120
|
-
schedule.status.code === 'marked_as_completed'), sortKey, sortOrder);
|
|
173
|
+
const { sortKey, sortOrder, selectedJETypeFilter } = uiState;
|
|
121
174
|
const unsortedResolveSchedules = [];
|
|
122
175
|
failedJeScheduledTransaction.forEach((schedule) => {
|
|
123
176
|
schedule.scheduledJournalEntry.forEach((journalEntry) => {
|
|
@@ -129,10 +182,73 @@ export function getExpenseAutomationJESchedulesView(state) {
|
|
|
129
182
|
});
|
|
130
183
|
const resolveSchedules = sortJEScheduledTransactions(unsortedResolveSchedules, sortKey, sortOrder);
|
|
131
184
|
const validResolveForPending = unsortedResolveSchedules.filter((r) => jeScheduleLocalDataById[r.scheduledJournalEntry.scheduledJournalEntryID] != null);
|
|
185
|
+
const matchesTypeFilter = (schedule) => selectedJETypeFilter == null ||
|
|
186
|
+
schedule.jeScheduleType === selectedJETypeFilter;
|
|
187
|
+
const isCreatedStatus = (schedule) => schedule.status.code === 'ongoing' ||
|
|
188
|
+
schedule.status.code === 'completed' ||
|
|
189
|
+
schedule.status.code === 'marked_as_completed';
|
|
190
|
+
const allScheduleRows = [
|
|
191
|
+
...jeScheduledTransaction,
|
|
192
|
+
...accruedSchedules,
|
|
193
|
+
];
|
|
132
194
|
const pendingReviewSchedules = sortJEScheduledTransactions([
|
|
133
|
-
...
|
|
195
|
+
...allScheduleRows.filter((schedule) => schedule.status.code === 'draft' && matchesTypeFilter(schedule)),
|
|
134
196
|
...validResolveForPending,
|
|
135
197
|
], sortKey, sortOrder);
|
|
198
|
+
const createdSchedules = sortJEScheduledTransactions(allScheduleRows.filter((schedule) => isCreatedStatus(schedule) && matchesTypeFilter(schedule)), sortKey, sortOrder);
|
|
199
|
+
// KPI summary — derived from unfiltered schedules so counts are always accurate
|
|
200
|
+
const allSchedules = allScheduleRows;
|
|
201
|
+
const sumAmount = (schedules) => schedules.reduce((total, schedule) => total + (getScheduleAmount(schedule) ?? 0), 0);
|
|
202
|
+
const pendingReviewTotal = sumAmount(pendingReviewSchedules);
|
|
203
|
+
const createdTotal = sumAmount(createdSchedules);
|
|
204
|
+
const aiAccountantSchedules = allSchedules.filter(isAiAccountantSchedule);
|
|
205
|
+
const manualSchedules = allSchedules.filter((schedule) => !isAiAccountantSchedule(schedule));
|
|
206
|
+
const pendingUnfiltered = allSchedules.filter((schedule) => schedule.status.code === 'draft');
|
|
207
|
+
const kpiSummary = {
|
|
208
|
+
aiAccountant: {
|
|
209
|
+
count: aiAccountantSchedules.length,
|
|
210
|
+
amount: sumAmount(aiAccountantSchedules),
|
|
211
|
+
},
|
|
212
|
+
manual: {
|
|
213
|
+
count: manualSchedules.length,
|
|
214
|
+
amount: sumAmount(manualSchedules),
|
|
215
|
+
},
|
|
216
|
+
needsReview: {
|
|
217
|
+
count: pendingUnfiltered.length,
|
|
218
|
+
amount: sumAmount(pendingUnfiltered),
|
|
219
|
+
},
|
|
220
|
+
total: {
|
|
221
|
+
count: allSchedules.length,
|
|
222
|
+
amount: sumAmount(allSchedules),
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
// Each tab's badge counts from that tab's own bucket. A tab that has not been fetched yet
|
|
226
|
+
// reports undefined rather than 0, so the UI can hide the badge instead of asserting "none".
|
|
227
|
+
const scheduleCountsByTab = {};
|
|
228
|
+
ALL_JE_PAGE_TABS.forEach((pageTab) => {
|
|
229
|
+
const periodTabKey = monthYearPeriodId != null
|
|
230
|
+
? getJESchedulePeriodTabKey(pageTab, monthYearPeriodId)
|
|
231
|
+
: null;
|
|
232
|
+
const transactionKeys = periodTabKey != null
|
|
233
|
+
? jeScheduleTransactionKeysByPeriodAndTab[periodTabKey]
|
|
234
|
+
: undefined;
|
|
235
|
+
if (transactionKeys == null) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const accruedKeys = (periodTabKey != null
|
|
239
|
+
? jeScheduleAccruedKeysByPeriodAndTab[periodTabKey]
|
|
240
|
+
: undefined) ?? [];
|
|
241
|
+
const rowsForTab = [
|
|
242
|
+
...transactionKeys.map((transactionKey) => getJEScheduledTransactionByJEScheduleTransactionKey(transactionKey, state)),
|
|
243
|
+
...accruedKeys
|
|
244
|
+
.map((accruedKey) => getJEAccruedScheduleByJEScheduleKey(accruedKey, state))
|
|
245
|
+
.filter((accruedRow) => accruedRow != null),
|
|
246
|
+
];
|
|
247
|
+
scheduleCountsByTab[pageTab] =
|
|
248
|
+
pageTab === 'existing_schedules'
|
|
249
|
+
? rowsForTab.filter((row) => row.status.code === 'ongoing').length
|
|
250
|
+
: rowsForTab.filter((row) => row.status.code === 'draft' || isCreatedStatus(row)).length;
|
|
251
|
+
});
|
|
136
252
|
const allSteps = monthYearPeriodId != null
|
|
137
253
|
? getAllSteps(monthEndCloseChecksState, monthYearPeriodId, currentTenant.tenantId)
|
|
138
254
|
: [];
|
|
@@ -145,6 +261,22 @@ export function getExpenseAutomationJESchedulesView(state) {
|
|
|
145
261
|
{ fetchState, error },
|
|
146
262
|
monthEndFetchState,
|
|
147
263
|
]);
|
|
264
|
+
const countByType = (schedules) => {
|
|
265
|
+
const counts = ALL_SCHEDULES_TYPES.reduce((acc, type) => ({ ...acc, [type]: 0 }), {});
|
|
266
|
+
schedules.forEach((schedule) => {
|
|
267
|
+
if (schedule.jeScheduleType != null) {
|
|
268
|
+
counts[schedule.jeScheduleType] += 1;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
return counts;
|
|
272
|
+
};
|
|
273
|
+
const jeScheduleTypeCounts = {
|
|
274
|
+
active: countByType(allSchedules.filter((schedule) => schedule.status.code === 'draft' ||
|
|
275
|
+
schedule.status.code === 'ongoing' ||
|
|
276
|
+
schedule.status.code === 'completed' ||
|
|
277
|
+
schedule.status.code === 'marked_as_completed')),
|
|
278
|
+
ongoing: countByType(allSchedules.filter((schedule) => schedule.status.code === 'ongoing')),
|
|
279
|
+
};
|
|
148
280
|
return {
|
|
149
281
|
uncategorizedAccounts,
|
|
150
282
|
allDepreciationAccountList: filteredDepAccounts,
|
|
@@ -153,12 +285,17 @@ export function getExpenseAutomationJESchedulesView(state) {
|
|
|
153
285
|
allAccountList: filteredAccounts,
|
|
154
286
|
accountListNestedAccountHierarchy,
|
|
155
287
|
allClassList: allClasses,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
288
|
+
createdSchedules,
|
|
289
|
+
createdTotal,
|
|
290
|
+
hasLoadedForSelectedTab,
|
|
291
|
+
jeScheduleTypeCounts,
|
|
292
|
+
scheduleCountsByTab,
|
|
293
|
+
kpiSummary,
|
|
159
294
|
pendingReviewSchedules,
|
|
295
|
+
pendingReviewTotal,
|
|
160
296
|
resolveSchedules: resolveSchedules,
|
|
161
297
|
accountSettingsView,
|
|
298
|
+
newScheduleView,
|
|
162
299
|
postStatusById: postStatusById,
|
|
163
300
|
ignoreStatusById: ignoreStatusById,
|
|
164
301
|
fetchState: reducedFetchStatus.fetchState,
|
|
@@ -16,9 +16,8 @@ const JE_SCHEDULE_SORT_KEYS = [
|
|
|
16
16
|
'memo',
|
|
17
17
|
];
|
|
18
18
|
export const toJEScheduleSortKey = (v) => stringToUnion(v, JE_SCHEDULE_SORT_KEYS);
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
export const toJEScheduleMainTab = (v) => stringToUnion(v, JE_SCHEDULE_MAIN_TABS);
|
|
19
|
+
const JE_PAGE_TABS = ['schedules', 'existing_schedules'];
|
|
20
|
+
export const toJEPageTab = (v) => stringToUnion(v, JE_PAGE_TABS);
|
|
21
|
+
export const ALL_JE_PAGE_TABS = JE_PAGE_TABS;
|
|
22
|
+
export const getJESchedulePeriodTabKey = (selectedPageTab, monthYearPeriodId) => `${selectedPageTab}_${monthYearPeriodId}`;
|
|
23
|
+
export const MIN_NEW_SCHEDULE_SEARCH_LENGTH = 2;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { from } from 'rxjs';
|
|
2
2
|
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
3
|
import { updateInvoicingCreditNotes } from '../../../entity/invoicing/creditNote/creditNoteReducer';
|
|
4
|
+
import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
4
5
|
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
5
6
|
import { localDataToCreateCreditNotePayload, } from './createCreditNotePayload';
|
|
6
7
|
import { createCreditNoteFailure, createCreditNoteSuccess, submitCreateCreditNote, } from './createCreditNoteReducer';
|
|
@@ -15,9 +16,28 @@ export const createCreditNoteEpic = (actions$, state$, zeniAPI) => actions$.pipe
|
|
|
15
16
|
return [
|
|
16
17
|
updateInvoicingCreditNotes([response.data], 'merge'),
|
|
17
18
|
createCreditNoteSuccess({ creditNoteId: response.data.id }),
|
|
19
|
+
openSnackbar({
|
|
20
|
+
messageSection: 'invoicing_credit_note_saved',
|
|
21
|
+
messageText: 'success',
|
|
22
|
+
type: 'success',
|
|
23
|
+
}),
|
|
18
24
|
];
|
|
19
25
|
}
|
|
20
|
-
return
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
return from([
|
|
27
|
+
createCreditNoteFailure(response.status),
|
|
28
|
+
openSnackbar({
|
|
29
|
+
messageSection: 'invoicing_credit_note_saved',
|
|
30
|
+
messageText: 'failed',
|
|
31
|
+
type: 'error',
|
|
32
|
+
}),
|
|
33
|
+
]);
|
|
34
|
+
}), catchError((error) => from([
|
|
35
|
+
createCreditNoteFailure(createZeniAPIStatus('Unexpected Error', 'Create credit note REST API call errored out ' +
|
|
36
|
+
JSON.stringify(error))),
|
|
37
|
+
openSnackbar({
|
|
38
|
+
messageSection: 'invoicing_credit_note_saved',
|
|
39
|
+
messageText: 'failed',
|
|
40
|
+
type: 'error',
|
|
41
|
+
}),
|
|
42
|
+
])));
|
|
23
43
|
}));
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { from } from 'rxjs';
|
|
2
2
|
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
3
|
import { updateAddresses } from '../../../entity/address/addressReducer';
|
|
4
4
|
import { extractInvoicingInvoiceAddresses } from '../../../entity/invoicing/invoice/invoicePayload';
|
|
5
5
|
import { updateInvoices } from '../../../entity/invoicing/invoice/invoiceReducer';
|
|
6
|
+
import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
6
7
|
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
7
8
|
import { addInvoiceToList } from '../invoiceList/invoiceListReducer';
|
|
8
9
|
import { createInvoice, createInvoiceFailure, createInvoiceSuccess, } from './createInvoiceReducer';
|
|
@@ -22,9 +23,28 @@ export const createInvoiceEpic = (actions$, _state$, zeniAPI) => actions$.pipe(f
|
|
|
22
23
|
status: response.data.status,
|
|
23
24
|
}),
|
|
24
25
|
createInvoiceSuccess({ invoiceId: response.data.id }),
|
|
26
|
+
openSnackbar({
|
|
27
|
+
messageSection: 'invoicing_invoice_saved',
|
|
28
|
+
messageText: 'success',
|
|
29
|
+
type: 'success',
|
|
30
|
+
}),
|
|
25
31
|
];
|
|
26
32
|
}
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
return from([
|
|
34
|
+
createInvoiceFailure(response.status),
|
|
35
|
+
openSnackbar({
|
|
36
|
+
messageSection: 'invoicing_invoice_saved',
|
|
37
|
+
messageText: 'failed',
|
|
38
|
+
type: 'error',
|
|
39
|
+
}),
|
|
40
|
+
]);
|
|
41
|
+
}), catchError((error) => from([
|
|
42
|
+
createInvoiceFailure(createZeniAPIStatus('Unexpected Error', 'Create invoice REST API call errored out ' +
|
|
43
|
+
JSON.stringify(error))),
|
|
44
|
+
openSnackbar({
|
|
45
|
+
messageSection: 'invoicing_invoice_saved',
|
|
46
|
+
messageText: 'failed',
|
|
47
|
+
type: 'error',
|
|
48
|
+
}),
|
|
49
|
+
])));
|
|
30
50
|
}));
|
|
@@ -1,25 +1,52 @@
|
|
|
1
|
+
import { getAddressByAddressId } from '../../../entity/address/addressSelector';
|
|
1
2
|
import { getInvoicingCatalogListView, getInvoicingCatalogPrices, } from '../invoicingCatalogListView/invoicingCatalogListViewSelector';
|
|
2
3
|
import { getInvoicingCustomerListView } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
|
|
3
4
|
import { getInvoicingSettings } from '../settingsView/settingsViewSelector';
|
|
5
|
+
/**
|
|
6
|
+
* Display name for a customer option: company, else the full name, else the
|
|
7
|
+
* email, else the raw id.
|
|
8
|
+
*/
|
|
9
|
+
const customerDisplayName = (customer) => {
|
|
10
|
+
const fullName = [customer.firstName, customer.lastName]
|
|
11
|
+
.filter((part) => part != null && part.trim() !== '')
|
|
12
|
+
.join(' ')
|
|
13
|
+
.trim();
|
|
14
|
+
const company = customer.company?.trim() ?? '';
|
|
15
|
+
const email = customer.email?.trim() ?? '';
|
|
16
|
+
return company !== ''
|
|
17
|
+
? company
|
|
18
|
+
: fullName !== ''
|
|
19
|
+
? fullName
|
|
20
|
+
: email !== ''
|
|
21
|
+
? email
|
|
22
|
+
: customer.id;
|
|
23
|
+
};
|
|
4
24
|
/**
|
|
5
25
|
* Single bundled view for the create-invoice screen — web-ui consumes only this.
|
|
6
26
|
* Carries the form draft (`localData`), the option sources the form needs
|
|
7
|
-
* (
|
|
8
|
-
* state/error, and the id of the invoice last created
|
|
9
|
-
* navigate on success).
|
|
10
|
-
* option shapes.
|
|
27
|
+
* (customer picker rows, catalog plan names, products, settings), the
|
|
28
|
+
* create-invoice fetch state/error, and the id of the invoice last created
|
|
29
|
+
* (`savedInvoiceId`, to navigate on success).
|
|
11
30
|
*/
|
|
12
31
|
export const getCreateInvoiceFormView = (state) => {
|
|
13
32
|
const createInvoiceState = state.invoicingCreateInvoiceState;
|
|
14
33
|
const catalogListView = getInvoicingCatalogListView(state);
|
|
15
|
-
const plans = getInvoicingCatalogPrices(catalogListView.plans);
|
|
16
34
|
return {
|
|
17
|
-
customers: getInvoicingCustomerListView(state).customers
|
|
35
|
+
customers: getInvoicingCustomerListView(state).customers.map((customer) => ({
|
|
36
|
+
billingAddress: customer.billingAddressId != null
|
|
37
|
+
? getAddressByAddressId(state.addressState, customer.billingAddressId)
|
|
38
|
+
: undefined,
|
|
39
|
+
email: customer.email,
|
|
40
|
+
id: customer.id,
|
|
41
|
+
name: customerDisplayName(customer),
|
|
42
|
+
})),
|
|
18
43
|
localData: createInvoiceState.formDraft,
|
|
19
|
-
planNames: plans
|
|
20
|
-
.
|
|
21
|
-
.
|
|
22
|
-
|
|
44
|
+
planNames: catalogListView.plans.flatMap((plan) => (plan.subFamilies ?? [])
|
|
45
|
+
.filter((subFamily) => (subFamily.name ?? '') !== '')
|
|
46
|
+
.map((subFamily) => ({
|
|
47
|
+
planName: plan.name ?? '',
|
|
48
|
+
subFamilyName: subFamily.name ?? '',
|
|
49
|
+
}))),
|
|
23
50
|
products: getInvoicingCatalogPrices(catalogListView.products),
|
|
24
51
|
savedInvoiceId: createInvoiceState.createdInvoiceId,
|
|
25
52
|
settings: getInvoicingSettings(state),
|
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { from } from 'rxjs';
|
|
2
2
|
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
3
|
import { updateInvoicingCoupons } from '../../../entity/invoicing/coupon/couponReducer';
|
|
4
|
+
import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
4
5
|
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
5
6
|
import { runInvoicingDiscountAction, runInvoicingDiscountActionFailure, runInvoicingDiscountActionSuccess, } from './discountActionReducer';
|
|
7
|
+
/** Snackbar copy per action kind; unlisted kinds run without one. */
|
|
8
|
+
const SNACKBAR_SECTION_BY_KIND = {
|
|
9
|
+
archive: 'invoicing_discount_archive',
|
|
10
|
+
delete: 'invoicing_discount_delete',
|
|
11
|
+
unarchive: 'invoicing_discount_unarchive',
|
|
12
|
+
};
|
|
6
13
|
export const runInvoicingDiscountActionEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(runInvoicingDiscountAction.match), mergeMap((action) => {
|
|
7
14
|
const { action: kind, id } = action.payload;
|
|
8
15
|
const requestBody = { action: kind };
|
|
16
|
+
const snackbarSection = SNACKBAR_SECTION_BY_KIND[kind];
|
|
17
|
+
const snackbar = (messageText) => snackbarSection == null
|
|
18
|
+
? []
|
|
19
|
+
: [
|
|
20
|
+
openSnackbar({
|
|
21
|
+
messageSection: snackbarSection,
|
|
22
|
+
messageText,
|
|
23
|
+
type: messageText === 'success' ? 'success' : 'error',
|
|
24
|
+
}),
|
|
25
|
+
];
|
|
9
26
|
const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/discounts/${encodeURIComponent(id)}/actions`;
|
|
10
27
|
return zeniAPI
|
|
11
28
|
.postAndGetJSON(apiUrl, requestBody)
|
|
@@ -14,15 +31,22 @@ export const runInvoicingDiscountActionEpic = (actions$, _state$, zeniAPI) => ac
|
|
|
14
31
|
return [
|
|
15
32
|
updateInvoicingCoupons([response.data], 'merge'),
|
|
16
33
|
runInvoicingDiscountActionSuccess({ id }),
|
|
34
|
+
...snackbar('success'),
|
|
17
35
|
];
|
|
18
36
|
}
|
|
19
|
-
return
|
|
20
|
-
|
|
37
|
+
return from([
|
|
38
|
+
runInvoicingDiscountActionFailure({
|
|
39
|
+
error: response.status,
|
|
40
|
+
id,
|
|
41
|
+
}),
|
|
42
|
+
...snackbar('failed'),
|
|
43
|
+
]);
|
|
44
|
+
}), catchError((error) => from([
|
|
45
|
+
runInvoicingDiscountActionFailure({
|
|
46
|
+
error: createZeniAPIStatus('Unexpected Error', 'Discount action REST API call errored out ' +
|
|
47
|
+
JSON.stringify(error)),
|
|
21
48
|
id,
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
JSON.stringify(error)),
|
|
26
|
-
id,
|
|
27
|
-
}))));
|
|
49
|
+
}),
|
|
50
|
+
...snackbar('failed'),
|
|
51
|
+
])));
|
|
28
52
|
}));
|
|
@@ -1,10 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { from } from 'rxjs';
|
|
2
2
|
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
3
|
import { updateInvoicingDunningCases } from '../../../entity/invoicing/dunningCase/dunningCaseReducer';
|
|
4
|
+
import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
4
5
|
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
5
6
|
import { runInvoicingDunningAction, runInvoicingDunningActionFailure, runInvoicingDunningActionSuccess, } from './dunningActionReducer';
|
|
7
|
+
/**
|
|
8
|
+
* Snackbar copy is per action kind; anything not listed here runs without one.
|
|
9
|
+
*/
|
|
10
|
+
const SNACKBAR_SECTION_BY_KIND = {
|
|
11
|
+
cancel: 'invoicing_dunning_cancel',
|
|
12
|
+
resolve: 'invoicing_dunning_case_resolved',
|
|
13
|
+
retry: 'invoicing_dunning_retry',
|
|
14
|
+
};
|
|
6
15
|
export const runInvoicingDunningActionEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(runInvoicingDunningAction.match), mergeMap((action) => {
|
|
7
16
|
const { action: kind, body, id } = action.payload;
|
|
17
|
+
const snackbarSection = SNACKBAR_SECTION_BY_KIND[kind];
|
|
18
|
+
const snackbar = (messageText) => snackbarSection == null
|
|
19
|
+
? []
|
|
20
|
+
: [
|
|
21
|
+
openSnackbar({
|
|
22
|
+
messageSection: snackbarSection,
|
|
23
|
+
messageText,
|
|
24
|
+
type: messageText === 'success' ? 'success' : 'error',
|
|
25
|
+
}),
|
|
26
|
+
];
|
|
8
27
|
const apiUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/dunning/${encodeURIComponent(id)}/${kind}`;
|
|
9
28
|
return zeniAPI
|
|
10
29
|
.postAndGetJSON(apiUrl, (body ?? {}))
|
|
@@ -13,15 +32,22 @@ export const runInvoicingDunningActionEpic = (actions$, _state$, zeniAPI) => act
|
|
|
13
32
|
return [
|
|
14
33
|
updateInvoicingDunningCases([response.data], 'merge'),
|
|
15
34
|
runInvoicingDunningActionSuccess({ id }),
|
|
35
|
+
...snackbar('success'),
|
|
16
36
|
];
|
|
17
37
|
}
|
|
18
|
-
return
|
|
19
|
-
|
|
38
|
+
return from([
|
|
39
|
+
runInvoicingDunningActionFailure({
|
|
40
|
+
error: response.status,
|
|
41
|
+
id,
|
|
42
|
+
}),
|
|
43
|
+
...snackbar('failed'),
|
|
44
|
+
]);
|
|
45
|
+
}), catchError((error) => from([
|
|
46
|
+
runInvoicingDunningActionFailure({
|
|
47
|
+
error: createZeniAPIStatus('Unexpected Error', 'Dunning action REST API call errored out ' +
|
|
48
|
+
JSON.stringify(error)),
|
|
20
49
|
id,
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
JSON.stringify(error)),
|
|
25
|
-
id,
|
|
26
|
-
}))));
|
|
50
|
+
}),
|
|
51
|
+
...snackbar('failed'),
|
|
52
|
+
])));
|
|
27
53
|
}));
|
package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/catalogDetailEditPayload.js
CHANGED
|
@@ -116,17 +116,21 @@ export const catalogDetailEditValuesToSavePayload = (values, itemType = 'plan',
|
|
|
116
116
|
export const createSubFamilyFormToPayload = (itemId, itemType, values) => {
|
|
117
117
|
const name = values.name.trim();
|
|
118
118
|
const priceCode = trimOrUndefined(values.priceCode) ?? toProductCode(name);
|
|
119
|
+
const tiered = isTieredCatalogPricingModel(values.pricingModel);
|
|
119
120
|
const payload = {
|
|
120
121
|
currency_code: 'USD',
|
|
121
122
|
free_quantity: Number(values.freeQuantity) || 0,
|
|
122
123
|
is_taxable: true,
|
|
123
124
|
item_id: itemId,
|
|
124
125
|
name,
|
|
125
|
-
price: values.priceDollars?.amount ?? 0,
|
|
126
|
+
price: tiered ? 0 : (values.priceDollars?.amount ?? 0),
|
|
126
127
|
price_code: priceCode,
|
|
127
128
|
pricing_model: values.pricingModel || 'flat_fee',
|
|
128
129
|
show_description_in_invoices: true,
|
|
129
130
|
};
|
|
131
|
+
if (tiered) {
|
|
132
|
+
payload.tiers = (values.tiers ?? []).map(tierToPayload);
|
|
133
|
+
}
|
|
130
134
|
const description = trimOrUndefined(values.description);
|
|
131
135
|
if (description != null) {
|
|
132
136
|
payload.description = description;
|
package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { forkJoin,
|
|
1
|
+
import { forkJoin, from } from 'rxjs';
|
|
2
2
|
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
3
|
import { mergeInvoicingPlanSubFamily, updateInvoicingPlans, } from '../../../entity/invoicing/plan/planReducer';
|
|
4
4
|
import { updateInvoicingProducts } from '../../../entity/invoicing/product/productReducer';
|
|
5
|
+
import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
5
6
|
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
6
7
|
import { addInvoicingCatalogItem } from '../invoicingCatalogListView/invoicingCatalogListViewReducer';
|
|
7
8
|
import { localDataToSaveInvoicingCatalogItemPayload, } from './editInvoicingCatalogItemDetailViewPayload';
|
|
@@ -38,7 +39,14 @@ export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actio
|
|
|
38
39
|
const failedProductResponse = productResponses.find((productResponse) => !isSuccessResponse(productResponse) ||
|
|
39
40
|
productResponse.data == null);
|
|
40
41
|
if (failedProductResponse != null) {
|
|
41
|
-
return
|
|
42
|
+
return from([
|
|
43
|
+
submitInvoicingCatalogItemFormFailure(failedProductResponse.status),
|
|
44
|
+
openSnackbar({
|
|
45
|
+
messageSection: 'invoicing_catalog_item_saved',
|
|
46
|
+
messageText: 'failed',
|
|
47
|
+
type: 'error',
|
|
48
|
+
}),
|
|
49
|
+
]);
|
|
42
50
|
}
|
|
43
51
|
const savedProducts = productResponses.flatMap((productResponse) => productResponse.data != null ? [productResponse.data] : []);
|
|
44
52
|
const resultActions = [
|
|
@@ -65,6 +73,11 @@ export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actio
|
|
|
65
73
|
: 'plan';
|
|
66
74
|
resultActions.push(addInvoicingCatalogItem({ id: savedItem.id, type: itemType }));
|
|
67
75
|
}
|
|
76
|
+
resultActions.push(openSnackbar({
|
|
77
|
+
messageSection: 'invoicing_catalog_item_saved',
|
|
78
|
+
messageText: 'success',
|
|
79
|
+
type: 'success',
|
|
80
|
+
}));
|
|
68
81
|
return resultActions;
|
|
69
82
|
};
|
|
70
83
|
const productUpdates = saveInvoicingCatalogItem.match(action)
|
|
@@ -75,7 +88,21 @@ export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actio
|
|
|
75
88
|
}
|
|
76
89
|
return forkJoin(productUpdates.map((productUpdate) => zeniAPI.putAndGetJSON(`${baseUrl}/catalog/products/${encodeURIComponent(productUpdate.id)}`, productUpdate.data))).pipe(mergeMap(completeSave));
|
|
77
90
|
}
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
|
|
91
|
+
return from([
|
|
92
|
+
submitInvoicingCatalogItemFormFailure(response.status),
|
|
93
|
+
openSnackbar({
|
|
94
|
+
messageSection: 'invoicing_catalog_item_saved',
|
|
95
|
+
messageText: 'failed',
|
|
96
|
+
type: 'error',
|
|
97
|
+
}),
|
|
98
|
+
]);
|
|
99
|
+
}), catchError((error) => from([
|
|
100
|
+
submitInvoicingCatalogItemFormFailure(createZeniAPIStatus('Unexpected Error', 'Save catalog item REST API call errored out ' +
|
|
101
|
+
JSON.stringify(error))),
|
|
102
|
+
openSnackbar({
|
|
103
|
+
messageSection: 'invoicing_catalog_item_saved',
|
|
104
|
+
messageText: 'failed',
|
|
105
|
+
type: 'error',
|
|
106
|
+
}),
|
|
107
|
+
])));
|
|
81
108
|
}));
|