@zeniai/client-epic-state 4.19.84-betaVR1 → 4.19.84-betaVR10

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 (29) hide show
  1. package/lib/esm/index.js +2 -1
  2. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.js +7 -3
  3. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchDetailsEpic.js +1 -1
  4. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.js +1 -1
  5. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +1 -1
  6. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +13 -3
  7. package/lib/esm/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +19 -2
  8. package/lib/esm/view/expenseAutomationView/payload/missingReceiptsPayload.js +27 -1
  9. package/lib/esm/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +21 -10
  10. package/lib/esm/view/expenseAutomationView/selectors/missingReceiptsSelector.js +6 -5
  11. package/lib/esm/view/expenseAutomationView/types/missingReceiptsViewState.js +10 -0
  12. package/lib/index.d.ts +2 -2
  13. package/lib/index.js +35 -33
  14. package/lib/tsconfig.typecheck.tsbuildinfo +1 -0
  15. package/lib/view/expenseAutomationView/epics/missingReceipts/bulkUploadReceiptsEpic.js +7 -3
  16. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchBulkUploadBatchDetailsEpic.js +1 -1
  17. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMoreBatchDetailsEpic.js +1 -1
  18. package/lib/view/expenseAutomationView/epics/missingReceipts/fetchMultipleBatchDetailsEpic.js +1 -1
  19. package/lib/view/expenseAutomationView/epics/missingReceipts/searchTransactionsForManualMatchEpic.js +12 -2
  20. package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.d.ts +7 -1
  21. package/lib/view/expenseAutomationView/epics/missingReceipts/watchBulkUploadBatchStatusEpic.js +19 -2
  22. package/lib/view/expenseAutomationView/payload/missingReceiptsPayload.d.ts +12 -1
  23. package/lib/view/expenseAutomationView/payload/missingReceiptsPayload.js +28 -1
  24. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.d.ts +2 -2
  25. package/lib/view/expenseAutomationView/reducers/missingReceiptsViewReducer.js +21 -10
  26. package/lib/view/expenseAutomationView/selectors/missingReceiptsSelector.js +6 -5
  27. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.d.ts +8 -1
  28. package/lib/view/expenseAutomationView/types/missingReceiptsViewState.js +12 -1
  29. package/package.json +2 -2
@@ -15,9 +15,13 @@ const bulkUploadReceiptsEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0,
15
15
  .postFormData(`${zeniAPI.apiEndPoints.communicationAgentMicroServiceBaseUrl}/1.0/receipts/bulk-upload`, formData)
