@zeniai/client-epic-state 5.1.52-beta0RD → 5.1.53-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/esm/view/transactionDetail/epics/markTransactionAsNotMiscategorizedEpic.js +89 -39
- package/lib/esm/view/transactionDetail/epics/updateTransactionDetailEpic.js +57 -64
- package/lib/esm/view/transactionDetail/transactionDetailLocalDataHelper.js +4 -1
- package/lib/esm/view/transactionDetail/transactionDetailReducer.js +2 -1
- package/lib/view/transactionDetail/epics/markTransactionAsNotMiscategorizedEpic.js +88 -38
- package/lib/view/transactionDetail/epics/updateTransactionDetailEpic.js +55 -62
- package/lib/view/transactionDetail/transactionDetailLocalDataHelper.js +4 -1
- package/lib/view/transactionDetail/transactionDetailReducer.d.ts +2 -1
- package/lib/view/transactionDetail/transactionDetailReducer.js +2 -1
- package/package.json +1 -1
|
@@ -4,11 +4,99 @@ import { toMonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
|
|
|
4
4
|
import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
5
5
|
import { getCurrentTenant } from '../../../entity/tenant/tenantSelector';
|
|
6
6
|
import { removeTransactionCategorization, updateTransactionIsMiscategorized, } from '../../../entity/transaction/transactionReducer';
|
|
7
|
-
import {
|
|
7
|
+
import { getSupportedTransactionById } from '../../../entity/transaction/transactionSelector';
|
|
8
|
+
import { createZeniAPIStatus, isSuccessResponse, isSuccessStatus, } from '../../../responsePayload';
|
|
8
9
|
import { updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateTotalCountForTransactionCategorization, } from '../../expenseAutomationView/reducers/transactionsViewReducer';
|
|
9
10
|
import { markTransactionAsNotMiscategorized, updateStatusConsolidatedSaveTransactionDetail, updateStatusForTransactionNotMiscategorizedUpdate, } from '../transactionDetailReducer';
|
|
10
11
|
import { getTransactionDetailKey } from '../transactionDetailState';
|
|
11
12
|
export const markTransactionAsNotMiscategorizedEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(markTransactionAsNotMiscategorized.match), mergeMap((action) => {
|
|
13
|
+
if (action.payload.isExpenseAutomationEnabled === true) {
|
|
14
|
+
const transactionKey = getTransactionDetailKey(action.payload.transactionId);
|
|
15
|
+
const transaction = getSupportedTransactionById(state$.value.transactionState, transactionKey);
|
|
16
|
+
const lineIds = (action.payload.lineIds?.length ?? 0) > 0
|
|
17
|
+
? action.payload.lineIds
|
|
18
|
+
: (transaction?.lines ?? []).map((line) => line.id);
|
|
19
|
+
return zeniAPI
|
|
20
|
+
.putAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expense-automation/mark-as-reviewed`, {
|
|
21
|
+
transactions_reviewed: [
|
|
22
|
+
{
|
|
23
|
+
transaction_id: action.payload.transactionId.id,
|
|
24
|
+
line_ids: lineIds,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
})
|
|
28
|
+
.pipe(mergeMap((response) => {
|
|
29
|
+
if (isSuccessResponse(response)) {
|
|
30
|
+
const actions = [];
|
|
31
|
+
actions.push(updateStatusForTransactionNotMiscategorizedUpdate(action.payload.transactionId, 'Completed', undefined));
|
|
32
|
+
const txnId = action.payload.transactionId.id;
|
|
33
|
+
const reviewedFailed = response.data?.transactions_reviewed_failed ?? [];
|
|
34
|
+
const isTransactionFailed = reviewedFailed.some((t) => t.transaction_id === txnId);
|
|
35
|
+
if (!isTransactionFailed) {
|
|
36
|
+
actions.push(updateTransactionIsMiscategorized(transactionKey, false));
|
|
37
|
+
const { expenseAutomationViewState, expenseAutomationTransactionsViewState, } = state$.value;
|
|
38
|
+
const { transactionCategorizationView, selectedTransactionCategorizationTab, } = expenseAutomationTransactionsViewState;
|
|
39
|
+
const currentTenantId = getCurrentTenant(state$.value)?.tenantId;
|
|
40
|
+
const { selectedPeriodByTenantId } = expenseAutomationViewState;
|
|
41
|
+
const selectedPeriod = currentTenantId != null
|
|
42
|
+
? selectedPeriodByTenantId[currentTenantId]
|
|
43
|
+
: undefined;
|
|
44
|
+
const autoTabLineItems = transactionCategorizationView.autoCategorized
|
|
45
|
+
.transactionReviewLocalDataById[txnId]
|
|
46
|
+
?.transactionReviewLocalData.tabSpecificLineItems
|
|
47
|
+
?.autoCategorized ?? [];
|
|
48
|
+
const hasAutoLines = autoTabLineItems.length > 0;
|
|
49
|
+
const { uiState } = transactionCategorizationView[selectedTransactionCategorizationTab];
|
|
50
|
+
const removeIdsFromPeriod = selectedTransactionCategorizationTab === 'review'
|
|
51
|
+
? [txnId]
|
|
52
|
+
: hasAutoLines
|
|
53
|
+
? []
|
|
54
|
+
: [txnId];
|
|
55
|
+
if (selectedPeriod != null) {
|
|
56
|
+
actions.push(updateStatusForTransactionNotMiscategorizedUpdateForCategorization(selectedTransactionCategorizationTab, [txnId], removeIdsFromPeriod, selectedPeriod, 'Completed'));
|
|
57
|
+
}
|
|
58
|
+
if (!hasAutoLines) {
|
|
59
|
+
actions.push(removeTransactionCategorization({
|
|
60
|
+
transactionIds: [txnId],
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
if (removeIdsFromPeriod.length > 0 &&
|
|
64
|
+
selectedPeriod != null) {
|
|
65
|
+
const totalCountByPeriod = uiState.totalCount[toMonthYearPeriodId(selectedPeriod)];
|
|
66
|
+
const safeTotalCount = totalCountByPeriod ?? 0;
|
|
67
|
+
const updatedTotalCount = Math.max(0, safeTotalCount - 1);
|
|
68
|
+
actions.push(updateTotalCountForTransactionCategorization({
|
|
69
|
+
selectedPeriod,
|
|
70
|
+
selectedTab: selectedTransactionCategorizationTab,
|
|
71
|
+
totalCount: updatedTotalCount,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
actions.push(updateStatusConsolidatedSaveTransactionDetail({
|
|
76
|
+
transactionId: action.payload.transactionId,
|
|
77
|
+
fetchState: 'Completed',
|
|
78
|
+
}));
|
|
79
|
+
if (action.payload.transactionSaveUpdates.anyBulkUpdates === true) {
|
|
80
|
+
actions.push(openSnackbar({
|
|
81
|
+
messageSection: 'transactionDetails_updatingPastTransactions',
|
|
82
|
+
messageText: 'success',
|
|
83
|
+
type: 'success',
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
actions.push(openSnackbar({
|
|
88
|
+
messageSection: 'transactionDetails_transactionUpdated',
|
|
89
|
+
messageText: 'success',
|
|
90
|
+
type: 'success',
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
return from(actions);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
return of(updateStatusForTransactionNotMiscategorizedUpdate(action.payload.transactionId, 'Error', response.status));
|
|
97
|
+
}
|
|
98
|
+
}), catchError((error) => of(updateStatusForTransactionNotMiscategorizedUpdate(action.payload.transactionId, 'Error', createZeniAPIStatus('Unexpected Error', `Transaction mark as reviewed REST API call errored out ${error}`)))));
|
|
99
|
+
}
|
|
12
100
|
return zeniAPI
|
|
13
101
|
.putAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/transactions/${action.payload.transactionId.id}/mark-miscategorized-as-correct`, preparePayload(action.payload.transactionId))
|
|
14
102
|
.pipe(mergeMap((response) => {
|
|
@@ -16,44 +104,7 @@ export const markTransactionAsNotMiscategorizedEpic = (actions$, state$, zeniAPI
|
|
|
16
104
|
const actions = [];
|
|
17
105
|
actions.push(updateStatusForTransactionNotMiscategorizedUpdate(action.payload.transactionId, 'Completed', undefined));
|
|
18
106
|
const transactionKey = getTransactionDetailKey(action.payload.transactionId);
|
|
19
|
-
// Mark this transaction as not miscategorized anymore
|
|
20
107
|
actions.push(updateTransactionIsMiscategorized(transactionKey, false));
|
|
21
|
-
if (action.payload.isExpenseAutomationEnabled === true) {
|
|
22
|
-
const { expenseAutomationViewState, expenseAutomationTransactionsViewState, } = state$.value;
|
|
23
|
-
const { transactionCategorizationView, selectedTransactionCategorizationTab, } = expenseAutomationTransactionsViewState;
|
|
24
|
-
const currentTenant = getCurrentTenant(state$.value);
|
|
25
|
-
const { selectedPeriodByTenantId } = expenseAutomationViewState;
|
|
26
|
-
const selectedPeriod = selectedPeriodByTenantId[currentTenant.tenantId];
|
|
27
|
-
const txnId = action.payload.transactionId.id;
|
|
28
|
-
const autoTabLineItems = transactionCategorizationView.autoCategorized
|
|
29
|
-
.transactionReviewLocalDataById[txnId]
|
|
30
|
-
?.transactionReviewLocalData.tabSpecificLineItems
|
|
31
|
-
.autoCategorized ?? [];
|
|
32
|
-
const hasAutoLines = autoTabLineItems.length > 0;
|
|
33
|
-
const { uiState } = transactionCategorizationView[selectedTransactionCategorizationTab];
|
|
34
|
-
const removeIdsFromPeriod = selectedTransactionCategorizationTab === 'review'
|
|
35
|
-
? [txnId]
|
|
36
|
-
: hasAutoLines
|
|
37
|
-
? []
|
|
38
|
-
: [txnId];
|
|
39
|
-
actions.push(updateStatusForTransactionNotMiscategorizedUpdateForCategorization(selectedTransactionCategorizationTab, [txnId], removeIdsFromPeriod, selectedPeriod, 'Completed'));
|
|
40
|
-
if (!hasAutoLines) {
|
|
41
|
-
actions.push(removeTransactionCategorization({
|
|
42
|
-
transactionIds: [txnId],
|
|
43
|
-
}));
|
|
44
|
-
}
|
|
45
|
-
if (removeIdsFromPeriod.length > 0) {
|
|
46
|
-
const totalCountByPeriod = uiState.totalCount[toMonthYearPeriodId(selectedPeriod)];
|
|
47
|
-
const updatedTotalCount = totalCountByPeriod === 0
|
|
48
|
-
? totalCountByPeriod
|
|
49
|
-
: totalCountByPeriod - 1;
|
|
50
|
-
actions.push(updateTotalCountForTransactionCategorization({
|
|
51
|
-
selectedPeriod,
|
|
52
|
-
selectedTab: selectedTransactionCategorizationTab,
|
|
53
|
-
totalCount: updatedTotalCount,
|
|
54
|
-
}));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
108
|
actions.push(updateStatusConsolidatedSaveTransactionDetail({
|
|
58
109
|
transactionId: action.payload.transactionId,
|
|
59
110
|
fetchState: 'Completed',
|
|
@@ -66,7 +117,6 @@ export const markTransactionAsNotMiscategorizedEpic = (actions$, state$, zeniAPI
|
|
|
66
117
|
}));
|
|
67
118
|
}
|
|
68
119
|
else {
|
|
69
|
-
//Finally everything done for this transaction
|
|
70
120
|
actions.push(openSnackbar({
|
|
71
121
|
messageSection: 'transactionDetails_transactionUpdated',
|
|
72
122
|
messageText: 'success',
|
|
@@ -3,6 +3,7 @@ import { from, of } from 'rxjs';
|
|
|
3
3
|
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
4
4
|
import { toRecommendationPayload } from '../../../commonPayloadTypes/recommendationPayload';
|
|
5
5
|
import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
6
|
+
import { getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, } from '../../../entity/tenant/tenantSelector';
|
|
6
7
|
import { toCustomerTransactionPayload } from '../../../entity/transaction/payloadTypes/customerTransactionPayload';
|
|
7
8
|
import { toTransferTransactionPayload } from '../../../entity/transaction/payloadTypes/otherTransactionPayload';
|
|
8
9
|
import { toTransactionID } from '../../../entity/transaction/payloadTypes/transactionIDPayload';
|
|
@@ -10,7 +11,7 @@ import { injectCOTFlagsIntoTransactionPayload, toTransactionPayload, } from '../
|
|
|
10
11
|
import { toVendorTransactionPayload, toVendorTransactionWithCustomerPayload, } from '../../../entity/transaction/payloadTypes/vendorTransactionPayload';
|
|
11
12
|
import { isCustomerTransaction, } from '../../../entity/transaction/stateTypes/customerTransaction';
|
|
12
13
|
import { isTransferTransaction, } from '../../../entity/transaction/stateTypes/otherTransaction';
|
|
13
|
-
import { toJEAndDepositTransactionTypeStrict
|
|
14
|
+
import { toJEAndDepositTransactionTypeStrict } from '../../../entity/transaction/stateTypes/transactionType';
|
|
14
15
|
import { isVendorTransaction, } from '../../../entity/transaction/stateTypes/vendorTransaction';
|
|
15
16
|
import toSupportedTransaction from '../../../entity/transaction/toSupportedTransaction';
|
|
16
17
|
import { getTransactionWithCOT } from '../../../entity/transaction/transactionHelper';
|
|
@@ -19,13 +20,12 @@ import { getSupportedTransactionById } from '../../../entity/transaction/transac
|
|
|
19
20
|
import { updateVendors } from '../../../entity/vendor/vendorReducer';
|
|
20
21
|
import { getVendorByVendorId } from '../../../entity/vendor/vendorSelector';
|
|
21
22
|
import { createZeniAPIStatus, isActionNotAllowed, isSuccessResponse, } from '../../../responsePayload';
|
|
22
|
-
import {
|
|
23
|
-
import { getUncategorizedAccounts } from '../../accountList/accountListSelector';
|
|
23
|
+
import { getUncategorizedAccounts, } from '../../accountList/accountListSelector';
|
|
24
24
|
import { getPendingReviewLineIdsFromTransaction } from '../../expenseAutomationView/helpers/transactionCategorizationLocalDataHelper';
|
|
25
25
|
import { syncTransactionCategorizationFromDetailSave } from '../../expenseAutomationView/reducers/transactionsViewReducer';
|
|
26
|
+
import { isAccountUncategorized } from '../transactionDetailLocalDataHelper';
|
|
26
27
|
import { initializeTransactionDetailLocalData, markTransactionAsNotMiscategorized, updateStatusConsolidatedSaveTransactionDetail, updateTransactionDetail, updateTransactionDetailSaveStatus, updateTransactionStateIfUpdateActionNotAllowed, } from '../transactionDetailReducer';
|
|
27
28
|
import { getTransactionDetailKey, } from '../transactionDetailState';
|
|
28
|
-
import { isTransactionUncategorized } from './saveTransactionHelper';
|
|
29
29
|
export const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(updateTransactionDetail.match), mergeMap((action) => {
|
|
30
30
|
const { transactionState, vendorState, transactionDetailState } = state$.value;
|
|
31
31
|
const isAccountingProjectsEnabled = getIsAccountingProjectsEnabled(state$.value);
|
|
@@ -36,24 +36,6 @@ export const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => action
|
|
|
36
36
|
if (isSuccessResponse(response) && response.data != null) {
|
|
37
37
|
const transactionIdResp = toTransactionID(response.data.transaction);
|
|
38
38
|
const transactionDetailKeyForResponse = getTransactionDetailKey(transactionIdResp);
|
|
39
|
-
const tranDetail = recordGet(transactionDetailState.transactionDetailById, transactionDetailKeyForResponse, undefined);
|
|
40
|
-
const tranVendorUpdates = tranDetail?.transactionDetailLocalData.vendorUpdates;
|
|
41
|
-
let vendorUpdates = undefined;
|
|
42
|
-
if (tranVendorUpdates?.qboId != null) {
|
|
43
|
-
const vendor = getVendorByVendorId(vendorState, tranVendorUpdates.qboId);
|
|
44
|
-
if (vendor != null) {
|
|
45
|
-
vendorUpdates = {
|
|
46
|
-
id: vendor?.qboId,
|
|
47
|
-
name: vendor?.name,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
else if (tranVendorUpdates.name != null) {
|
|
51
|
-
vendorUpdates = {
|
|
52
|
-
id: tranVendorUpdates.qboId,
|
|
53
|
-
name: tranVendorUpdates.name,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
39
|
const actions = [];
|
|
58
40
|
if (response.data.vendors != null) {
|
|
59
41
|
actions.push(updateVendors(response.data.vendors, 'merge'));
|
|
@@ -66,30 +48,14 @@ export const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => action
|
|
|
66
48
|
vendorId: response.data.vendors?.[0]?.vendor_id,
|
|
67
49
|
}));
|
|
68
50
|
actions.push(updateTransactionDetailSaveStatus(transactionIdResp, 'Completed'));
|
|
69
|
-
let callMarkTransactionAsNotMiscategorized = true;
|
|
70
|
-
// NOTE: If tran is vendorType OR JE OR Deposit transaction and there is no vendor chosen on this tranaction while saving,
|
|
71
|
-
// then we need to skip mark-miscategorized-as-correct api call
|
|
72
|
-
if (toVendorTransactionTypeStrict(response.data.transaction.transaction_type) != null ||
|
|
73
|
-
toJEAndDepositTransactionTypeStrict(response.data.transaction.transaction_type) != null) {
|
|
74
|
-
const isAccountingClassesEnabled = getIsAccountingClassesEnabled(state$.value);
|
|
75
|
-
const isAnyClassMissing = isAccountingClassesEnabled &&
|
|
76
|
-
checkIfClassMissingForAnyLineItem(response.data.transaction);
|
|
77
|
-
const isVendorOrCustomerMissingInAnyLine = toJEAndDepositTransactionTypeStrict(response.data.transaction.transaction_type) != null &&
|
|
78
|
-
checkIfVendorOrCustomerMissingForAnyLineItem(response.data.transaction);
|
|
79
|
-
if ((vendorUpdates?.id == null &&
|
|
80
|
-
isVendorOrCustomerMissingInAnyLine == true) ||
|
|
81
|
-
isAnyClassMissing == true) {
|
|
82
|
-
callMarkTransactionAsNotMiscategorized = false;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
51
|
const uncategorizedIncomeExpense = getUncategorizedAccounts(state$.value.accountState, state$.value.accountListState, 'accountList');
|
|
86
52
|
const transaction = toSupportedTransaction(response.data.transaction);
|
|
87
|
-
const
|
|
53
|
+
const isAccountingClassesEnabled = getIsAccountingClassesEnabled(state$.value);
|
|
54
|
+
const eligibleLineIds = getLineIdsEligibleForReview(transaction, uncategorizedIncomeExpense, isAccountingClassesEnabled, action.payload.isExpenseAutomationEnabled);
|
|
88
55
|
if (action.payload.transactionSaveUpdates.markAsMiscatUpdates ===
|
|
89
56
|
true &&
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
actions.push(markTransactionAsNotMiscategorized(transactionIdResp, action.payload.transactionSaveUpdates, action.payload.isExpenseAutomationEnabled));
|
|
57
|
+
eligibleLineIds.length > 0) {
|
|
58
|
+
actions.push(markTransactionAsNotMiscategorized(transactionIdResp, action.payload.transactionSaveUpdates, action.payload.isExpenseAutomationEnabled, eligibleLineIds));
|
|
93
59
|
}
|
|
94
60
|
else {
|
|
95
61
|
actions.push(updateStatusConsolidatedSaveTransactionDetail({
|
|
@@ -299,31 +265,58 @@ export const toTransactionUpdatePayload = (updates, detail, cotTransactionTracki
|
|
|
299
265
|
// Inject COT flags into the payload
|
|
300
266
|
return injectCOTFlagsIntoTransactionPayload(basePayload, transaction);
|
|
301
267
|
};
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
268
|
+
const getLineIdsEligibleForReview = (transaction, uncategorizedIncomeExpense, isAccountingClassesEnabled, isExpenseAutomationEnabled) => {
|
|
269
|
+
// For non-EA, any line missing a required field (class when enabled, or entity on JE/Deposit)
|
|
270
|
+
// suppresses the entire call. EA keeps the per-line model, returning only eligible line IDs.
|
|
271
|
+
if (!isExpenseAutomationEnabled) {
|
|
272
|
+
const isApplicableType = isVendorTransaction(transaction) ||
|
|
273
|
+
toJEAndDepositTransactionTypeStrict(transaction.type) != null;
|
|
274
|
+
if (isApplicableType) {
|
|
275
|
+
const lines = (transaction.lines ?? []).filter((line) => line.type === 'transaction_with_account_and_class_line' ||
|
|
276
|
+
line.type === 'journal_entry_transaction_line' ||
|
|
277
|
+
line.type === 'transaction_with_product_or_service_line');
|
|
278
|
+
if (isAccountingClassesEnabled) {
|
|
279
|
+
const anyLineMissingClass = lines.some((line) => (line.class?.classId ??
|
|
280
|
+
'') === '');
|
|
281
|
+
if (anyLineMissingClass) {
|
|
282
|
+
return [];
|
|
283
|
+
}
|
|
309
284
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (line.line_detail
|
|
319
|
-
?.vendor == null &&
|
|
320
|
-
line.line_detail
|
|
321
|
-
?.customer == null) {
|
|
322
|
-
isAnyVendorOrCustomerMissing = true;
|
|
285
|
+
if (toJEAndDepositTransactionTypeStrict(transaction.type) != null) {
|
|
286
|
+
const anyLineMissingEntity = lines.some((line) => {
|
|
287
|
+
const tl = line;
|
|
288
|
+
return tl.vendor == null && tl.customer == null;
|
|
289
|
+
});
|
|
290
|
+
if (anyLineMissingEntity) {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
323
293
|
}
|
|
324
|
-
}
|
|
294
|
+
}
|
|
325
295
|
}
|
|
326
|
-
return
|
|
296
|
+
return (transaction.lines ?? [])
|
|
297
|
+
.filter((line) => {
|
|
298
|
+
if (line.type !== 'transaction_with_account_and_class_line' &&
|
|
299
|
+
line.type !== 'journal_entry_transaction_line' &&
|
|
300
|
+
line.type !== 'transaction_with_product_or_service_line') {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
const typedLine = line;
|
|
304
|
+
const hasCategory = !isAccountUncategorized(uncategorizedIncomeExpense, typedLine.account?.qboId);
|
|
305
|
+
// class is always a ClassBase object (never null) because the payload mapper
|
|
306
|
+
// calls mapClassBasePayloadToClassBase(payload ?? {}) which returns {} with classId:''.
|
|
307
|
+
// A real class is indicated by a non-empty classId.
|
|
308
|
+
const hasClass = !isAccountingClassesEnabled || (typedLine.class?.classId ?? '') !== '';
|
|
309
|
+
const hasAmount = (typedLine.amount?.amount ?? 0) !== 0;
|
|
310
|
+
// JE lines and Deposit transactions require vendor or customer before being eligible for review.
|
|
311
|
+
const isJEOrDepositContext = line.type === 'journal_entry_transaction_line' ||
|
|
312
|
+
toJEAndDepositTransactionTypeStrict(transaction.type) != null;
|
|
313
|
+
const hasEntity = !isJEOrDepositContext ||
|
|
314
|
+
typedLine.vendor != null ||
|
|
315
|
+
typedLine.customer != null;
|
|
316
|
+
return hasCategory && hasClass && hasAmount && hasEntity;
|
|
317
|
+
})
|
|
318
|
+
.map((line) => line.id)
|
|
319
|
+
.filter(Boolean);
|
|
327
320
|
};
|
|
328
321
|
// Converts a locally-created line item (from edit mode) into the wire shape
|
|
329
322
|
// expected by the categorize endpoint. Sending id='' signals the backend to
|
|
@@ -755,6 +755,7 @@ export const setEntityRecommendationForLineIdInLocalData = (transaction, transac
|
|
|
755
755
|
latestUpdatedLineWithEntity: transactionDetailLocalData.latestUpdatedLineWithEntity,
|
|
756
756
|
vendorBulkUpdateCategoryClassRecommendationsFetchStatus: transactionDetailLocalData.vendorBulkUpdateCategoryClassRecommendationsFetchStatus,
|
|
757
757
|
selectedEntity: transactionDetailLocalData.selectedEntity,
|
|
758
|
+
editMode: transactionDetailLocalData.editMode,
|
|
758
759
|
};
|
|
759
760
|
return newLocalData;
|
|
760
761
|
};
|
|
@@ -808,6 +809,7 @@ export const removeEntityRecommendationForLineIdInLocalData = (transaction, tran
|
|
|
808
809
|
latestSelectedEntityOnLineItem: transactionDetailLocalData.latestSelectedEntityOnLineItem,
|
|
809
810
|
latestUpdatedLineWithEntity: transactionDetailLocalData.latestUpdatedLineWithEntity,
|
|
810
811
|
selectedEntity: transactionDetailLocalData.selectedEntity,
|
|
812
|
+
editMode: transactionDetailLocalData.editMode,
|
|
811
813
|
};
|
|
812
814
|
return newLocalData;
|
|
813
815
|
};
|
|
@@ -827,7 +829,8 @@ export const getChangedLineItemCountFromLocalData = (transaction, transactionLoc
|
|
|
827
829
|
const accountLine = line;
|
|
828
830
|
hasCategoryChanged =
|
|
829
831
|
accountLineLocal.account?.qboId !== accountLine.account.qboId &&
|
|
830
|
-
accountLineLocal.account?.accountId !==
|
|
832
|
+
accountLineLocal.account?.accountId !==
|
|
833
|
+
accountLine.account.accountId;
|
|
831
834
|
hasClassChanged =
|
|
832
835
|
(accountLineLocal.class?.qboId != null ||
|
|
833
836
|
accountLine.class?.qboId != null) &&
|
|
@@ -190,12 +190,13 @@ const transactionDetail = createSlice({
|
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
192
|
},
|
|
193
|
-
prepare(transactionId, transactionSaveUpdates, isExpenseAutomationEnabled) {
|
|
193
|
+
prepare(transactionId, transactionSaveUpdates, isExpenseAutomationEnabled, lineIds) {
|
|
194
194
|
return {
|
|
195
195
|
payload: {
|
|
196
196
|
transactionId,
|
|
197
197
|
transactionSaveUpdates,
|
|
198
198
|
isExpenseAutomationEnabled,
|
|
199
|
+
lineIds,
|
|
199
200
|
},
|
|
200
201
|
};
|
|
201
202
|
},
|
|
@@ -7,11 +7,99 @@ const timePeriod_1 = require("../../../commonStateTypes/timePeriod");
|
|
|
7
7
|
const snackbarReducer_1 = require("../../../entity/snackbar/snackbarReducer");
|
|
8
8
|
const tenantSelector_1 = require("../../../entity/tenant/tenantSelector");
|
|
9
9
|
const transactionReducer_1 = require("../../../entity/transaction/transactionReducer");
|
|
10
|
+
const transactionSelector_1 = require("../../../entity/transaction/transactionSelector");
|
|
10
11
|
const responsePayload_1 = require("../../../responsePayload");
|
|
11
12
|
const transactionsViewReducer_1 = require("../../expenseAutomationView/reducers/transactionsViewReducer");
|
|
12
13
|
const transactionDetailReducer_1 = require("../transactionDetailReducer");
|
|
13
14
|
const transactionDetailState_1 = require("../transactionDetailState");
|
|
14
15
|
const markTransactionAsNotMiscategorizedEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(transactionDetailReducer_1.markTransactionAsNotMiscategorized.match), (0, operators_1.mergeMap)((action) => {
|
|
16
|
+
if (action.payload.isExpenseAutomationEnabled === true) {
|
|
17
|
+
const transactionKey = (0, transactionDetailState_1.getTransactionDetailKey)(action.payload.transactionId);
|
|
18
|
+
const transaction = (0, transactionSelector_1.getSupportedTransactionById)(state$.value.transactionState, transactionKey);
|
|
19
|
+
const lineIds = (action.payload.lineIds?.length ?? 0) > 0
|
|
20
|
+
? action.payload.lineIds
|
|
21
|
+
: (transaction?.lines ?? []).map((line) => line.id);
|
|
22
|
+
return zeniAPI
|
|
23
|
+
.putAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expense-automation/mark-as-reviewed`, {
|
|
24
|
+
transactions_reviewed: [
|
|
25
|
+
{
|
|
26
|
+
transaction_id: action.payload.transactionId.id,
|
|
27
|
+
line_ids: lineIds,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
})
|
|
31
|
+
.pipe((0, operators_1.mergeMap)((response) => {
|
|
32
|
+
if ((0, responsePayload_1.isSuccessResponse)(response)) {
|
|
33
|
+
const actions = [];
|
|
34
|
+
actions.push((0, transactionDetailReducer_1.updateStatusForTransactionNotMiscategorizedUpdate)(action.payload.transactionId, 'Completed', undefined));
|
|
35
|
+
const txnId = action.payload.transactionId.id;
|
|
36
|
+
const reviewedFailed = response.data?.transactions_reviewed_failed ?? [];
|
|
37
|
+
const isTransactionFailed = reviewedFailed.some((t) => t.transaction_id === txnId);
|
|
38
|
+
if (!isTransactionFailed) {
|
|
39
|
+
actions.push((0, transactionReducer_1.updateTransactionIsMiscategorized)(transactionKey, false));
|
|
40
|
+
const { expenseAutomationViewState, expenseAutomationTransactionsViewState, } = state$.value;
|
|
41
|
+
const { transactionCategorizationView, selectedTransactionCategorizationTab, } = expenseAutomationTransactionsViewState;
|
|
42
|
+
const currentTenantId = (0, tenantSelector_1.getCurrentTenant)(state$.value)?.tenantId;
|
|
43
|
+
const { selectedPeriodByTenantId } = expenseAutomationViewState;
|
|
44
|
+
const selectedPeriod = currentTenantId != null
|
|
45
|
+
? selectedPeriodByTenantId[currentTenantId]
|
|
46
|
+
: undefined;
|
|
47
|
+
const autoTabLineItems = transactionCategorizationView.autoCategorized
|
|
48
|
+
.transactionReviewLocalDataById[txnId]
|
|
49
|
+
?.transactionReviewLocalData.tabSpecificLineItems
|
|
50
|
+
?.autoCategorized ?? [];
|
|
51
|
+
const hasAutoLines = autoTabLineItems.length > 0;
|
|
52
|
+
const { uiState } = transactionCategorizationView[selectedTransactionCategorizationTab];
|
|
53
|
+
const removeIdsFromPeriod = selectedTransactionCategorizationTab === 'review'
|
|
54
|
+
? [txnId]
|
|
55
|
+
: hasAutoLines
|
|
56
|
+
? []
|
|
57
|
+
: [txnId];
|
|
58
|
+
if (selectedPeriod != null) {
|
|
59
|
+
actions.push((0, transactionsViewReducer_1.updateStatusForTransactionNotMiscategorizedUpdateForCategorization)(selectedTransactionCategorizationTab, [txnId], removeIdsFromPeriod, selectedPeriod, 'Completed'));
|
|
60
|
+
}
|
|
61
|
+
if (!hasAutoLines) {
|
|
62
|
+
actions.push((0, transactionReducer_1.removeTransactionCategorization)({
|
|
63
|
+
transactionIds: [txnId],
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
if (removeIdsFromPeriod.length > 0 &&
|
|
67
|
+
selectedPeriod != null) {
|
|
68
|
+
const totalCountByPeriod = uiState.totalCount[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)];
|
|
69
|
+
const safeTotalCount = totalCountByPeriod ?? 0;
|
|
70
|
+
const updatedTotalCount = Math.max(0, safeTotalCount - 1);
|
|
71
|
+
actions.push((0, transactionsViewReducer_1.updateTotalCountForTransactionCategorization)({
|
|
72
|
+
selectedPeriod,
|
|
73
|
+
selectedTab: selectedTransactionCategorizationTab,
|
|
74
|
+
totalCount: updatedTotalCount,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
actions.push((0, transactionDetailReducer_1.updateStatusConsolidatedSaveTransactionDetail)({
|
|
79
|
+
transactionId: action.payload.transactionId,
|
|
80
|
+
fetchState: 'Completed',
|
|
81
|
+
}));
|
|
82
|
+
if (action.payload.transactionSaveUpdates.anyBulkUpdates === true) {
|
|
83
|
+
actions.push((0, snackbarReducer_1.openSnackbar)({
|
|
84
|
+
messageSection: 'transactionDetails_updatingPastTransactions',
|
|
85
|
+
messageText: 'success',
|
|
86
|
+
type: 'success',
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
actions.push((0, snackbarReducer_1.openSnackbar)({
|
|
91
|
+
messageSection: 'transactionDetails_transactionUpdated',
|
|
92
|
+
messageText: 'success',
|
|
93
|
+
type: 'success',
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
return (0, rxjs_1.from)(actions);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
return (0, rxjs_1.of)((0, transactionDetailReducer_1.updateStatusForTransactionNotMiscategorizedUpdate)(action.payload.transactionId, 'Error', response.status));
|
|
100
|
+
}
|
|
101
|
+
}), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, transactionDetailReducer_1.updateStatusForTransactionNotMiscategorizedUpdate)(action.payload.transactionId, 'Error', (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', `Transaction mark as reviewed REST API call errored out ${error}`)))));
|
|
102
|
+
}
|
|
15
103
|
return zeniAPI
|
|
16
104
|
.putAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/transactions/${action.payload.transactionId.id}/mark-miscategorized-as-correct`, preparePayload(action.payload.transactionId))
|
|
17
105
|
.pipe((0, operators_1.mergeMap)((response) => {
|
|
@@ -19,44 +107,7 @@ const markTransactionAsNotMiscategorizedEpic = (actions$, state$, zeniAPI) => ac
|
|
|
19
107
|
const actions = [];
|
|
20
108
|
actions.push((0, transactionDetailReducer_1.updateStatusForTransactionNotMiscategorizedUpdate)(action.payload.transactionId, 'Completed', undefined));
|
|
21
109
|
const transactionKey = (0, transactionDetailState_1.getTransactionDetailKey)(action.payload.transactionId);
|
|
22
|
-
// Mark this transaction as not miscategorized anymore
|
|
23
110
|
actions.push((0, transactionReducer_1.updateTransactionIsMiscategorized)(transactionKey, false));
|
|
24
|
-
if (action.payload.isExpenseAutomationEnabled === true) {
|
|
25
|
-
const { expenseAutomationViewState, expenseAutomationTransactionsViewState, } = state$.value;
|
|
26
|
-
const { transactionCategorizationView, selectedTransactionCategorizationTab, } = expenseAutomationTransactionsViewState;
|
|
27
|
-
const currentTenant = (0, tenantSelector_1.getCurrentTenant)(state$.value);
|
|
28
|
-
const { selectedPeriodByTenantId } = expenseAutomationViewState;
|
|
29
|
-
const selectedPeriod = selectedPeriodByTenantId[currentTenant.tenantId];
|
|
30
|
-
const txnId = action.payload.transactionId.id;
|
|
31
|
-
const autoTabLineItems = transactionCategorizationView.autoCategorized
|
|
32
|
-
.transactionReviewLocalDataById[txnId]
|
|
33
|
-
?.transactionReviewLocalData.tabSpecificLineItems
|
|
34
|
-
.autoCategorized ?? [];
|
|
35
|
-
const hasAutoLines = autoTabLineItems.length > 0;
|
|
36
|
-
const { uiState } = transactionCategorizationView[selectedTransactionCategorizationTab];
|
|
37
|
-
const removeIdsFromPeriod = selectedTransactionCategorizationTab === 'review'
|
|
38
|
-
? [txnId]
|
|
39
|
-
: hasAutoLines
|
|
40
|
-
? []
|
|
41
|
-
: [txnId];
|
|
42
|
-
actions.push((0, transactionsViewReducer_1.updateStatusForTransactionNotMiscategorizedUpdateForCategorization)(selectedTransactionCategorizationTab, [txnId], removeIdsFromPeriod, selectedPeriod, 'Completed'));
|
|
43
|
-
if (!hasAutoLines) {
|
|
44
|
-
actions.push((0, transactionReducer_1.removeTransactionCategorization)({
|
|
45
|
-
transactionIds: [txnId],
|
|
46
|
-
}));
|
|
47
|
-
}
|
|
48
|
-
if (removeIdsFromPeriod.length > 0) {
|
|
49
|
-
const totalCountByPeriod = uiState.totalCount[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)];
|
|
50
|
-
const updatedTotalCount = totalCountByPeriod === 0
|
|
51
|
-
? totalCountByPeriod
|
|
52
|
-
: totalCountByPeriod - 1;
|
|
53
|
-
actions.push((0, transactionsViewReducer_1.updateTotalCountForTransactionCategorization)({
|
|
54
|
-
selectedPeriod,
|
|
55
|
-
selectedTab: selectedTransactionCategorizationTab,
|
|
56
|
-
totalCount: updatedTotalCount,
|
|
57
|
-
}));
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
111
|
actions.push((0, transactionDetailReducer_1.updateStatusConsolidatedSaveTransactionDetail)({
|
|
61
112
|
transactionId: action.payload.transactionId,
|
|
62
113
|
fetchState: 'Completed',
|
|
@@ -69,7 +120,6 @@ const markTransactionAsNotMiscategorizedEpic = (actions$, state$, zeniAPI) => ac
|
|
|
69
120
|
}));
|
|
70
121
|
}
|
|
71
122
|
else {
|
|
72
|
-
//Finally everything done for this transaction
|
|
73
123
|
actions.push((0, snackbarReducer_1.openSnackbar)({
|
|
74
124
|
messageSection: 'transactionDetails_transactionUpdated',
|
|
75
125
|
messageText: 'success',
|
|
@@ -9,6 +9,7 @@ const rxjs_1 = require("rxjs");
|
|
|
9
9
|
const operators_1 = require("rxjs/operators");
|
|
10
10
|
const recommendationPayload_1 = require("../../../commonPayloadTypes/recommendationPayload");
|
|
11
11
|
const snackbarReducer_1 = require("../../../entity/snackbar/snackbarReducer");
|
|
12
|
+
const tenantSelector_1 = require("../../../entity/tenant/tenantSelector");
|
|
12
13
|
const customerTransactionPayload_1 = require("../../../entity/transaction/payloadTypes/customerTransactionPayload");
|
|
13
14
|
const otherTransactionPayload_1 = require("../../../entity/transaction/payloadTypes/otherTransactionPayload");
|
|
14
15
|
const transactionIDPayload_1 = require("../../../entity/transaction/payloadTypes/transactionIDPayload");
|
|
@@ -25,13 +26,12 @@ const transactionSelector_1 = require("../../../entity/transaction/transactionSe
|
|
|
25
26
|
const vendorReducer_1 = require("../../../entity/vendor/vendorReducer");
|
|
26
27
|
const vendorSelector_1 = require("../../../entity/vendor/vendorSelector");
|
|
27
28
|
const responsePayload_1 = require("../../../responsePayload");
|
|
28
|
-
const tenantSelector_1 = require("../../../entity/tenant/tenantSelector");
|
|
29
29
|
const accountListSelector_1 = require("../../accountList/accountListSelector");
|
|
30
30
|
const transactionCategorizationLocalDataHelper_1 = require("../../expenseAutomationView/helpers/transactionCategorizationLocalDataHelper");
|
|
31
31
|
const transactionsViewReducer_1 = require("../../expenseAutomationView/reducers/transactionsViewReducer");
|
|
32
|
+
const transactionDetailLocalDataHelper_1 = require("../transactionDetailLocalDataHelper");
|
|
32
33
|
const transactionDetailReducer_1 = require("../transactionDetailReducer");
|
|
33
34
|
const transactionDetailState_1 = require("../transactionDetailState");
|
|
34
|
-
const saveTransactionHelper_1 = require("./saveTransactionHelper");
|
|
35
35
|
const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(transactionDetailReducer_1.updateTransactionDetail.match), (0, operators_1.mergeMap)((action) => {
|
|
36
36
|
const { transactionState, vendorState, transactionDetailState } = state$.value;
|
|
37
37
|
const isAccountingProjectsEnabled = (0, tenantSelector_1.getIsAccountingProjectsEnabled)(state$.value);
|
|
@@ -42,24 +42,6 @@ const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => actions$.pipe
|
|
|
42
42
|
if ((0, responsePayload_1.isSuccessResponse)(response) && response.data != null) {
|
|
43
43
|
const transactionIdResp = (0, transactionIDPayload_1.toTransactionID)(response.data.transaction);
|
|
44
44
|
const transactionDetailKeyForResponse = (0, transactionDetailState_1.getTransactionDetailKey)(transactionIdResp);
|
|
45
|
-
const tranDetail = (0, get_1.default)(transactionDetailState.transactionDetailById, transactionDetailKeyForResponse, undefined);
|
|
46
|
-
const tranVendorUpdates = tranDetail?.transactionDetailLocalData.vendorUpdates;
|
|
47
|
-
let vendorUpdates = undefined;
|
|
48
|
-
if (tranVendorUpdates?.qboId != null) {
|
|
49
|
-
const vendor = (0, vendorSelector_1.getVendorByVendorId)(vendorState, tranVendorUpdates.qboId);
|
|
50
|
-
if (vendor != null) {
|
|
51
|
-
vendorUpdates = {
|
|
52
|
-
id: vendor?.qboId,
|
|
53
|
-
name: vendor?.name,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
else if (tranVendorUpdates.name != null) {
|
|
57
|
-
vendorUpdates = {
|
|
58
|
-
id: tranVendorUpdates.qboId,
|
|
59
|
-
name: tranVendorUpdates.name,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
45
|
const actions = [];
|
|
64
46
|
if (response.data.vendors != null) {
|
|
65
47
|
actions.push((0, vendorReducer_1.updateVendors)(response.data.vendors, 'merge'));
|
|
@@ -72,30 +54,14 @@ const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => actions$.pipe
|
|
|
72
54
|
vendorId: response.data.vendors?.[0]?.vendor_id,
|
|
73
55
|
}));
|
|
74
56
|
actions.push((0, transactionDetailReducer_1.updateTransactionDetailSaveStatus)(transactionIdResp, 'Completed'));
|
|
75
|
-
let callMarkTransactionAsNotMiscategorized = true;
|
|
76
|
-
// NOTE: If tran is vendorType OR JE OR Deposit transaction and there is no vendor chosen on this tranaction while saving,
|
|
77
|
-
// then we need to skip mark-miscategorized-as-correct api call
|
|
78
|
-
if ((0, transactionType_1.toVendorTransactionTypeStrict)(response.data.transaction.transaction_type) != null ||
|
|
79
|
-
(0, transactionType_1.toJEAndDepositTransactionTypeStrict)(response.data.transaction.transaction_type) != null) {
|
|
80
|
-
const isAccountingClassesEnabled = (0, tenantSelector_1.getIsAccountingClassesEnabled)(state$.value);
|
|
81
|
-
const isAnyClassMissing = isAccountingClassesEnabled &&
|
|
82
|
-
checkIfClassMissingForAnyLineItem(response.data.transaction);
|
|
83
|
-
const isVendorOrCustomerMissingInAnyLine = (0, transactionType_1.toJEAndDepositTransactionTypeStrict)(response.data.transaction.transaction_type) != null &&
|
|
84
|
-
checkIfVendorOrCustomerMissingForAnyLineItem(response.data.transaction);
|
|
85
|
-
if ((vendorUpdates?.id == null &&
|
|
86
|
-
isVendorOrCustomerMissingInAnyLine == true) ||
|
|
87
|
-
isAnyClassMissing == true) {
|
|
88
|
-
callMarkTransactionAsNotMiscategorized = false;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
57
|
const uncategorizedIncomeExpense = (0, accountListSelector_1.getUncategorizedAccounts)(state$.value.accountState, state$.value.accountListState, 'accountList');
|
|
92
58
|
const transaction = (0, toSupportedTransaction_1.default)(response.data.transaction);
|
|
93
|
-
const
|
|
59
|
+
const isAccountingClassesEnabled = (0, tenantSelector_1.getIsAccountingClassesEnabled)(state$.value);
|
|
60
|
+
const eligibleLineIds = getLineIdsEligibleForReview(transaction, uncategorizedIncomeExpense, isAccountingClassesEnabled, action.payload.isExpenseAutomationEnabled);
|
|
94
61
|
if (action.payload.transactionSaveUpdates.markAsMiscatUpdates ===
|
|
95
62
|
true &&
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
actions.push((0, transactionDetailReducer_1.markTransactionAsNotMiscategorized)(transactionIdResp, action.payload.transactionSaveUpdates, action.payload.isExpenseAutomationEnabled));
|
|
63
|
+
eligibleLineIds.length > 0) {
|
|
64
|
+
actions.push((0, transactionDetailReducer_1.markTransactionAsNotMiscategorized)(transactionIdResp, action.payload.transactionSaveUpdates, action.payload.isExpenseAutomationEnabled, eligibleLineIds));
|
|
99
65
|
}
|
|
100
66
|
else {
|
|
101
67
|
actions.push((0, transactionDetailReducer_1.updateStatusConsolidatedSaveTransactionDetail)({
|
|
@@ -307,31 +273,58 @@ const toTransactionUpdatePayload = (updates, detail, cotTransactionTracking, isA
|
|
|
307
273
|
return (0, transactionPayload_1.injectCOTFlagsIntoTransactionPayload)(basePayload, transaction);
|
|
308
274
|
};
|
|
309
275
|
exports.toTransactionUpdatePayload = toTransactionUpdatePayload;
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
276
|
+
const getLineIdsEligibleForReview = (transaction, uncategorizedIncomeExpense, isAccountingClassesEnabled, isExpenseAutomationEnabled) => {
|
|
277
|
+
// For non-EA, any line missing a required field (class when enabled, or entity on JE/Deposit)
|
|
278
|
+
// suppresses the entire call. EA keeps the per-line model, returning only eligible line IDs.
|
|
279
|
+
if (!isExpenseAutomationEnabled) {
|
|
280
|
+
const isApplicableType = (0, vendorTransaction_1.isVendorTransaction)(transaction) ||
|
|
281
|
+
(0, transactionType_1.toJEAndDepositTransactionTypeStrict)(transaction.type) != null;
|
|
282
|
+
if (isApplicableType) {
|
|
283
|
+
const lines = (transaction.lines ?? []).filter((line) => line.type === 'transaction_with_account_and_class_line' ||
|
|
284
|
+
line.type === 'journal_entry_transaction_line' ||
|
|
285
|
+
line.type === 'transaction_with_product_or_service_line');
|
|
286
|
+
if (isAccountingClassesEnabled) {
|
|
287
|
+
const anyLineMissingClass = lines.some((line) => (line.class?.classId ??
|
|
288
|
+
'') === '');
|
|
289
|
+
if (anyLineMissingClass) {
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
317
292
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (line.line_detail
|
|
327
|
-
?.vendor == null &&
|
|
328
|
-
line.line_detail
|
|
329
|
-
?.customer == null) {
|
|
330
|
-
isAnyVendorOrCustomerMissing = true;
|
|
293
|
+
if ((0, transactionType_1.toJEAndDepositTransactionTypeStrict)(transaction.type) != null) {
|
|
294
|
+
const anyLineMissingEntity = lines.some((line) => {
|
|
295
|
+
const tl = line;
|
|
296
|
+
return tl.vendor == null && tl.customer == null;
|
|
297
|
+
});
|
|
298
|
+
if (anyLineMissingEntity) {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
331
301
|
}
|
|
332
|
-
}
|
|
302
|
+
}
|
|
333
303
|
}
|
|
334
|
-
return
|
|
304
|
+
return (transaction.lines ?? [])
|
|
305
|
+
.filter((line) => {
|
|
306
|
+
if (line.type !== 'transaction_with_account_and_class_line' &&
|
|
307
|
+
line.type !== 'journal_entry_transaction_line' &&
|
|
308
|
+
line.type !== 'transaction_with_product_or_service_line') {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
const typedLine = line;
|
|
312
|
+
const hasCategory = !(0, transactionDetailLocalDataHelper_1.isAccountUncategorized)(uncategorizedIncomeExpense, typedLine.account?.qboId);
|
|
313
|
+
// class is always a ClassBase object (never null) because the payload mapper
|
|
314
|
+
// calls mapClassBasePayloadToClassBase(payload ?? {}) which returns {} with classId:''.
|
|
315
|
+
// A real class is indicated by a non-empty classId.
|
|
316
|
+
const hasClass = !isAccountingClassesEnabled || (typedLine.class?.classId ?? '') !== '';
|
|
317
|
+
const hasAmount = (typedLine.amount?.amount ?? 0) !== 0;
|
|
318
|
+
// JE lines and Deposit transactions require vendor or customer before being eligible for review.
|
|
319
|
+
const isJEOrDepositContext = line.type === 'journal_entry_transaction_line' ||
|
|
320
|
+
(0, transactionType_1.toJEAndDepositTransactionTypeStrict)(transaction.type) != null;
|
|
321
|
+
const hasEntity = !isJEOrDepositContext ||
|
|
322
|
+
typedLine.vendor != null ||
|
|
323
|
+
typedLine.customer != null;
|
|
324
|
+
return hasCategory && hasClass && hasAmount && hasEntity;
|
|
325
|
+
})
|
|
326
|
+
.map((line) => line.id)
|
|
327
|
+
.filter(Boolean);
|
|
335
328
|
};
|
|
336
329
|
// Converts a locally-created line item (from edit mode) into the wire shape
|
|
337
330
|
// expected by the categorize endpoint. Sending id='' signals the backend to
|
|
@@ -764,6 +764,7 @@ const setEntityRecommendationForLineIdInLocalData = (transaction, transactionDet
|
|
|
764
764
|
latestUpdatedLineWithEntity: transactionDetailLocalData.latestUpdatedLineWithEntity,
|
|
765
765
|
vendorBulkUpdateCategoryClassRecommendationsFetchStatus: transactionDetailLocalData.vendorBulkUpdateCategoryClassRecommendationsFetchStatus,
|
|
766
766
|
selectedEntity: transactionDetailLocalData.selectedEntity,
|
|
767
|
+
editMode: transactionDetailLocalData.editMode,
|
|
767
768
|
};
|
|
768
769
|
return newLocalData;
|
|
769
770
|
};
|
|
@@ -818,6 +819,7 @@ const removeEntityRecommendationForLineIdInLocalData = (transaction, transaction
|
|
|
818
819
|
latestSelectedEntityOnLineItem: transactionDetailLocalData.latestSelectedEntityOnLineItem,
|
|
819
820
|
latestUpdatedLineWithEntity: transactionDetailLocalData.latestUpdatedLineWithEntity,
|
|
820
821
|
selectedEntity: transactionDetailLocalData.selectedEntity,
|
|
822
|
+
editMode: transactionDetailLocalData.editMode,
|
|
821
823
|
};
|
|
822
824
|
return newLocalData;
|
|
823
825
|
};
|
|
@@ -838,7 +840,8 @@ const getChangedLineItemCountFromLocalData = (transaction, transactionLocalData)
|
|
|
838
840
|
const accountLine = line;
|
|
839
841
|
hasCategoryChanged =
|
|
840
842
|
accountLineLocal.account?.qboId !== accountLine.account.qboId &&
|
|
841
|
-
accountLineLocal.account?.accountId !==
|
|
843
|
+
accountLineLocal.account?.accountId !==
|
|
844
|
+
accountLine.account.accountId;
|
|
842
845
|
hasClassChanged =
|
|
843
846
|
(accountLineLocal.class?.qboId != null ||
|
|
844
847
|
accountLine.class?.qboId != null) &&
|
|
@@ -38,10 +38,11 @@ export declare const downloadAccountingProviderAttachment: import("@reduxjs/tool
|
|
|
38
38
|
error: ZeniAPIStatus<Record<string, unknown>> | undefined;
|
|
39
39
|
}, "transactionDetail/updateTransactionDetailSaveStatus", never, never>, clearTransactionDetailSaveStatus: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[transactionId: TransactionID], {
|
|
40
40
|
transactionId: TransactionID;
|
|
41
|
-
}, "transactionDetail/clearTransactionDetailSaveStatus", never, never>, markTransactionAsNotMiscategorized: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[transactionId: TransactionID, transactionSaveUpdates: TransactionSaveUpdates, isExpenseAutomationEnabled: boolean], {
|
|
41
|
+
}, "transactionDetail/clearTransactionDetailSaveStatus", never, never>, markTransactionAsNotMiscategorized: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[transactionId: TransactionID, transactionSaveUpdates: TransactionSaveUpdates, isExpenseAutomationEnabled: boolean, lineIds?: string[] | undefined], {
|
|
42
42
|
transactionId: TransactionID;
|
|
43
43
|
transactionSaveUpdates: TransactionSaveUpdates;
|
|
44
44
|
isExpenseAutomationEnabled: boolean;
|
|
45
|
+
lineIds: string[] | undefined;
|
|
45
46
|
}, "transactionDetail/markTransactionAsNotMiscategorized", never, never>, deleteTransactionAttachment: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[attachmentFileName: string, transactionId: TransactionID], {
|
|
46
47
|
attachmentFileName: string;
|
|
47
48
|
transactionId: TransactionID;
|
|
@@ -197,12 +197,13 @@ const transactionDetail = (0, toolkit_1.createSlice)({
|
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
199
|
},
|
|
200
|
-
prepare(transactionId, transactionSaveUpdates, isExpenseAutomationEnabled) {
|
|
200
|
+
prepare(transactionId, transactionSaveUpdates, isExpenseAutomationEnabled, lineIds) {
|
|
201
201
|
return {
|
|
202
202
|
payload: {
|
|
203
203
|
transactionId,
|
|
204
204
|
transactionSaveUpdates,
|
|
205
205
|
isExpenseAutomationEnabled,
|
|
206
|
+
lineIds,
|
|
206
207
|
},
|
|
207
208
|
};
|
|
208
209
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeniai/client-epic-state",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.53-betaRD1",
|
|
4
4
|
"description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/esm/index.js",
|