@zeniai/client-epic-state 5.1.35-betaRD2 → 5.1.35-betaRR0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/lib/entity/jeSchedules/jeSchedulesPayload.d.ts +0 -1
  2. package/lib/entity/jeSchedules/jeSchedulesPayload.js +0 -1
  3. package/lib/entity/jeSchedules/jeSchedulesSelector.d.ts +0 -1
  4. package/lib/entity/jeSchedules/jeSchedulesSelector.js +1 -2
  5. package/lib/entity/jeSchedules/jeSchedulesState.d.ts +0 -1
  6. package/lib/esm/entity/jeSchedules/jeSchedulesPayload.js +0 -1
  7. package/lib/esm/entity/jeSchedules/jeSchedulesSelector.js +1 -2
  8. package/lib/esm/index.js +6 -6
  9. package/lib/esm/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +5 -31
  10. package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +3 -18
  11. package/lib/esm/view/expenseAutomationView/types/jeSchedulesViewState.js +6 -2
  12. package/lib/esm/view/transactionDetail/epics/saveTransactionHelper.js +19 -0
  13. package/lib/esm/view/transactionDetail/epics/updateTransactionDetailEpic.js +69 -2
  14. package/lib/esm/view/transactionDetail/journalEntryLinesViewModel.js +58 -0
  15. package/lib/esm/view/transactionDetail/transactionDetailReducer.js +66 -1
  16. package/lib/index.d.ts +8 -8
  17. package/lib/index.js +34 -35
  18. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +2 -6
  19. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +6 -32
  20. package/lib/view/expenseAutomationView/selectorTypes/jeSchedulesSelectorTypes.d.ts +0 -8
  21. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +3 -18
  22. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.d.ts +3 -6
  23. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.js +8 -4
  24. package/lib/view/transactionDetail/epics/saveTransactionHelper.js +19 -0
  25. package/lib/view/transactionDetail/epics/updateTransactionDetailEpic.d.ts +5 -1
  26. package/lib/view/transactionDetail/epics/updateTransactionDetailEpic.js +71 -3
  27. package/lib/view/transactionDetail/journalEntryLinesViewModel.d.ts +27 -0
  28. package/lib/view/transactionDetail/journalEntryLinesViewModel.js +61 -1
  29. package/lib/view/transactionDetail/transactionDetailReducer.d.ts +11 -2
  30. package/lib/view/transactionDetail/transactionDetailReducer.js +67 -2
  31. package/lib/view/transactionDetail/transactionDetailState.d.ts +8 -1
  32. package/lib/view/transactionDetail/transactionDetailTypes.d.ts +2 -0
  33. package/package.json +6 -7