16
16
  .pipe((0, operators_1.mergeMap)((response) => {
17
17
  if ((0, responsePayload_1.isSuccessResponse)(response) && response.data != null) {
18
- return (0, rxjs_1.of)((0, missingReceiptsViewReducer_1.bulkUploadReceiptsSuccess)({
19
- batchId: response.data.batch_id,
20
- }));
18
+ const batchId = String(response.data.batch_id ?? '').trim();
19
+ if (batchId === '') {
20
+ return (0, rxjs_1.of)((0, missingReceiptsViewReducer_1.bulkUploadReceiptsFailure)({
21
+ error: (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Bulk upload succeeded but batch_id was missing or empty.'),
22
+ }));
23
+ }
24
+ return (0, rxjs_1.of)((0, missingReceiptsViewReducer_1.bulkUploadReceiptsSuccess)({ batchId }));
21
25
  }
22
26
  return (0, rxjs_1.of)((0, missingReceiptsViewReducer_1.bulkUploadReceiptsFailure)({ error: response.status }));
23
27
  }), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, missingReceiptsViewReducer_1.bulkUploadReceiptsFailure)({
@@ -35,7 +35,7 @@ const fetchBulkUploadBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.
35
35
  actions.push((0, transactionReducer_1.updateTransactions)(transactionPayloads, (value) => value.transaction_id));
36
36
  }
37
37
  actions.push((0, missingReceiptsViewReducer_1.fetchBulkUploadBatchDetailsSuccess)((0, missingReceiptsPayload_1.toBatchDetails)(response.data, zeniAPI.apiEndPoints.fileMicroServiceBaseUrl)));
38
- actions.push((0, missingReceiptsViewReducer_1.fetchBulkUploadBatches)(true));
38
+ actions.push((0, missingReceiptsViewReducer_1.fetchBulkUploadBatches)({ cacheOverride: true }));
39
39
  return (0, rxjs_1.from)(actions);
40
40
  }), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, missingReceiptsViewReducer_1.fetchBulkUploadBatchDetailsFailure)({
41
41
  error: (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Fetch batch details errored out: ' + JSON.stringify(error)),
@@ -9,7 +9,7 @@ const responsePayload_1 = require("../../../../responsePayload");
9
9
  const missingReceiptsPayload_1 = require("../../payload/missingReceiptsPayload");
10
10
  const missingReceiptsViewReducer_1 = require("../../reducers/missingReceiptsViewReducer");
11
11
  const fetchMultipleBatchDetailsEpic_1 = require("./fetchMultipleBatchDetailsEpic");
12
- const MORE_BATCH_PAGE_SIZE = 1;
12
+ const MORE_BATCH_PAGE_SIZE = 5;
13
13
  const fetchMoreBatchDetailsEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.fetchMoreBatchDetails.match),
14
14
  /** One page at a time — avoids duplicate loads if IO + scroll fallback both fire. */
15
15
  (0, operators_1.exhaustMap)(() => {
@@ -8,7 +8,7 @@ const transactionReducer_1 = require("../../../../entity/transaction/transaction
8
8
  const responsePayload_1 = require("../../../../responsePayload");
9
9
  const missingReceiptsPayload_1 = require("../../payload/missingReceiptsPayload");
10
10
  const missingReceiptsViewReducer_1 = require("../../reducers/missingReceiptsViewReducer");
11
- const INITIAL_BATCH_PAGE_SIZE = 1;
11
+ const INITIAL_BATCH_PAGE_SIZE = 5;
12
12
  function fetchBatchDetailsByIds(batchIds, zeniAPI) {
13
13
  if (batchIds.length === 0) {
14
14
  return rxjs_1.EMPTY;
@@ -19,9 +19,19 @@ exports.MANUAL_TRANSACTION_SEARCH_AUTO_CATEGORIZED = false;
19
19
  const searchTransactionsForManualMatchEpic = (actions$, state$, zeniAPI) => {
20
20
  const searchActions$ = actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.searchTransactionsForManualMatch.match));
21
21
  const pagination$ = searchActions$.pipe((0, operators_1.filter)((a) => a.payload.pageToken != null));
22
- const newSearch$ = searchActions$.pipe((0, operators_1.filter)((a) => a.payload.pageToken == null), (0, operators_1.debounceTime)(300));
23
- return (0, rxjs_1.merge)(pagination$, newSearch$).pipe((0, operators_1.switchMap)((action) => {
22
+ /**
23
+ * No API for empty or 1-char queries; immediate merge so switchMap cancels any in-flight HTTP.
24
+ */
25
+ const newSearchTooShortOrClear$ = searchActions$.pipe((0, operators_1.filter)((a) => a.payload.pageToken == null &&
26
+ a.payload.query.trim().length < 2));
27
+ const newSearchDebounced$ = searchActions$.pipe((0, operators_1.filter)((a) => a.payload.pageToken == null &&
28
+ a.payload.query.trim().length >= 2), (0, operators_1.debounceTime)(300));
29
+ return (0, rxjs_1.merge)(pagination$, newSearchTooShortOrClear$, newSearchDebounced$).pipe((0, operators_1.switchMap)((action) => {
24
30
  const { query, pageToken } = action.payload;
31
+ if (pageToken == null &&
32
+ query.trim().length < 2) {
33
+ return rxjs_1.EMPTY;
34
+ }
25
35
  const state = state$.value;
26
36
  const { expenseAutomationViewState: { selectedPeriodByTenantId }, expenseAutomationMissingReceiptsViewState: { bulkUpload: { manualSearch, sortKey, sortOrder }, }, } = state;
27
37
  const currentTenant = (0, tenantSelector_1.getCurrentTenant)(state);
@@ -1,13 +1,19 @@
1
1
  import { ActionsObservable, StateObservable } from 'redux-observable';
2
2
  import { Observable } from 'rxjs';
3
+ import { openSnackbar } from '../../../../entity/snackbar/snackbarReducer';
3
4
  import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
4
5
  import { RootState } from '../../../../reducer';
5
6
  import { ZeniAPI } from '../../../../zeniAPI';
6
7
  import { bulkUploadReceiptsSuccess, clearBulkUpload, fetchBulkUploadBatchDetailsSuccess, fetchBulkUploadBatches, pusherBatchStatusUpdate, requestMissingReceiptsTabNavigation } from '../../reducers/missingReceiptsViewReducer';
7
- export type ActionType = ReturnType<typeof bulkUploadReceiptsSuccess> | ReturnType<typeof pusherBatchStatusUpdate> | ReturnType<typeof fetchBulkUploadBatchDetailsSuccess> | ReturnType<typeof fetchBulkUploadBatches> | ReturnType<typeof requestMissingReceiptsTabNavigation> | ReturnType<typeof updateTransactions> | ReturnType<typeof clearBulkUpload>;
8
+ export type ActionType = ReturnType<typeof bulkUploadReceiptsSuccess> | ReturnType<typeof pusherBatchStatusUpdate> | ReturnType<typeof fetchBulkUploadBatchDetailsSuccess> | ReturnType<typeof fetchBulkUploadBatches> | ReturnType<typeof requestMissingReceiptsTabNavigation> | ReturnType<typeof updateTransactions> | ReturnType<typeof clearBulkUpload> | ReturnType<typeof openSnackbar>;
8
9
  /**
9
10
  * On Pusher batch completion: refresh batch list (then fetchMultipleBatchDetailsEpic runs),
10
11
  * and request switching to the Unmatched tab. Debounced to reduce duplicate refreshes.
11
12
  */
12
13
  export declare const pusherBatchStatusCompletionEpic: (actions$: ActionsObservable<ActionType>) => Observable<ActionType>;
14
+ /**
15
+ * Fallback polling when Pusher is slow or unavailable. Network errors on a poll tick show one
16
+ * error snackbar per upload session (first failure only) so the user is not spammed every
17
+ * interval; Pusher or a later successful poll still completes the flow.
18
+ */
13
19
  export declare const pollBulkUploadBatchStatusEpic: (actions$: ActionsObservable<ActionType>, _state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.pollBulkUploadBatchStatusEpic = exports.pusherBatchStatusCompletionEpic = void 0;
4
4
  const rxjs_1 = require("rxjs");
5
5
  const operators_1 = require("rxjs/operators");
6
+ const snackbarReducer_1 = require("../../../../entity/snackbar/snackbarReducer");
6
7
  const transactionReducer_1 = require("../../../../entity/transaction/transactionReducer");
7
8
  const responsePayload_1 = require("../../../../responsePayload");
8
9
  const missingReceiptsPayload_1 = require("../../payload/missingReceiptsPayload");
@@ -22,8 +23,14 @@ const pusherBatchStatusCompletionEpic = (actions$) => actions$.pipe((0, operator
22
23
  (0, missingReceiptsViewReducer_1.requestMissingReceiptsTabNavigation)({ tab: 'unmatched' }),
23
24
  ])));
24
25
  exports.pusherBatchStatusCompletionEpic = pusherBatchStatusCompletionEpic;
26
+ /**
27
+ * Fallback polling when Pusher is slow or unavailable. Network errors on a poll tick show one
28
+ * error snackbar per upload session (first failure only) so the user is not spammed every
29
+ * interval; Pusher or a later successful poll still completes the flow.
30
+ */
25
31
  const pollBulkUploadBatchStatusEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.bulkUploadReceiptsSuccess.match), (0, operators_1.mergeMap)((action) => {
26
32
  const { batchId } = action.payload;
33
+ let pollErrorNotified = false;
27
34
  return (0, rxjs_1.timer)(FALLBACK_FIRST_DELAY_MS, FALLBACK_POLL_INTERVAL_MS).pipe((0, operators_1.takeUntil)((0, rxjs_1.merge)(actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.clearBulkUpload.match)), actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.pusherBatchStatusUpdate.match), (0, operators_1.filter)((a) => a.payload.batchId === batchId)), actions$.pipe((0, operators_1.filter)(missingReceiptsViewReducer_1.fetchBulkUploadBatchDetailsSuccess.match), (0, operators_1.filter)((a) => a.payload.batchId === batchId)))), (0, operators_1.switchMap)(() => zeniAPI
28
35
  .getJSON(`${zeniAPI.apiEndPoints.communicationAgentMicroServiceBaseUrl}/1.0/batches/${batchId}`)
29
36
  .pipe((0, operators_1.mergeMap)((response) => {
@@ -36,12 +43,22 @@ const pollBulkUploadBatchStatusEpic = (actions$, _state$, zeniAPI) => actions$.p
36
43
  actions.push((0, transactionReducer_1.updateTransactions)(transactionPayloads, (value) => value.transaction_id));
37
44
  }
38
45
  actions.push((0, missingReceiptsViewReducer_1.fetchBulkUploadBatchDetailsSuccess)((0, missingReceiptsPayload_1.toBatchDetails)(response.data, zeniAPI.apiEndPoints.fileMicroServiceBaseUrl)));
39
- actions.push((0, missingReceiptsViewReducer_1.fetchBulkUploadBatches)(true));
46
+ actions.push((0, missingReceiptsViewReducer_1.fetchBulkUploadBatches)({ cacheOverride: true }));
40
47
  actions.push((0, missingReceiptsViewReducer_1.requestMissingReceiptsTabNavigation)({ tab: 'unmatched' }));
41
48
  return (0, rxjs_1.from)(actions);
42
49
  }
43
50
  }
44
51
  return rxjs_1.EMPTY;
45
- }), (0, operators_1.catchError)(() => (0, rxjs_1.of)()))));
52
+ }), (0, operators_1.catchError)(() => {
53
+ if (!pollErrorNotified) {
54
+ pollErrorNotified = true;
55
+ return (0, rxjs_1.of)((0, snackbarReducer_1.openSnackbar)({
56
+ messageSection: 'receipts_upload',
57
+ messageText: 'failed',
58
+ type: 'error',
59
+ }));
60
+ }
61
+ return rxjs_1.EMPTY;
62
+ }))));
46
63
  }));