@@ -72,7 +72,6 @@ export interface JEScheduledTransactionPayload {
72
72
  updated_by: UserPayload;
73
73
  vendor: VendorPayload;
74
74
  ai_summaries?: JEScheduleAISummariesPayload;
75
- confidence_score?: number | null;
76
75
  created_by?: UserPayload;
77
76
  end_date?: string | null;
78
77
  recommendations?: JEScheduleRecommendationsPayload;
@@ -285,7 +285,6 @@ function toJEScheduledTransaction(payload) {
285
285
  : {},
286
286
  scheduledJournalEntry: [],
287
287
  aiSummaries: toJEScheduleAISummaries(payload.ai_summaries),
288
- confidenceScore: payload.confidence_score ?? undefined,
289
288
  createdByUserID: payload.created_by?.user_id ?? undefined,
290
289
  recommendations: toJEScheduleRecommendations(payload.recommendations),
291
290
  };
@@ -61,7 +61,6 @@ export interface JEScheduledTransaction {
61
61
  vendor: Vendor;
62
62
  aiSummaries?: JEScheduleAISummaries;
63
63
  balanceAsOfToday?: Amount;
64
- confidenceScore?: number;
65
64
  createdByUser?: User;
66
65
  dayOfPostingDate?: ScheduleDaysOfMonth;
67
66
  endDate?: ZeniDate;
@@ -77,7 +77,7 @@ exports.getJEAccruedScheduleByJEScheduleKey = getJEAccruedScheduleByJEScheduleKe
77
77
  const getJEScheduledTransactionByJEScheduleTransactionKey = (jeScheduleTransactionKey, state) => {
78
78
  const { jeSchedulesState, vendorState, accountState, userState, classState, transactionState, } = state;
79
79
  const jeScheduleTransactionState = (0, get_1.default)(jeSchedulesState.schedulesByTransactionKey, jeScheduleTransactionKey);
80
- const { period, updatedByUserID: updatedBy, createdByUserID: createdBy, vendorId, jeCredit, jeDebit, currencyCode, currencySymbol, balanceAsOfToday, baseTransaction, startDate, endDate, status, updatedAt, jeScheduleId, scheduledJournalEntry, dayOfPostingDate: postingDate, lastDayOfMonth, otherAttributes, jeScheduleType, aiSummaries, confidenceScore, recommendations, } = jeScheduleTransactionState;
80
+ const { period, updatedByUserID: updatedBy, createdByUserID: createdBy, vendorId, jeCredit, jeDebit, currencyCode, currencySymbol, balanceAsOfToday, baseTransaction, startDate, endDate, status, updatedAt, jeScheduleId, scheduledJournalEntry, dayOfPostingDate: postingDate, lastDayOfMonth, otherAttributes, jeScheduleType, aiSummaries, recommendations, } = jeScheduleTransactionState;
81
81
  const vendorDetails = (0, vendorSelector_1.getVendorByVendorId)(vendorState, vendorId);
82
82
  if (vendorDetails == null) {
83
83
  throw new Error(`Vendor with ${vendorId} doesn't exist`);
@@ -136,7 +136,6 @@ const getJEScheduledTransactionByJEScheduleTransactionKey = (jeScheduleTransacti
136
136
  },
137
137
  scheduledJournalEntry,
138
138
  aiSummaries,
139
- confidenceScore,
140
139
  createdByUser: createdByUserDetails,
141
140
  recommendations,
142
141
  };
@@ -94,7 +94,6 @@ export interface JEScheduledTransactionState {
94
94
  vendorId: ID;
95
95
  aiSummaries?: JEScheduleAISummaries;
96
96
  balanceAsOfToday?: number;
97
- confidenceScore?: number;
98
97
  createdByUserID?: ID;
99
98
  dayOfPostingDate?: ScheduleDaysOfMonth;
100
99
  endDate?: ZeniDate;
@@ -275,7 +275,6 @@ export function toJEScheduledTransaction(payload) {
275
275
  : {},
276
276
  scheduledJournalEntry: [],
277
277
  aiSummaries: toJEScheduleAISummaries(payload.ai_summaries),
278
- confidenceScore: payload.confidence_score ?? undefined,
279
278
  createdByUserID: payload.created_by?.user_id ?? undefined,
280
279
  recommendations: toJEScheduleRecommendations(payload.recommendations),
281
280
  };
@@ -70,7 +70,7 @@ export const getJEAccruedScheduleByJEScheduleKey = (JEScheduleKey, state) => {
70
70
  export const getJEScheduledTransactionByJEScheduleTransactionKey = (jeScheduleTransactionKey, state) => {
71
71
  const { jeSchedulesState, vendorState, accountState, userState, classState, transactionState, } = state;
72
72
  const jeScheduleTransactionState = recordGet(jeSchedulesState.schedulesByTransactionKey, jeScheduleTransactionKey);
73
- const { period, updatedByUserID: updatedBy, createdByUserID: createdBy, vendorId, jeCredit, jeDebit, currencyCode, currencySymbol, balanceAsOfToday, baseTransaction, startDate, endDate, status, updatedAt, jeScheduleId, scheduledJournalEntry, dayOfPostingDate: postingDate, lastDayOfMonth, otherAttributes, jeScheduleType, aiSummaries, confidenceScore, recommendations, } = jeScheduleTransactionState;
73
+ const { period, updatedByUserID: updatedBy, createdByUserID: createdBy, vendorId, jeCredit, jeDebit, currencyCode, currencySymbol, balanceAsOfToday, baseTransaction, startDate, endDate, status, updatedAt, jeScheduleId, scheduledJournalEntry, dayOfPostingDate: postingDate, lastDayOfMonth, otherAttributes, jeScheduleType, aiSummaries, recommendations, } = jeScheduleTransactionState;
74
74
  const vendorDetails = getVendorByVendorId(vendorState, vendorId);
75
75
  if (vendorDetails == null) {
76
76
  throw new Error(`Vendor with ${vendorId} doesn't exist`);
@@ -129,7 +129,6 @@ export const getJEScheduledTransactionByJEScheduleTransactionKey = (jeScheduleTr
129
129
  },
130
130
  scheduledJournalEntry,
131
131
  aiSummaries,
132
- confidenceScore,
133
132
  createdByUser: createdByUserDetails,
134
133
  recommendations,
135
134
  };
package/lib/esm/index.js CHANGED
@@ -171,7 +171,7 @@ import { toExpenseAutomationViewType, } from './view/expenseAutomationView/expen
171
171
  import { isReviewTransactionBankTransferType, isReviewTransactionBillPaymentType, isReviewTransactionCreditCardCreditType, isReviewTransactionCreditCardPaymentType, isReviewTransactionDepositType, isReviewTransactionExpenseType, } from './view/expenseAutomationView/helpers/reconciliationHelpers';
172
172
  import { MAX_SELECTION_LIMIT, checkIfAllLineItemsAreCategoryClassFilled, getLineItemsByTransactionIdsFromLocalData, isAnyItemWithUncategorizedExpenseAccount, } from './view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper';
173
173
  import { clearExpenseAutomationFluxAnalysisView, fetchFluxAnalysisView, reviewFluxAnalysisView, updateFluxAnalysisViewPageMetaData, updateFluxAnalysisViewUIState, updateOperatingExpensesIdsForReview, updateSelectedSectionIdsForReview, } from './view/expenseAutomationView/reducers/fluxAnalysisViewReducer';
174
- import { clearJeScheduleLocalData as clearExpenseAutomationJEScheduleLocalData, clearExpenseAutomationJESchedulesView, fetchJeSchedulesPage as fetchExpenseAutomationJESchedulesPage, ignoreRecommendedJeSchedule as ignoreExpenseAutomationJESchedule, initializeAccountSettingsView as initializeJeAccountSettingsView, initializeJeScheduleLocalData, removeJeScheduleTransactionKey, retryJeSchedule as retryExpenseAutomationJESchedule, saveAccountSettings as saveJeAccountSettings, saveAccountSettingsLocalData as saveJeAccountSettingsLocalData, toggleJEPendingGroupCollapsed as toggleExpenseAutomationJEPendingGroupCollapsed, toggleJERecordedGroupCollapsed as toggleExpenseAutomationJERecordedGroupCollapsed, updateJEHighLevelTab as updateExpenseAutomationJEHighLevelTab, updateJEScheduleTypeFilter as updateExpenseAutomationJEScheduleTypeFilter, updateJESchedulesUIState as updateExpenseAutomationJESchedulesUIState, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys, } from './view/expenseAutomationView/reducers/jeSchedulesViewReducer';
174
+ import { clearJeScheduleLocalData as clearExpenseAutomationJEScheduleLocalData, clearExpenseAutomationJESchedulesView, fetchJeSchedulesPage as fetchExpenseAutomationJESchedulesPage, ignoreRecommendedJeSchedule as ignoreExpenseAutomationJESchedule, initializeAccountSettingsView as initializeJeAccountSettingsView, initializeJeScheduleLocalData, removeJeScheduleTransactionKey, retryJeSchedule as retryExpenseAutomationJESchedule, saveAccountSettings as saveJeAccountSettings, saveAccountSettingsLocalData as saveJeAccountSettingsLocalData, updateJESchedulesUIState as updateExpenseAutomationJESchedulesUIState, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys, } from './view/expenseAutomationView/reducers/jeSchedulesViewReducer';
175
175
  import { acknowledgeBulkUploadConfirmMatchComplete, bulkUploadAutomatchingTimedOut, bulkUploadReceipts, bulkUploadReceiptsFailure, bulkUploadReceiptsSuccess, clearBulkUpload, clearBulkUploadBatchDetailsForScopeChange, clearManualSearchResults, clearMissingReceiptsTabNavigation, confirmBulkUploadMatch, confirmBulkUploadMatchFailure, confirmBulkUploadMatchSuccess, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsFailure, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatches, fetchBulkUploadBatchesFailure, fetchBulkUploadBatchesSuccess, fetchCompletedTransactions, fetchCompletedTransactionsFailure, fetchCompletedTransactionsSuccess, fetchMissingReceipts as fetchExpenseAutomationMissingReceipts, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, markBatchDetailRefreshAttempted, markMissingReceiptAsDone as markExpenseAutomationMissingReceiptAsDone, pusherBatchStatusUpdate, refreshBatchDetailsForBatchId, requestMissingReceiptsTabNavigation, restoreBulkUploadAutomatchingOnMount, restoreBulkUploadMatchingState, searchTransactionsForManualMatch, searchTransactionsForManualMatchFailure, searchTransactionsForManualMatchSuccess, setBulkUploadCompletedSubTab, setBulkUploadResultsTab, setBulkUploadSortConfig, setBulkUploadUploadedFileCount, storeBatchDetails, updateBulkUploadProgress, updateMissingReceiptUploadState as updateExpenseAutomationMissingReceiptUploadState, updateMissingReceiptsUIState as updateExpenseAutomationMissingReceiptsUIState, uploadMissingReceiptSuccess as uploadExpenseAutomationMissingReceiptSuccess, } from './view/expenseAutomationView/reducers/missingReceiptsViewReducer';
176
176
  import { clearStatementDateConflict, deleteAccountStatement, excludeAccountFromReconciliation, fetchReconciliation as fetchReconciliationView, includeAccountInReconciliation, parseStatement, parseStatementFailure, parseStatementSuccess, reparseStatement, reparseStatementFailure, reparseStatementSuccess, resetReparseStatementStatus, saveReconciliationDetail as saveExpenseAutomationReconciliationDetail, saveReconciliationReview as saveExpenseAutomationReconciliationReview, setConnectionInProgressForAccount as setConnectionInProgressForAccountReconciliation, setStatementParseInProgress, submitStatementUpdate, updateAccountReconciliationLocalData as updateExpenseAutomationAccountReconciliationLocalData, updateSelectedAccountId as updateExpenseAutomationAccountReconciliationSelectedAccountId, updateSelectedTab as updateExpenseAutomationAccountReconciliationSelectedTab, updateReconListScrollPosition as updateExpenseAutomationReconListScrollPosition, updateReviewTabSortState as updateExpenseAutomationReconReviewTabListSortState, updateReviewTabLocalData as updateExpenseAutomationReconReviewTabLocalData, updateReconcileTabListScrollState as updateExpenseAutomationReconcileTabListScrollState, updateReconcileTabListSortState as updateExpenseAutomationReconcileTabListSortState, updateReconcileTabLocalData as updateExpenseAutomationReconcileTabLocalData, updateSelectedDrawerAccountId as updateExpenseAutomationSelectedDrawerAccountId, updateNodeCollapseState, updateParsedStatementData, updateStatementProcessingFailed, updateStatementUpdateLocalData, updateStatementUploadChosen, uploadAccountStatement, } from './view/expenseAutomationView/reducers/reconciliationViewReducer';
177
177
  import { backgroundRefetchReviewTab, clearExpenseAutomationTransactionsView, fetchTransactionCategorization, fetchTransactionCategorizationFailure, fetchTransactionCategorizationView, initializeTransactionCategorizationViewLocalData, markCategoryClassRecommendationsFailureForCategorization, markTransactionAsNotMiscategorized, removeTransactionFromAllTabs, saveTransactionCategorization, saveTransactionCategorizationLocalData, setAllItemsToCategoryClassInLocalDataForCategorization, setEntityRecommendationForLineIdsForCategorization, syncTransactionCategorizationFromDetailSave, updateCurrentSelectedTransactionCategorizationTab, updateSelectedCheckboxTransactionIds, updateSelectedCustomerForTransaction, updateSelectedTransactionId, updateSelectedVendorForTransaction, updateTransactionCategorization, updateTransactionCategorizationCompletedSubTab, updateTransactionCategorizationSaveStatus, updateTransactionCategorizationUIState, updateTransactionCategorizationUploadReceiptState, updateTransactionFilters, uploadTransactionCategorizationReceiptSuccess, } from './view/expenseAutomationView/reducers/transactionsViewReducer';
@@ -180,7 +180,7 @@ import { getExpenseAutomationReconciliationView, getReparseStatementStatusByAcco
180
180
  import { getExpenseAutomationTransactionView, getLastTransferEntryReplacement, } from './view/expenseAutomationView/selectors/transactionCategorizationSelector';
181
181
  import { TRANSACTION_FILTER_CATEGORIES, applyTransactionFilters, } from './view/expenseAutomationView/transactionFilterHelpers';
182
182
  import { DEFAULT_COMPLETED_SUB_TAB, toCompletedSubTab, } from './view/expenseAutomationView/types/completedSubTab';
183
- import { toJEHighLevelTab as toExpenseAutomationJEHighLevelTab, toJEScheduleSortKey as toExpenseAutomationJEScheduleSortKey, } from './view/expenseAutomationView/types/jeSchedulesViewState';
183
+ import { toJEScheduleMainTab as toExpenseAutomationJEScheduleMainTab, toJEScheduleSortKey as toExpenseAutomationJEScheduleSortKey, } from './view/expenseAutomationView/types/jeSchedulesViewState';
184
184
  import { BATCH_FILE_STATUSES, isUnmatchedTabFileStatus, toBatchFileStatus, toBatchStatusValue, toBulkUploadPhase, toBulkUploadResultsTab, toBulkUploadSortKey, toMatchSource, toMissingReceiptsTab, } from './view/expenseAutomationView/types/missingReceiptsViewState';
185
185
  import { toMissingReceiptsSortKey as toExpenseAutomationMissingReceiptsSortKey, } from './view/expenseAutomationView/types/missingReceiptsViewState';
186
186
  import { toReconciliationTabsType, } from './view/expenseAutomationView/types/reconciliationViewState';
@@ -396,7 +396,7 @@ import { TOP_EX_TIME_PERIODS, } from './view/topEx/topExState';
396
396
  import { getAccountingProviderAttachment, } from './view/transactionDetail/getAccountingProviderAttachmentSelector';
397
397
  import { JOURNAL_ENTRY_SORT_KEYS, filterJournalEntryLinesExcludingLinked, journalEntryDefaultSortConfig, journalEntryTotalsByPostingSide, sortJournalEntryLines, sumJournalEntryAmounts, toJournalEntrySortKey, } from './view/transactionDetail/journalEntryLinesViewModel';
398
398
  import { getChangedLineItemCountFromLocalData, isAccountUncategorized, } from './view/transactionDetail/transactionDetailLocalDataHelper';
399
- import { clearTransactionDetailSaveStatus, deleteTransactionAttachment, downloadAccountingProviderAttachment, downloadAccountingProviderAttachmentFailure, downloadAccountingProviderAttachmentSuccess, fetchTransactionDetail, initializeTransactionDetailLocalData, markCategoryClassRecommendationsFailureForTransactionDetail, removeEntityRecommendationForLineIdForTransactionDetail, resetAllLineItemsToVendor, resetLineItemsCategoryClassInLocalData, resetSelectedCustomer, resetVendor, saveTransactionDetail, saveTransactionDetailLocalData, setAllLineItemsLocalDataIsUpdateAllChecked, setAllLineItemsToCategoryClassInLocalData, setIsVendorUpdateAllChecked, updateLatestSelectedEntity, updateLocalDataWithTransactionsUpdateWithVendorCheckbox, updateSelectedCustomer, updateSelectedVendor, updateTransactionDetail, updateVendorBulkRecommendationsFetchStatus, updateVendorToAllLineItems, uploadMissingAttachmentSuccess, } from './view/transactionDetail/transactionDetailReducer';
399
+ import { clearTransactionDetailSaveStatus, deleteTransactionAttachment, downloadAccountingProviderAttachment, downloadAccountingProviderAttachmentFailure, downloadAccountingProviderAttachmentSuccess, fetchTransactionDetail, initializeTransactionDetailLocalData, markCategoryClassRecommendationsFailureForTransactionDetail, removeEntityRecommendationForLineIdForTransactionDetail, resetAllLineItemsToVendor, resetLineItemsCategoryClassInLocalData, resetSelectedCustomer, resetVendor, saveTransactionDetail, saveTransactionDetailLocalData, setAllLineItemsLocalDataIsUpdateAllChecked, setAllLineItemsToCategoryClassInLocalData, setIsVendorUpdateAllChecked, addSplitLine, deleteSplitLine, toggleSplitEditMode, updateLatestSelectedEntity, updateLocalDataWithTransactionsUpdateWithVendorCheckbox, updateSelectedCustomer, updateSelectedVendor, updateTransactionDetail, updateVendorBulkRecommendationsFetchStatus, updateVendorToAllLineItems, uploadMissingAttachmentSuccess, } from './view/transactionDetail/transactionDetailReducer';
400
400
  import { getInitialUpdatablePeriodsForTransactionRecommendations, getTransactionDetail, getTransactionDetailForTransaction, } from './view/transactionDetail/transactionDetailSelector';
401
401
  import { getThirdPartyIdFromTransactionId, } from './view/transactionDetail/transactionDetailState';
402
402
  import { getTransactionListByAccount, getTransactionListUIStateByAccountKey, } from './view/transactionList/transactionListByAccountSelector';
@@ -459,7 +459,7 @@ export { stringToUnion, stringToUnionStrict };
459
459
  export { fetchMonthEndCloseChecks, fetchMonthClosePerformanceTrend, getMonthEndCloseChecksViewByTenantId, ALL_MONTH_END_CLOSE_CHECKS_FREQUENCY, };
460
460
  export {
461
461
  // Bulk Upload Types
462
- BATCH_FILE_STATUSES, isUnmatchedTabFileStatus, DEFAULT_COMPLETED_SUB_TAB, toBatchFileStatus, toBatchStatusValue, toBulkUploadPhase, toBulkUploadResultsTab, toBulkUploadSortKey, toCompletedSubTab, toMatchSource, toMissingReceiptsTab, toExpenseAutomationJEHighLevelTab, toExpenseAutomationJEScheduleSortKey, getExpenseAutomationView, toExpenseAutomationMissingReceiptsSortKey, toExpenseAutomationTransactionsTabKey, toExpenseAutomationViewType, uploadExpenseAutomationMissingReceiptSuccess, markExpenseAutomationMissingReceiptAsDone, fetchAllExpenseAutomationTabs, refreshExpenseAutomationCurrentTab, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, fetchExpenseAutomationMissingReceipts,
462
+ BATCH_FILE_STATUSES, isUnmatchedTabFileStatus, DEFAULT_COMPLETED_SUB_TAB, toBatchFileStatus, toBatchStatusValue, toBulkUploadPhase, toBulkUploadResultsTab, toBulkUploadSortKey, toCompletedSubTab, toMatchSource, toMissingReceiptsTab, toExpenseAutomationJEScheduleMainTab, toExpenseAutomationJEScheduleSortKey, getExpenseAutomationView, toExpenseAutomationMissingReceiptsSortKey, toExpenseAutomationTransactionsTabKey, toExpenseAutomationViewType, uploadExpenseAutomationMissingReceiptSuccess, markExpenseAutomationMissingReceiptAsDone, fetchAllExpenseAutomationTabs, refreshExpenseAutomationCurrentTab, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, fetchExpenseAutomationMissingReceipts,
463
463
  // Bulk Upload Actions
464
464
  bulkUploadReceipts, bulkUploadAutomatchingTimedOut, bulkUploadReceiptsSuccess, bulkUploadReceiptsFailure, restoreBulkUploadAutomatchingOnMount, restoreBulkUploadMatchingState, updateBulkUploadProgress, pusherBatchStatusUpdate, requestMissingReceiptsTabNavigation, clearMissingReceiptsTabNavigation, fetchBulkUploadBatchDetails, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatchDetailsFailure, storeBatchDetails, fetchMoreBatchDetails, fetchMoreBatchDetailsComplete, fetchMoreBatchDetailsFailure, fetchBulkUploadBatches, fetchBulkUploadBatchesSuccess, fetchBulkUploadBatchesFailure, clearBulkUploadBatchDetailsForScopeChange, markBatchDetailRefreshAttempted, refreshBatchDetailsForBatchId, confirmBulkUploadMatch, confirmBulkUploadMatchSuccess, confirmBulkUploadMatchFailure, setBulkUploadResultsTab, setBulkUploadCompletedSubTab, setBulkUploadSortConfig, setBulkUploadUploadedFileCount, clearBulkUpload, searchTransactionsForManualMatch, searchTransactionsForManualMatchSuccess, searchTransactionsForManualMatchFailure, clearManualSearchResults, acknowledgeBulkUploadConfirmMatchComplete, fetchCompletedTransactions, fetchCompletedTransactionsSuccess, fetchCompletedTransactionsFailure, fetchFluxAnalysisView, clearExpenseAutomationFluxAnalysisView, updateOperatingExpensesIdsForReview as updateFluxOperatingExpensesIdsForReview, updateSelectedSectionIdsForReview as updateFluxAnalysisSelectedSectionIdsForReview, reviewFluxAnalysisView, updateExpenseAutomationMissingReceiptUploadState, updateExpenseAutomationMissingReceiptsUIState, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, getExpenseAutomationFluxAnalysisView, updateCurrentSelectedView, updateCurrentSelectedPeriod, getExpenseAutomationTransactionView, getLastTransferEntryReplacement, updateFluxAnalysisViewUIState, updateFluxAnalysisViewPageMetaData, MAX_SELECTION_LIMIT, checkIfAllLineItemsAreCategoryClassFilled, getLineItemsByTransactionIdsFromLocalData, isAnyItemWithUncategorizedExpenseAccount, excludeAccountFromReconciliation, includeAccountInReconciliation, saveExpenseAutomationReconciliationDetail, updateExpenseAutomationReconcileTabListScrollState, updateExpenseAutomationReconReviewTabListSortState, updateExpenseAutomationReconcileTabListSortState, updateExpenseAutomationReconcileTabLocalData, updateExpenseAutomationAccountReconciliationSelectedTab, updateExpenseAutomationAccountReconciliationSelectedAccountId, getExpenseAutomationReconciliationView, getReparseStatementStatusByAccountId, fetchReconciliationView, uploadAccountStatementIntoDocumentAI, updateExpenseAutomationReconListScrollPosition, setConnectionInProgressForAccountReconciliation, getAccountReconByAccountIdAndSelectedPeriod, toReconciliationTabsType, isAccountReconReport, updateExpenseAutomationReconReviewTabLocalData, updateExpenseAutomationSelectedDrawerAccountId, updateStatementProcessingFailed, saveExpenseAutomationReconciliationReview, updateExpenseAutomationAccountReconciliationLocalData, toReconciliationAccountSource, deleteAccountStatement, uploadAccountStatement, parseStatement, parseStatementSuccess, parseStatementFailure, reparseStatement, reparseStatementSuccess, reparseStatementFailure, resetReparseStatementStatus, updateParsedStatementData, updateNodeCollapseState, updateStatementUploadChosen, submitStatementUpdate, clearStatementDateConflict, updateStatementUpdateLocalData, isReviewTransactionBankTransferType, isReviewTransactionBillPaymentType, isReviewTransactionCreditCardPaymentType, isReviewTransactionDepositType, isReviewTransactionExpenseType, isReviewTransactionCreditCardCreditType, setStatementParseInProgress, };
465
465
  export { fetchTransactionCategorization, fetchTransactionCategorizationView, updateTransactionCategorizationUIState, updateTransactionFilters, updateSelectedCheckboxTransactionIds, markCategoryClassRecommendationsFailureForCategorization, setEntityRecommendationForLineIdsForCategorization, initializeTransactionCategorizationViewLocalData, setAllItemsToCategoryClassInLocalDataForCategorization, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, createTransferEntry, createTransferEntryFailure, createTransferEntryReplacedTransaction, createTransferEntrySuccess, resetCreateTransferEntryStatus, clearTransferEntryRouteReplacement, fetchAccountsForTransferFlow, removeTransactionFromAllTabs, clearExpenseAutomationTransactionsView, toTransactionsSortKey, };
@@ -515,7 +515,7 @@ export { isTransferTransaction };
515
515
  export { getSupportedTransactionById, };
516
516
  export { clearTransactionList, updateLatestTransactionId, parallelFetchAccountTransactionList, parallelFetchEntityTransactionList, fetchAccountTransactionList, updateTransactionListByAccount, updateTransactionListByAccountFailure, updateTransactionListByAccountUIState, parallelFetchClassTransactionList, updateTransactionListByClassUIState, updateTransactionListByClass, parallelFetchProjectTransactionList, updateTransactionListByProjectUIState, updateTransactionListByProject, updateTransactionListUIStateByCategoryType, parallelFetchTransactionListByCategoryType, };
517
517
  export { getTransactionListByAccount, getTransactionListUIStateByAccountKey, };
518
- export { fetchEntityTransactionList, updateTransactionListByEntity, saveTransactionDetailLocalData, clearTransactionDetailSaveStatus, updateTransactionListByEntityFailure, resetLineItemsCategoryClassInLocalData, setAllLineItemsToCategoryClassInLocalData, saveTransactionDetail, setAllLineItemsLocalDataIsUpdateAllChecked, initializeTransactionDetailLocalData, markCategoryClassRecommendationsFailureForTransactionDetail, removeEntityRecommendationForLineIdForTransactionDetail, updateSelectedVendor, updateSelectedCustomer, resetSelectedCustomer, resetVendor, updateLocalDataWithTransactionsUpdateWithVendorCheckbox, updateVendorToAllLineItems, resetAllLineItemsToVendor, updateLatestSelectedEntity, updateVendorBulkRecommendationsFetchStatus, setIsVendorUpdateAllChecked, };
518
+ export { fetchEntityTransactionList, updateTransactionListByEntity, saveTransactionDetailLocalData, clearTransactionDetailSaveStatus, updateTransactionListByEntityFailure, resetLineItemsCategoryClassInLocalData, setAllLineItemsToCategoryClassInLocalData, saveTransactionDetail, setAllLineItemsLocalDataIsUpdateAllChecked, initializeTransactionDetailLocalData, markCategoryClassRecommendationsFailureForTransactionDetail, removeEntityRecommendationForLineIdForTransactionDetail, updateSelectedVendor, updateSelectedCustomer, resetSelectedCustomer, resetVendor, updateLocalDataWithTransactionsUpdateWithVendorCheckbox, updateVendorToAllLineItems, resetAllLineItemsToVendor, updateLatestSelectedEntity, updateVendorBulkRecommendationsFetchStatus, setIsVendorUpdateAllChecked, addSplitLine, deleteSplitLine, toggleSplitEditMode, };
519
519
  export { downloadAccountingProviderAttachment, downloadAccountingProviderAttachmentSuccess, downloadAccountingProviderAttachmentFailure, fetchTransactionDetail, updateTransactionDetail, uploadMissingAttachmentSuccess, deleteTransactionAttachment, getThirdPartyIdFromTransactionId, };
520
520
  export { getTransactionListByEntity };
521
521
  export { getTransactionListByClass, getTransactionListUIStateByClassKey, getTransactionListByProject, getTransactionListUIStateByProjectKey, getTransactionListByEntityForSinglePeriod, };
@@ -623,7 +623,7 @@ export { updateChargeCardTransactionAttachments, updateChargeCardStatusFromPushe
623
623
  export { TIME_SERIES_DURATIONS, convertToTimeSeriesSelectionRange, toTimeSeriesDuration, fetchAggregatedReport, aggregatedReportView, };
624
624
  export { fetchApAging, getApAgingReport, updateApAgingUIState, };
625
625
  export { fetchApAgingDetail, getApAgingDetailForVendor, updateApAgingDetailUIState, };
626
- export { clearExpenseAutomationJEScheduleLocalData, clearExpenseAutomationJESchedulesView, fetchExpenseAutomationJESchedulesPage, ignoreExpenseAutomationJESchedule, initializeJeAccountSettingsView, initializeJeScheduleLocalData, removeJeScheduleTransactionKey, retryExpenseAutomationJESchedule, toggleExpenseAutomationJEPendingGroupCollapsed, toggleExpenseAutomationJERecordedGroupCollapsed, updateExpenseAutomationJEHighLevelTab, updateExpenseAutomationJEScheduleTypeFilter, updateExpenseAutomationJESchedulesUIState, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys, };
626
+ export { clearExpenseAutomationJEScheduleLocalData, clearExpenseAutomationJESchedulesView, fetchExpenseAutomationJESchedulesPage, ignoreExpenseAutomationJESchedule, initializeJeAccountSettingsView, initializeJeScheduleLocalData, removeJeScheduleTransactionKey, retryExpenseAutomationJESchedule, updateExpenseAutomationJESchedulesUIState, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys, };
627
627
  export { createNewSchedulesAccrued, deleteScheduleAccruedDetail, cancelScheduleAccruedJournalEntry, fetchScheduleAccruedDetails, fetchScheduleAccruedDetailsPage, resetJEAccruedLinkInLocalData, saveScheduleAccruedDetails, updateAmountsInScheduleAccruedDetail, updatedJEAccruedLinkWithRecommendedLocalData, updatedJELinkInLocalDataAccruedExpenses, fetchRecommendedTransactionRowIndex, clearSelectedJELinkRowIndex, updateLinkBillExpenseLocalData, updateScheduleAccruedDetailsLocalData, updateSelectedJEAccruedScheduleKey, resetSelectedJEAccruedScheduleKey, resetAccruedDetailNewScheduleState, };
628
628
  export { getJEScheduleTransactionKey, ALL_SCHEDULES_TYPES, getScheduleListReport, getAccruedScheduleListReport, fetchScheduleList, fetchAccruedScheduleList, fetchDownloadSchedules, fetchSchedulesAccount, updateScheduleListLocalData, getFetchStateForScheduleAccountList, toScheduleTypesType, toScheduleTypesTypeStrict, toScheduleListTabsFileTypeStrict, toScheduleSubTabType, updateScheduleListSubTab, updateScheduleListSearchText, updateScheduleListScrollState, updateScheduleListSortState, updateSelectedJEScheduleKey, fetchScheduleDetails, getScheduleDetailsView, getAccruedScheduleDetailsView, fetchScheduleDetailsPage, saveScheduleDetails, deleteScheduleDetail, createNewSchedules, updateScheduleDetailsLocalData, updateScheduleListDownloadState, updateAccruedJEScheduleAccruedByListKey, updatedSelectedJELinkRowIndex, getQBOUrlForLink, getThirdPartyIDFromQBOURL, updatedJELinkInLocalData, updateAmountsInScheduleDetail, resetJELinkInLocalData, updatedJELinkWithRecommendedLocalData, getFetchStateForScheduleListByType, getDefaultSelectedTimeframeForScheduleType, markAsCompleteScheduleDetail, resetMarkAsCompleteStatus, fetchVendorTabView, updateVendorTabViewTab, getVendorTabView, };
629
629
  export { toVendorFirstReviewViewColumnKeyType, getGlobalMerchantAutoCompleteResults, getVendorFirstReviewView, getVendorFirstReviewAttachmentView, fetchVendorFirstReviewView, fetchVendorFirstReviewAttachments, updateVendorFirstReviewViewScrollYOffset, resetVendorFirstReviewLocalData, updateVendorFirstReviewViewPageToken, clearRecentlySavedErroredVendorData, saveVendorFirstReviewView, updateVendorFirstReviewViewLocalData, updateVendorFirstReviewSortUiState, fetchGlobalMerchantAutoCompleteView, clearGlobalMerchantAutoCompleteResults, updateReviewVendorDetailLocalData, saveVendorDetailsView, getVendorDetailSelectorView, };
@@ -44,10 +44,7 @@ export const initialState = {
44
44
  uiState: {
45
45
  searchString: '',
46
46
  scrollPosition: { scrollTop: 0, scrollLeft: undefined },
47
- selectedJEHighLevelTab: 'je',
48
- selectedJETypeFilter: null,
49
- pendingGroupCollapsed: false,
50
- recordedGroupCollapsed: false,
47
+ selectedMainTab: 'pending_review',
51
48
  sortKey: 'transactionDate',
52
49
  sortOrder: 'descending',
53
50
  totalCount: 0,
@@ -384,39 +381,16 @@ const expenseAutomationJESchedulesView = createSlice({
384
381
  if (action.payload.scrollPosition != null) {
385
382
  draft.uiState.scrollPosition = action.payload.scrollPosition;
386
383
  }
387
- },
388
- updateJEHighLevelTab: {
389
- prepare(selectedJEHighLevelTab) {
390
- return { payload: { selectedJEHighLevelTab } };
391
- },
392
- reducer(draft, action) {
393
- draft.uiState.selectedJEHighLevelTab =
394
- action.payload.selectedJEHighLevelTab;
395
- },
396
- },
397
- updateJEScheduleTypeFilter: {
398
- prepare(selectedJETypeFilter) {
399
- return { payload: { selectedJETypeFilter } };
400
- },
401
- reducer(draft, action) {
402
- draft.uiState.selectedJETypeFilter =
403
- action.payload.selectedJETypeFilter;
404
- },
405
- },
406
- toggleJEPendingGroupCollapsed(draft) {
407
- draft.uiState.pendingGroupCollapsed =
408
- !draft.uiState.pendingGroupCollapsed;
409
- },
410
- toggleJERecordedGroupCollapsed(draft) {
411
- draft.uiState.recordedGroupCollapsed =
412
- !draft.uiState.recordedGroupCollapsed;
384
+ if (action.payload.selectedMainTab != null) {
385
+ draft.uiState.selectedMainTab = action.payload.selectedMainTab;
386
+ }
413
387
  },
414
388
  clearExpenseAutomationJESchedulesView(draft) {
415
389
  Object.assign(draft, initialState);
416
390
  },
417
391
  },
418
392
  });
419
- export const { clearExpenseAutomationJESchedulesView, clearJeAccountSettingsLocalData, clearJeScheduleLocalData, fetchAccountSettingsListForAccountTypes, fetchJeSchedules, fetchJeSchedulesFailure, fetchJeSchedulesPage, fetchJeSchedulesSuccess, fetchRecommendationForAccountSettings, ignoreRecommendedJeSchedule, ignoreRecommendedJeScheduleFailure, ignoreRecommendedJeScheduleSuccess, initializeAccountSettingsView, initializeJeScheduleLocalData, removeFailedJeScheduleTransactionKey, removeJeScheduleLocalDataById, removeJeScheduleTransactionKey, retryJeSchedule, retryJeScheduleFailure, retryJeScheduleSuccess, saveAccountSettings, saveAccountSettingsFailure, saveAccountSettingsLocalData, saveAccountSettingsSuccess, toggleJEPendingGroupCollapsed, toggleJERecordedGroupCollapsed, updateAccountSettingsListForAccountTypes, updateAccountSettingsListForAccountTypesFailure, updateJEHighLevelTab, updateJEScheduleTypeFilter, updateJESchedulesUIState, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys, updateRecommendationForAccountSettings, updateRecommendationForAccountSettingsFailure, } = expenseAutomationJESchedulesView.actions;
393
+ export const { clearExpenseAutomationJESchedulesView, clearJeAccountSettingsLocalData, clearJeScheduleLocalData, fetchAccountSettingsListForAccountTypes, fetchJeSchedules, fetchJeSchedulesFailure, fetchJeSchedulesPage, fetchJeSchedulesSuccess, fetchRecommendationForAccountSettings, ignoreRecommendedJeSchedule, ignoreRecommendedJeScheduleFailure, ignoreRecommendedJeScheduleSuccess, initializeAccountSettingsView, initializeJeScheduleLocalData, removeFailedJeScheduleTransactionKey, removeJeScheduleLocalDataById, removeJeScheduleTransactionKey, retryJeSchedule, retryJeScheduleFailure, retryJeScheduleSuccess, saveAccountSettings, saveAccountSettingsFailure, saveAccountSettingsLocalData, saveAccountSettingsSuccess, updateAccountSettingsListForAccountTypes, updateAccountSettingsListForAccountTypesFailure, updateJESchedulesUIState, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys, updateRecommendationForAccountSettings, updateRecommendationForAccountSettingsFailure, } = expenseAutomationJESchedulesView.actions;
420
394
  export default expenseAutomationJESchedulesView.reducer;
421
395
  const doUpdateRecommendationForAccountSettings = (draft, payload) => {
422
396
  const { prepaidExpensesRecommendations, fixedAssetsRecommendations, accruedExpensesRecommendations, } = mapAccountTypeRecommendationPayloadToAccountRecommendationByType(payload);
@@ -113,24 +113,11 @@ export function getExpenseAutomationJESchedulesView(state) {
113
113
  const jeScheduleDetails = getJEScheduledTransactionByJEScheduleTransactionKey(jeScheduleKey, state);
114
114
  failedJeScheduledTransaction.push(jeScheduleDetails);
115
115
  });
116
- const { sortKey, sortOrder, selectedJETypeFilter } = uiState;
117
- const matchesTypeFilter = (schedule) => selectedJETypeFilter == null ||
118
- schedule.jeScheduleType === selectedJETypeFilter;
116
+ const { sortKey, sortOrder } = uiState;
119
117
  const draftSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'draft'), sortKey, sortOrder);
120
118
  const ongoingSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'ongoing'), sortKey, sortOrder);
121
119
  const completedSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'completed' ||
122
120
  schedule.status.code === 'marked_as_completed'), sortKey, sortOrder);
123
- const recordedSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => (schedule.status.code === 'ongoing' ||
124
- schedule.status.code === 'completed' ||
125
- schedule.status.code === 'marked_as_completed') &&
126
- matchesTypeFilter(schedule)), sortKey, sortOrder);
127
- const allDraftSchedules = jeScheduledTransaction.filter((schedule) => schedule.status.code === 'draft');
128
- const kpiSummary = {
129
- aiAccountant: jeScheduledTransaction.filter((schedule) => schedule.confidenceScore != null).length,
130
- manual: jeScheduledTransaction.filter((schedule) => schedule.confidenceScore == null).length,
131
- needsReview: allDraftSchedules.length,
132
- total: jeScheduledTransaction.length,
133
- };
134
121
  const unsortedResolveSchedules = [];
135
122
  failedJeScheduledTransaction.forEach((schedule) => {
136
123
  schedule.scheduledJournalEntry.forEach((journalEntry) => {
@@ -143,8 +130,8 @@ export function getExpenseAutomationJESchedulesView(state) {
143
130
  const resolveSchedules = sortJEScheduledTransactions(unsortedResolveSchedules, sortKey, sortOrder);
144
131
  const validResolveForPending = unsortedResolveSchedules.filter((r) => jeScheduleLocalDataById[r.scheduledJournalEntry.scheduledJournalEntryID] != null);
145
132
  const pendingReviewSchedules = sortJEScheduledTransactions([
146
- ...allDraftSchedules.filter(matchesTypeFilter),
147
- ...validResolveForPending.filter((r) => matchesTypeFilter(r)),
133
+ ...jeScheduledTransaction.filter((schedule) => schedule.status.code === 'draft'),
134
+ ...validResolveForPending,
148
135
  ], sortKey, sortOrder);
149
136
  const allSteps = monthYearPeriodId != null
150
137
  ? getAllSteps(monthEndCloseChecksState, monthYearPeriodId, currentTenant.tenantId)
@@ -168,10 +155,8 @@ export function getExpenseAutomationJESchedulesView(state) {
168
155
  allClassList: allClasses,
169
156
  completedSchedules,
170
157
  draftSchedules,
171
- kpiSummary,
172
158
  ongoingSchedules,
173
159
  pendingReviewSchedules,
174
- recordedSchedules,
175
160
  resolveSchedules: resolveSchedules,
176
161
  accountSettingsView,
177
162
  postStatusById: postStatusById,
@@ -1,6 +1,4 @@
1
1
  import { stringToUnion } from '../../../commonStateTypes/stringToUnion';
2
- const JE_HIGH_LEVEL_TABS = ['je', 'ongoing_schedules'];
3
- export const toJEHighLevelTab = (v) => stringToUnion(v, JE_HIGH_LEVEL_TABS);
4
2
  const JE_SCHEDULE_SORT_KEYS = [
5
3
  'vendor',
6
4
  'category',
@@ -18,3 +16,9 @@ const JE_SCHEDULE_SORT_KEYS = [
18
16
  'memo',
19
17
  ];
20
18
  export const toJEScheduleSortKey = (v) => stringToUnion(v, JE_SCHEDULE_SORT_KEYS);
19
+ const JE_SCHEDULE_MAIN_TABS = [
20
+ 'pending_review',
21
+ 'ongoing',
22
+ 'completed',
23
+ ];
24
+ export const toJEScheduleMainTab = (v) => stringToUnion(v, JE_SCHEDULE_MAIN_TABS);
@@ -45,7 +45,15 @@ export const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDe
45
45
  let lastUpdatedCategory;
46
46
  let lastUpdatedClass;
47
47
  // single recommendation to just pick the last recommendation in the list of lines
48
+ const deletedLineIds = new Set(transactionDetailLocalData.deletedLineIds ?? []);
49
+ const existingLineIds = new Set(transaction.lines?.map((l) => l.id) ?? []);
50
+ // Collect new lines — in lineItemById but not yet in transaction.lines.
51
+ const addedLines = Object.values(transactionDetailLocalData.lineItemById).filter((item) => !existingLineIds.has(item.id));
48
52
  transaction.lines?.forEach((line) => {
53
+ // Skip lines the user deleted in edit mode.
54
+ if (deletedLineIds.has(line.id)) {
55
+ return;
56
+ }
49
57
  const lineItem = transactionDetailLocalData.lineItemById[line.id];
50
58
  if (line.type === 'transaction_with_account_and_class_line' ||
51
59
  line.type === 'transaction_with_product_or_service_line' ||
@@ -56,6 +64,7 @@ export const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDe
56
64
  const entityDetails = getEntityDetails(selectedEntity, lineItem, transactionDetailLocalData.vendorUpdates, transactionDetailLocalData.customerUpdates);
57
65
  updatesByLineId[line.id] = {
58
66
  lineDescription: lineItem.lineDescription,
67
+ amount: lineItem.amount,
59
68
  class: lineItem.class,
60
69
  account: lineItem.account,
61
70
  // Project is at the transaction level for invoice/credit_memo; omit from lines.
@@ -77,6 +86,14 @@ export const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDe
77
86
  currentSelectedEntityLine.type === 'vendor'
78
87
  ? lineItem.vendor
79
88
  : undefined,
89
+ // Carry the JE debit/credit side so a split edit that flips an
90
+ // existing line's posting side persists through the line patch.
91
+ ...(line.type === 'journal_entry_transaction_line'
92
+ ? {
93
+ postingType: lineItem.postingType,
94
+ postingTypeName: lineItem.postingTypeName,
95
+ }
96
+ : {}),
80
97
  };
81
98
  }
82
99
  let hasCategoryChanged = false;
@@ -163,6 +180,8 @@ export const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDe
163
180
  and all the line items are NOT uncategorized*/
164
181
  markAsMiscatUpdates: isTransactionMisCategorized,
165
182
  anyBulkUpdates: isBulkUpdate,
183
+ addedLines: addedLines.length > 0 ? addedLines : undefined,
184
+ deletedLineIds: deletedLineIds.size > 0 ? [...deletedLineIds] : undefined,
166
185
  };
167
186
  };
168
187
  const doUpdateEntityUpdates = (entityUpdates, entityId, entityType, recommendation, isBulkUpdate = false, updatedEntityId, entityName) => {
@@ -159,7 +159,7 @@ function preparePayload(transactionState, vendorState, transactionDetailState, t
159
159
  };
160
160
  }
161
161
  if (detail != null) {
162
- const transUpdates = toTransactionUpdatePayload(saveUpdates.updates, detail, cotTransactionTracking, isAccountingProjectsEnabled, vendorUpdates, customerUpdates, currentSelectedEntity);
162
+ const transUpdates = toTransactionUpdatePayload(saveUpdates.updates, detail, cotTransactionTracking, isAccountingProjectsEnabled, vendorUpdates, customerUpdates, currentSelectedEntity, saveUpdates.deletedLineIds, saveUpdates.addedLines);
163
163
  const entityUpdates = toEntityUpdatePayload(saveUpdates.entityRelatedUpdates);
164
164
  return {
165
165
  entity_related_updates: entityUpdates,
@@ -169,7 +169,7 @@ function preparePayload(transactionState, vendorState, transactionDetailState, t
169
169
  }
170
170
  return undefined;
171
171
  }
172
- export const toTransactionUpdatePayload = (updates, detail, cotTransactionTracking, isAccountingProjectsEnabled, vendorUpdates, customerUpdates, tranSelectedEntity) => {
172
+ export const toTransactionUpdatePayload = (updates, detail, cotTransactionTracking, isAccountingProjectsEnabled, vendorUpdates, customerUpdates, tranSelectedEntity, deletedLineIds, addedLines) => {
173
173
  const transaction = Object.assign({}, detail);
174
174
  transaction.memo = updates.memo ?? '';
175
175
  // Unlike `memo` (always reset to '' when absent), `documentId` falls back to
@@ -178,6 +178,11 @@ export const toTransactionUpdatePayload = (updates, detail, cotTransactionTracki
178
178
  // either the unchanged current value or undefined, and we must not send an
179
179
  // empty `document_id` that the server would treat as clearing the number.
180
180
  transaction.documentId = updates.documentId ?? transaction.documentId;
181
+ // Remove lines deleted by the user in edit mode before patching or serializing.
182
+ if (deletedLineIds != null && deletedLineIds.length > 0) {
183
+ const deletedSet = new Set(deletedLineIds);
184
+ transaction.lines = transaction.lines?.filter((line) => !deletedSet.has(line.id));
185
+ }
181
186
  const recommendationUpdates = {};
182
187
  for (const key in updates.updatesByLineId) {
183
188
  const lineUpdates = updates.updatesByLineId[key];
@@ -185,6 +190,9 @@ export const toTransactionUpdatePayload = (updates, detail, cotTransactionTracki
185
190
  if (line.id === key) {
186
191
  const lineTobeUpdated = Object.assign({}, line);
187
192
  lineTobeUpdated.lineDescription = lineUpdates.lineDescription ?? '';
193
+ if (lineUpdates.amount != null) {
194
+ lineTobeUpdated.amount = lineUpdates.amount;
195
+ }
188
196
  if (lineUpdates.class != null) {
189
197
  lineTobeUpdated.class = lineUpdates.class;
190
198
  }
@@ -206,6 +214,16 @@ export const toTransactionUpdatePayload = (updates, detail, cotTransactionTracki
206
214
  else {
207
215
  lineTobeUpdated.vendor = undefined;
208
216
  }
217
+ // Persist a debit↔credit flip on an existing JE line. `lineTobeUpdated`
218
+ // is typed as the account-and-class shape, but for a JE the runtime
219
+ // object is a JournalEntryTransactionLine, so cast to set the side.
220
+ if (lineUpdates.postingType != null) {
221
+ const journalEntryLine = lineTobeUpdated;
222
+ journalEntryLine.postingType = lineUpdates.postingType;
223
+ if (lineUpdates.postingTypeName != null) {
224
+ journalEntryLine.postingTypeName = lineUpdates.postingTypeName;
225
+ }
226
+ }
209
227
  if (lineUpdates.recommendation != null) {
210
228
  recommendationUpdates[line.id] = lineUpdates.recommendation;
211
229
  }
@@ -225,6 +243,12 @@ export const toTransactionUpdatePayload = (updates, detail, cotTransactionTracki
225
243
  });
226
244
  transaction.lines = newLineMap;
227
245
  }
246
+ // Append lines added by the user in edit mode. Sending id='' tells the
247
+ // backend to create a new QBO line rather than updating an existing one.
248
+ if (addedLines != null && addedLines.length > 0) {
249
+ const newLines = addedLines.map(toNewTransactionLine);
250
+ transaction.lines = [...(transaction.lines ?? []), ...newLines];
251
+ }
228
252
  transaction.recommendations = recommendationUpdates;
229
253
  // Convert transaction to payload first
230
254
  let basePayload;
@@ -299,6 +323,49 @@ const checkIfVendorOrCustomerMissingForAnyLineItem = (transactionPayload) => {
299
323
  }
300
324
  return isAnyVendorOrCustomerMissing;
301
325
  };
326
+ // Converts a locally-created line item (from edit mode) into the wire shape
327
+ // expected by the categorize endpoint. Sending id='' signals the backend to
328
+ // create a new QBO line rather than update an existing one.
329
+ //
330
+ // Type-dispatched so each transaction family contributes its own new-line
331
+ // shape: journal entries carry debit/credit `postingType`, expenses carry
332
+ // `billable`. Phase 3 types extend this by adding a branch rather than forking
333
+ // the function.
334
+ const EMPTY_ACCOUNT = {
335
+ accountDescription: '',
336
+ accountId: '',
337
+ accountName: '',
338
+ labels: [],
339
+ };
340
+ export const toNewTransactionLine = (item) => {
341
+ if (item.type === 'journal_entry_transaction_line') {
342
+ const journalEntryLine = {
343
+ ...item,
344
+ id: '',
345
+ type: 'journal_entry_transaction_line',
346
+ platformLineDetailType: item.platformLineDetailType ?? 'journal_entry_line_detail',
347
+ account: item.account ?? EMPTY_ACCOUNT,
348
+ billable: item.billable ?? 'not_billable',
349
+ isCategoryMiscategorized: false,
350
+ isClassMiscategorized: false,
351
+ lineTransactionType: item.lineTransactionType ?? 'journal_entry',
352
+ postingType: item.postingType ?? 'debit',
353
+ postingTypeName: item.postingTypeName ?? '',
354
+ };
355
+ return journalEntryLine;
356
+ }
357
+ const accountAndClassLine = {
358
+ ...item,
359
+ id: '',
360
+ type: 'transaction_with_account_and_class_line',
361
+ platformLineDetailType: item.platformLineDetailType ?? 'account_based_expense_line_detail',
362
+ account: item.account ?? EMPTY_ACCOUNT,
363
+ billable: item.billable ?? 'not_billable',
364
+ isCategoryMiscategorized: false,
365
+ isClassMiscategorized: false,
366
+ };
367
+ return accountAndClassLine;
368
+ };
302
369
  const toEntityUpdatePayload = (entityUpdates) => {
303
370
  const entityUpdatesPayload = [];
304
371
  let entityKey;
@@ -1,3 +1,4 @@
1
+ import Big from 'big.js';
1
2
  import orderBy from 'lodash/orderBy';
2
3
  import { stringToUnion } from '../../commonStateTypes/stringToUnion';
3
4
  import { LINE_TYPE_LINKED, } from '../../entity/transaction/stateTypes/transactionLine';
@@ -101,6 +102,63 @@ export const journalEntryTotalsByPostingSide = (lines) => {
101
102
  };
102
103
  };
103
104
  // ---------------------------------------------------------------------------
105
+ // Split reconciliation & validation (Phase 2 — JE split)
106
+ // ---------------------------------------------------------------------------
107
+ // Half-cent tolerance for the balance equality checks, matching the expense
108
+ // reconciliation row's epsilon. Sums use big.js so repeated decimal addition
109
+ // doesn't drift before the comparison.
110
+ const JOURNAL_ENTRY_BALANCE_EPSILON = 0.005;
111
+ /**
112
+ * Sums debit and credit sides (big.js-exact) and reports whether the split is
113
+ * balanced: total debits equal total credits AND equal the original
114
+ * transaction total. The original total of a valid JE equals its total debits,
115
+ * so it is passed in by the caller (`transaction.amount.amount`).
116
+ */
117
+ export const journalEntrySplitReconciliation = (lines, originalTotal) => {
118
+ let debit = new Big(0);
119
+ let credit = new Big(0);
120
+ for (const line of lines) {
121
+ const amount = new Big(line.amount?.amount ?? 0);
122
+ if (line.postingType === 'debit') {
123
+ debit = debit.plus(amount);
124
+ }
125
+ else if (line.postingType === 'credit') {
126
+ credit = credit.plus(amount);
127
+ }
128
+ }
129
+ const balancedToEachOther = debit
130
+ .minus(credit)
131
+ .abs()
132
+ .lt(JOURNAL_ENTRY_BALANCE_EPSILON);
133
+ const balancedToOriginal = debit
134
+ .minus(originalTotal)
135
+ .abs()
136
+ .lt(JOURNAL_ENTRY_BALANCE_EPSILON);
137
+ return {
138
+ debitTotal: debit.toNumber(),
139
+ creditTotal: credit.toNumber(),
140
+ isBalanced: balancedToEachOther && balancedToOriginal,
141
+ };
142
+ };
143
+ /**
144
+ * Save-time validation for a JE split (Figma `4165:33028` / `4165:45379`):
145
+ * 1. every line needs a non-zero debit or credit, and
146
+ * 2. total debits == total credits == original total.
147
+ * Returns the first failing rule's `errorKey` (maps to a strings entry). Does
148
+ * NOT require category/class to be chosen — balance is the only gate.
149
+ */
150
+ export const validateJournalEntrySplit = (lines, originalTotal) => {
151
+ const everyLineHasSide = lines.every((line) => (line.postingType === 'debit' || line.postingType === 'credit') &&
152
+ Math.abs(line.amount?.amount ?? 0) > JOURNAL_ENTRY_BALANCE_EPSILON);
153
+ if (!everyLineHasSide) {
154
+ return { ok: false, errorKey: 'lineNeedsDebitOrCredit' };
155
+ }
156
+ if (!journalEntrySplitReconciliation(lines, originalTotal).isBalanced) {
157
+ return { ok: false, errorKey: 'totalsMustEqual' };
158
+ }
159
+ return { ok: true };
160
+ };
161
+ // ---------------------------------------------------------------------------
104
162
  // Filter
105
163
  // ---------------------------------------------------------------------------
106
164
  /**
@@ -663,6 +663,71 @@ const transactionDetail = createSlice({
663
663
  };
664
664
  },
665
665
  },
666
+ // Split transaction — Phase 1 (expense)
667
+ addSplitLine: {
668
+ reducer(draft, action) {
669
+ const key = getTransactionDetailKey(action.payload.transactionId);
670
+ const detail = recordGet(draft.transactionDetailById, key, undefined);
671
+ if (detail != null) {
672
+ draft.transactionDetailById[key] = {
673
+ ...detail,
674
+ transactionDetailLocalData: {
675
+ ...detail.transactionDetailLocalData,
676
+ lineItemById: {
677
+ ...detail.transactionDetailLocalData.lineItemById,
678
+ [action.payload.newLine.id]: action.payload.newLine,
679
+ },
680
+ },
681
+ };
682
+ }
683
+ },
684
+ prepare(transactionId, newLine) {
685
+ return { payload: { transactionId, newLine } };
686
+ },
687
+ },
688
+ deleteSplitLine: {
689
+ reducer(draft, action) {
690
+ const key = getTransactionDetailKey(action.payload.transactionId);
691
+ const detail = recordGet(draft.transactionDetailById, key, undefined);
692
+ if (detail == null) {
693
+ return;
694
+ }
695
+ const { lineItemById, deletedLineIds = [] } = detail.transactionDetailLocalData;
696
+ const updated = { ...lineItemById };
697
+ delete updated[action.payload.lineId];
698
+ draft.transactionDetailById[key] = {
699
+ ...detail,
700
+ transactionDetailLocalData: {
701
+ ...detail.transactionDetailLocalData,
702
+ lineItemById: updated,
703
+ deletedLineIds: [...deletedLineIds, action.payload.lineId],
704
+ },
705
+ };
706
+ },
707
+ prepare(transactionId, lineId) {
708
+ return { payload: { transactionId, lineId } };
709
+ },
710
+ },
711
+ toggleSplitEditMode: {
712
+ reducer(draft, action) {
713
+ const key = getTransactionDetailKey(action.payload.transactionId);
714
+ const detail = recordGet(draft.transactionDetailById, key, undefined);
715
+ if (detail != null) {
716
+ draft.transactionDetailById[key] = {
717
+ ...detail,
718
+ transactionDetailLocalData: {
719
+ ...detail.transactionDetailLocalData,
720
+ editMode: action.payload.editMode,
721
+ // Clear delete tracking when exiting edit mode without saving.
722
+ ...(!action.payload.editMode && { deletedLineIds: [] }),
723
+ },
724
+ };
725
+ }
726
+ },
727
+ prepare(transactionId, editMode) {
728
+ return { payload: { transactionId, editMode } };
729
+ },
730
+ },
666
731
  clearRecommendedVendorsList(draft) {
667
732
  Object.assign(draft, initialState);
668
733
  },
@@ -771,7 +836,7 @@ const transactionDetail = createSlice({
771
836
  },
772
837
  },
773
838
  });
774
- export const { downloadAccountingProviderAttachment, downloadAccountingProviderAttachmentSuccess, downloadAccountingProviderAttachmentFailure, fetchTransactionDetail, updateTransactionDetailFetchState, removeTransactionDetail, clearTransactionDetail, uploadMissingAttachmentSuccess, updateTransactionDetail, updateTransactionDetailSaveStatus, clearTransactionDetailSaveStatus, markTransactionAsNotMiscategorized, deleteTransactionAttachment, deleteTransactionAttachmentFailure, deleteTransactionAttachmentSuccess, updateStatusForTransactionNotMiscategorizedUpdate, updateStatusConsolidatedSaveTransactionDetail, updateTransactionStateIfUpdateActionNotAllowed, saveTransactionDetail, initializeTransactionDetailLocalData, saveTransactionDetailLocalData, clearTransactionDetailLocalData, resetLineItemsCategoryClassInLocalData, setAllLineItemsToCategoryClassInLocalData, setAllLineItemsLocalDataIsUpdateAllChecked, setEntityRecommendationForLineIdsForTransactionDetail, markCategoryClassRecommendationsInProgressForTransactionDetail, markCategoryClassRecommendationsCompletedForTransactionDetail, markCategoryClassRecommendationsFailureForTransactionDetail, removeEntityRecommendationForLineIdForTransactionDetail, updateSelectedVendor, updateSelectedCustomer, resetVendor, resetSelectedCustomer, updateLocalDataWithTransactionsUpdateWithVendorCheckbox, updateVendorToAllLineItems, resetAllLineItemsToVendor, updateLatestSelectedEntity, updateVendorBulkRecommendationsFetchStatus, setIsVendorUpdateAllChecked, } = transactionDetail.actions;
839
+ export const { downloadAccountingProviderAttachment, downloadAccountingProviderAttachmentSuccess, downloadAccountingProviderAttachmentFailure, fetchTransactionDetail, updateTransactionDetailFetchState, removeTransactionDetail, clearTransactionDetail, uploadMissingAttachmentSuccess, updateTransactionDetail, updateTransactionDetailSaveStatus, clearTransactionDetailSaveStatus, markTransactionAsNotMiscategorized, deleteTransactionAttachment, deleteTransactionAttachmentFailure, deleteTransactionAttachmentSuccess, updateStatusForTransactionNotMiscategorizedUpdate, updateStatusConsolidatedSaveTransactionDetail, updateTransactionStateIfUpdateActionNotAllowed, saveTransactionDetail, initializeTransactionDetailLocalData, saveTransactionDetailLocalData, clearTransactionDetailLocalData, resetLineItemsCategoryClassInLocalData, setAllLineItemsToCategoryClassInLocalData, setAllLineItemsLocalDataIsUpdateAllChecked, setEntityRecommendationForLineIdsForTransactionDetail, markCategoryClassRecommendationsInProgressForTransactionDetail, markCategoryClassRecommendationsCompletedForTransactionDetail, markCategoryClassRecommendationsFailureForTransactionDetail, removeEntityRecommendationForLineIdForTransactionDetail, updateSelectedVendor, updateSelectedCustomer, resetVendor, resetSelectedCustomer, updateLocalDataWithTransactionsUpdateWithVendorCheckbox, updateVendorToAllLineItems, resetAllLineItemsToVendor, updateLatestSelectedEntity, updateVendorBulkRecommendationsFetchStatus, setIsVendorUpdateAllChecked, addSplitLine, deleteSplitLine, toggleSplitEditMode, } = transactionDetail.actions;
775
840
  export default transactionDetail.reducer;
776
841
  function getUpdatedEntityLocalData(detail, updates, entity, resetRecommendations, type) {
777
842
  const key = type === 'customer' ? 'customerUpdates' : 'vendorUpdates';