47
64
  exports.pollBulkUploadBatchStatusEpic = pollBulkUploadBatchStatusEpic;
@@ -3,7 +3,7 @@ import { TransactionPayload } from '../../../entity/transaction/payloadTypes/tra
3
3
  import { SupportedTransactionPayload } from '../../../entity/transaction/transactionState';
4
4
  import { ZeniAPIResponse } from '../../../responsePayload';
5
5
  import type { MonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
6
- import type { BatchDetails, BatchFile, BatchListItem, BatchStatus, BatchSummary, ManualSearchResult } from '../types/missingReceiptsViewState';
6
+ import type { BatchDetails, BatchFile, BatchFileStatus, BatchListItem, BatchStatus, BatchSummary, ManualSearchResult } from '../types/missingReceiptsViewState';
7
7
  export interface MissingReceiptsQueryPayload {
8
8
  end_date: string;
9
9
  is_attachment_missing_only: boolean;
@@ -54,6 +54,11 @@ export interface MatchedTransactionPayload {
54
54
  export interface MatchCandidatePayload extends MatchedTransactionPayload {
55
55
  match_score: number;
56
56
  }
57
+ /**
58
+ * Wire payload for a batch file row. `status` may use legacy aliases (`matched`, `un_matched`,
59
+ * `possible_match`, `possible_matches`, etc.) until the backend is fully migrated; canonical
60
+ * values are applied in {@link parseBatchFileStatus} via {@link toBatchFile}.
61
+ */
57
62
  export interface BatchFilePayload {
58
63
  attachment_id: string;
59
64
  file_id: string;
@@ -78,6 +83,12 @@ export interface BatchDetailsResponsePayload {
78
83
  };
79
84
  }
80
85
  export type BatchDetailsResponse = ZeniAPIResponse<BatchDetailsResponsePayload>;
86
+ /**
87
+ * Maps batch-details API `files[].status` strings to canonical {@link BatchFileStatus}.
88
+ * Legacy aliases are supported during rollout; unknown strings map to `failed` so Redux never
89
+ * holds untyped status values.
90
+ */
91
+ export declare function parseBatchFileStatus(raw: string): BatchFileStatus;
81
92
  export declare function toBatchFile(payload: BatchFilePayload, filesEndPoint?: string): BatchFile;
82
93
  export declare function extractTransactionPayloadsFromBatchFiles(files: BatchFilePayload[]): SupportedTransactionPayload[];
83
94
  export declare function toBatchSummary(payload: BatchDetailsResponsePayload['summary']): BatchSummary;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseBatchFileStatus = parseBatchFileStatus;
3
4
  exports.toBatchFile = toBatchFile;
4
5
  exports.extractTransactionPayloadsFromBatchFiles = extractTransactionPayloadsFromBatchFiles;
5
6
  exports.toBatchSummary = toBatchSummary;
@@ -14,6 +15,32 @@ exports.supportedTransactionPayloadToManualSearchResult = supportedTransactionPa
14
15
  const urlPayload_1 = require("../../../commonPayloadTypes/urlPayload");
15
16
  const transactionPayload_1 = require("../../../entity/transaction/payloadTypes/transactionPayload");
16
17
  // -- Converter functions --
18
+ /**
19
+ * Maps batch-details API `files[].status` strings to canonical {@link BatchFileStatus}.
20
+ * Legacy aliases are supported during rollout; unknown strings map to `failed` so Redux never
21
+ * holds untyped status values.
22
+ */
23
+ function parseBatchFileStatus(raw) {
24
+ const s = raw.trim().toLowerCase().replace(/-/g, '_');
25
+ switch (s) {
26
+ case 'exact_match':
27
+ case 'matched':
28
+ return 'exact_match';
29
+ case 'possible_matches':
30
+ case 'possible_match':
31
+ case 'possiblematch':
32
+ case 'un_matched':
33
+ case 'unmatched':
34
+ return 'possible_matches';
35
+ case 'no_match':
36
+ case 'nomatch':
37
+ return 'no_match';
38
+ case 'failed':
39
+ return 'failed';
40
+ default:
41
+ return 'failed';
42
+ }
43
+ }
17
44
  function toBatchFile(payload, filesEndPoint) {
18
45
  return {
19
46
  attachmentId: payload.attachment_id,
@@ -29,7 +56,7 @@ function toBatchFile(payload, filesEndPoint) {
29
56
  filename: payload.filename,
30
57
  matchSource: payload.match_source,
31
58
  matchedTransactionId: payload.matched_transaction?.transaction_id,
32
- status: payload.status,
59
+ status: parseBatchFileStatus(payload.status),
33
60
  };
34
61
  }
35
62
  function extractTransactionPayloadsFromBatchFiles(files) {
@@ -5,7 +5,7 @@ import { MonthYearPeriod, TimePeriod } from '../../../commonStateTypes/timePerio
5
5
  import { TransactionPayload } from '../../../entity/transaction/payloadTypes/transactionPayload';
6
6
  import { SupportedTransactionPayload } from '../../../entity/transaction/transactionState';
7
7
  import { ZeniAPIStatus } from '../../../responsePayload';
8
- import type { BatchDetails, BatchListItem, BatchStatus, BulkUploadResultsTab, BulkUploadSortKey, BulkUploadState, CompletedSubTab, ManualSearchResult, MissingReceiptsViewState, MissingReceiptsViewUIState } from '../types/missingReceiptsViewState';
8
+ import { type BatchDetails, type BatchListItem, type BatchStatus, type BulkUploadResultsTab, type BulkUploadSortKey, type BulkUploadState, type CompletedSubTab, type ManualSearchResult, type MissingReceiptsViewState, type MissingReceiptsViewUIState } from '../types/missingReceiptsViewState';
9
9
  import type { MonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
10
10
  export declare const getCompletedTransactionsCacheKey: (periodId: MonthYearPeriodId, sortKey: BulkUploadSortKey, sortOrder: SortOrder, subTab: CompletedSubTab) => string;
11
11
  export declare const initialBulkUploadState: BulkUploadState;
@@ -44,7 +44,7 @@ export declare const fetchMissingReceipts: import("@reduxjs/toolkit").ActionCrea
44
44
  batchId: ID;
45
45
  }, "expenseAutomationMissingReceiptsView/batchDetailFetchFailed">, setInitialBatchDetailsLoading: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"expenseAutomationMissingReceiptsView/setInitialBatchDetailsLoading">, fetchMoreBatchDetails: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"expenseAutomationMissingReceiptsView/fetchMoreBatchDetails">, fetchMoreBatchDetailsComplete: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"expenseAutomationMissingReceiptsView/fetchMoreBatchDetailsComplete">, fetchMoreBatchDetailsFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
46
46
  error: ZeniAPIStatus;
47
- }, "expenseAutomationMissingReceiptsView/fetchMoreBatchDetailsFailure">, fetchBulkUploadBatches: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[input?: boolean | {
47
+ }, "expenseAutomationMissingReceiptsView/fetchMoreBatchDetailsFailure">, fetchBulkUploadBatches: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[input?: {
48
48
  cacheOverride?: boolean;
49
49
  invalidateBatchDetailsCache?: boolean;
50
50
  } | undefined], {
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.fetchCompletedTransactionsFailure = exports.fetchCompletedTransactionsSuccess = exports.fetchCompletedTransactions = exports.acknowledgeBulkUploadConfirmMatchComplete = exports.clearManualSearchResults = exports.searchTransactionsForManualMatchFailure = exports.searchTransactionsForManualMatchSuccess = exports.searchTransactionsForManualMatch = exports.clearBulkUpload = exports.setBulkUploadSortConfig = exports.setBulkUploadCompletedSubTab = exports.setBulkUploadResultsTab = exports.confirmBulkUploadMatchFailure = exports.confirmBulkUploadMatchSuccess = exports.confirmBulkUploadMatch = exports.fetchBulkUploadBatchesFailure = exports.fetchBulkUploadBatchesSuccess = exports.fetchBulkUploadBatches = exports.fetchMoreBatchDetailsFailure = exports.fetchMoreBatchDetailsComplete = exports.fetchMoreBatchDetails = exports.setInitialBatchDetailsLoading = exports.batchDetailFetchFailed = exports.storeBatchDetails = exports.fetchBulkUploadBatchDetailsFailure = exports.fetchBulkUploadBatchDetailsSuccess = exports.fetchBulkUploadBatchDetails = exports.clearMissingReceiptsTabNavigation = exports.requestMissingReceiptsTabNavigation = exports.pusherBatchStatusUpdate = exports.bulkUploadReceiptsFailure = exports.bulkUploadReceiptsSuccess = exports.updateBulkUploadProgress = exports.bulkUploadReceipts = exports.clearExpenseAutomationMissingReceiptsView = exports.markMissingReceiptAsDone = exports.updateMissingReceiptsUIState = exports.updateMissingReceiptUploadState = exports.uploadMissingReceiptSuccess = exports.fetchMissingReceiptsFailure = exports.fetchMissingReceiptsSuccess = exports.fetchMissingReceipts = exports.initialState = exports.initialBulkUploadState = exports.getCompletedTransactionsCacheKey = void 0;
5
5
  const toolkit_1 = require("@reduxjs/toolkit");
6
6
  const timePeriod_1 = require("../../../commonStateTypes/timePeriod");
7
+ const missingReceiptsViewState_1 = require("../types/missingReceiptsViewState");
7
8
  const getCompletedTransactionsCacheKey = (periodId, sortKey, sortOrder, subTab) => `completed-${subTab}-${sortKey}-${sortOrder === 'ascending' ? 'asc' : 'desc'}-${periodId}`;
8
9
  exports.getCompletedTransactionsCacheKey = getCompletedTransactionsCacheKey;
9
10
  exports.initialBulkUploadState = {
@@ -290,14 +291,6 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
290
291
  };
291
292
  },
292
293
  prepare(input) {
293
- if (input === true || input === false) {
294
- return {
295
- payload: {
296
- cacheOverride: input === true,
297
- invalidateBatchDetailsCache: false,
298
- },
299
- };
300
- }
301
294
  const o = input ?? {};
302
295
  return {
303
296
  payload: {
@@ -358,14 +351,14 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
358
351
  const file = details.files.find((f) => f.attachmentId === attachmentId);
359
352
  if (file != null) {
360
353
  const originalStatus = file.status;
361
- file.status = 'matched';
354
+ file.status = 'exact_match';
362
355
  file.matchedTransactionId = transactionId;
363
356
  file.candidates = undefined;
364
357
  details.summary.matched += 1;
365
358
  if (originalStatus === 'no_match') {
366
359
  details.summary.noMatch = Math.max(0, details.summary.noMatch - 1);
367
360
  }
368
- else {
361
+ else if (originalStatus === 'possible_matches') {
369
362
  details.summary.possibleMatches = Math.max(0, details.summary.possibleMatches - 1);
370
363
  }
371
364
  }
@@ -397,6 +390,24 @@ const expenseAutomationMissingReceiptsView = (0, toolkit_1.createSlice)({
397
390
  reducer(draft, action) {
398
391
  const { query, pageToken } = action.payload;
399
392
  const isLoadMore = pageToken != null;
393
+ const trimmed = query.trim();
394
+ /**
395
+ * Cleared or too-short query: no API (see epic). Reset fully (no In-Progress).
396
+ * Only bump `manualSearchUiResetKey` when clearing a real search — not when the UI
397
+ * dispatches `""` on every keystroke while length is below MIN (web-components always sends
398
+ * `onSearch("")` in that case), or ReceiptCard remounts and the input loses focus.
399
+ */
400
+ if (!isLoadMore &&
401
+ trimmed.length < missingReceiptsViewState_1.MIN_MANUAL_TRANSACTION_SEARCH_LENGTH) {
402
+ const hadActiveSearch = draft.bulkUpload.manualSearch.searchQuery.trim().length >=
403
+ missingReceiptsViewState_1.MIN_MANUAL_TRANSACTION_SEARCH_LENGTH ||
404
+ draft.bulkUpload.manualSearch.results.length > 0;
405
+ draft.bulkUpload.manualSearch = exports.initialBulkUploadState.manualSearch;
406
+ if (hadActiveSearch) {
407
+ draft.bulkUpload.manualSearchUiResetKey += 1;
408
+ }
409
+ return;
410
+ }
400
411
  draft.bulkUpload.manualSearch.searchQuery = query;
401
412
  if (isLoadMore) {
402
413
  draft.bulkUpload.manualSearch.isLoadingMore = true;
@@ -11,6 +11,7 @@ const transactionSelector_1 = require("../../../entity/transaction/transactionSe
11
11
  const expenseAutomationViewSelectorTypes_1 = require("../selectorTypes/expenseAutomationViewSelectorTypes");
12
12
  const missingReceiptsPayload_1 = require("../payload/missingReceiptsPayload");
13
13
  const missingReceiptsViewReducer_1 = require("../reducers/missingReceiptsViewReducer");
14
+ const missingReceiptsViewState_1 = require("../types/missingReceiptsViewState");
14
15
  function getExpenseAutomationMissingReceiptsView(state) {
15
16
  const { expenseAutomationMissingReceiptsViewState, expenseAutomationViewState, transactionState, monthEndCloseChecksState, } = state;
16
17
  const currentTenant = (0, tenantSelector_1.getCurrentTenant)(state);
@@ -64,7 +65,7 @@ function getExpenseAutomationMissingReceiptsView(state) {
64
65
  : [];
65
66
  /**
66
67
  * Primary "Unmatched" batch = first completed batch in platform list order, preferring the
67
- * first batch that still has un_matched/no_match work when any completed batch has such work.
68
+ * first batch that still has possible_matches/no_match work when any completed batch has such work.
68
69
  * Batches without details yet: wait if the list row still reports unmatched work; otherwise skip
69
70
  * so we do not block the whole tab while other rows are not fetched yet.
70
71
  */
@@ -106,8 +107,8 @@ function getExpenseAutomationMissingReceiptsView(state) {
106
107
  }
107
108
  };
108
109
  const bulkUploadSortDirection = bulkUpload.sortOrder === 'ascending' ? 'asc' : 'desc';
109
- const matchedFiles = (0, orderBy_1.default)(resolvedFiles?.filter((f) => f.status === 'matched') ?? [], getSortValue, bulkUploadSortDirection);
110
- const unmatchedFiles = (0, orderBy_1.default)(resolvedFiles?.filter((f) => f.status === 'un_matched' || f.status === 'no_match') ?? [], getSortValue, bulkUploadSortDirection);
110
+ const matchedFiles = (0, orderBy_1.default)(resolvedFiles?.filter((f) => f.status === 'exact_match') ?? [], getSortValue, bulkUploadSortDirection);
111
+ const unmatchedFiles = (0, orderBy_1.default)(resolvedFiles?.filter((f) => (0, missingReceiptsViewState_1.isUnmatchedTabFileStatus)(f.status)) ?? [], getSortValue, bulkUploadSortDirection);
111
112
  const progress = currentBatchStatus != null
112
113
  ? {
113
114
  ...currentBatchStatus.progress,
@@ -132,7 +133,7 @@ function getExpenseAutomationMissingReceiptsView(state) {
132
133
  return [];
133
134
  }
134
135
  return details.files
135
- .filter((f) => f.status === 'un_matched' || f.status === 'no_match')
136
+ .filter((f) => (0, missingReceiptsViewState_1.isUnmatchedTabFileStatus)(f.status))
136
137
  .map((f) => resolveBatchFile(f, b.batchId, transactionState));
137
138
  });
138
139
  const completedTransactionsCacheKey = monthYearPeriodId != null
@@ -206,7 +207,7 @@ function getExpenseAutomationMissingReceiptsView(state) {
206
207
  };
207
208
  }
208
209
  function batchDetailsHasUnmatchedWork(details) {
209
- return details.files.some((f) => f.status === 'un_matched' || f.status === 'no_match');
210
+ return details.files.some((f) => (0, missingReceiptsViewState_1.isUnmatchedTabFileStatus)(f.status));
210
211
  }
211
212
  /** List row summary before batch details load — aligns with unmatched / possible-match work. */
212
213
  function batchListItemHasUnmatchedWork(batch) {
@@ -13,8 +13,15 @@ export interface MissingReceiptsViewUIState {
13
13
  sortKey: MissingReceiptsSortKey;
14
14
  sortOrder: SortOrder;
15
15
  }
16
+ /**
17
+ * Minimum trimmed query length before manual-match search calls the expense-automation API.
18
+ * Kept in sync with `ManualSearchInput` in web-components (fires search only when length ≥ this).
19
+ */
20
+ export declare const MIN_MANUAL_TRANSACTION_SEARCH_LENGTH = 2;
16
21
  export type BatchStatusValue = 'pending' | 'processing' | 'completed';
17
- export type BatchFileStatus = 'matched' | 'un_matched' | 'no_match' | 'failed';
22
+ export type BatchFileStatus = 'exact_match' | 'possible_matches' | 'no_match' | 'failed';
23
+ /** True for files that belong in the Unmatched tab (possible matches or no match). */
24
+ export declare function isUnmatchedTabFileStatus(status: BatchFileStatus): boolean;
18
25
  export type BulkUploadPhase = 'idle' | 'uploading' | 'matching' | 'completed';
19
26
  export type BulkUploadResultsTab = 'completed' | 'unmatched';
20
27
  export type MissingReceiptsTab = 'missing' | BulkUploadResultsTab;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toMissingReceiptsSortKey = void 0;
3
+ exports.MIN_MANUAL_TRANSACTION_SEARCH_LENGTH = exports.toMissingReceiptsSortKey = void 0;
4
+ exports.isUnmatchedTabFileStatus = isUnmatchedTabFileStatus;
4
5
  const stringToUnion_1 = require("../../../commonStateTypes/stringToUnion");
5
6
  const MISSING_RECEIPTS_SORT_KEYS = [
6
7
  'vendor',
@@ -11,3 +12,13 @@ const MISSING_RECEIPTS_SORT_KEYS = [
11
12
  ];
12
13
  const toMissingReceiptsSortKey = (v) => (0, stringToUnion_1.stringToUnion)(v, MISSING_RECEIPTS_SORT_KEYS);
13
14
  exports.toMissingReceiptsSortKey = toMissingReceiptsSortKey;
15
+ // -- Bulk Receipt Upload Types --
16
+ /**
17
+ * Minimum trimmed query length before manual-match search calls the expense-automation API.
18
+ * Kept in sync with `ManualSearchInput` in web-components (fires search only when length ≥ this).
19
+ */
20
+ exports.MIN_MANUAL_TRANSACTION_SEARCH_LENGTH = 2;
21
+ /** True for files that belong in the Unmatched tab (possible matches or no match). */
22
+ function isUnmatchedTabFileStatus(status) {
23
+ return status === 'possible_matches' || status === 'no_match';
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "4.19.84-betaVR1",
3
+ "version": "4.19.84-betaVR10",
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",
@@ -111,7 +111,7 @@
111
111
  "find-dead-code": "ts-prune | grep -v '(used in module)'",
112
112
  "find-unused-exports": "ts-unused-exports ./tsconfig.json",
113
113
  "circular-dependency": "npx madge --circular --extensions ts ./src",
114
- "build": "concurrently --kill-others-on-fail --handle-input \"echo 'Running ESLint...' && eslint . --ext .js,.jsx,.ts,.tsx\" \"echo 'TypeScript build in progress...' && time tsc && echo 'ESM build...' && tsc -p tsconfig.esm.json\"",
114
+ "build": "concurrently --kill-others-on-fail --handle-input \"echo 'Running ESLint...' && eslint . --ext .js,.jsx,.ts,.tsx\" \"echo 'TypeScript build in progress...' && time tsc && echo 'ESM build...' && tsc -p tsconfig.esm.json\" \"echo 'Running tests and typecheck...' && pnpm test\" && rimraf \"lib/**/__mocks__\" \"lib/**/__testHelpers__\"",
115
115
  "only-build": "eslint . --ext .js,.jsx,.ts,.tsx && time tsc && tsc -p tsconfig.esm.json && rimraf \"lib/**/__mocks__\" \"lib/**/__testHelpers__\"",
116
116
  "only-build-dev": "eslint . --ext .js,.jsx,.ts,.tsx && time tsc -p tsconfig.dev.json && tsc -p tsconfig.esm.dev.json && rimraf \"lib/**/__mocks__\" \"lib/**/__testHelpers__\"",
117
117
  "format": "prettier --write --ignore-unknown \"src/**/*\" && pnpm lint",