@zeniai/client-epic-state 5.1.25 → 5.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -91,6 +91,7 @@ Testing types with `tsc` and Vitest is an experimental feature. Breaking changes
91
91
  - `pnpm clean-resolutions` — Cleans up resolutions using a custom script.
92
92
  - `pnpm create-or-update-release-branch` — Runs the release branch creation/update script.
93
93
  - `pnpm bump-versions-master` — Bumps versions and publishes all client repos on master end-to-end (per-repo PR + auto-merge + publish; v2 bumps only, no deploy).
94
+ - `pnpm bump-versions-beta` — Selectable upstream→downstream beta publish chain (CES / analytics / web-components / app-ui / cockpit) tagged `beta<initials>`. v2 step is a local-only dep repointing (no commit, no publish). Single-popup config.
94
95
  - `pnpm bump-update-web-app-cockpit-beta` — Runs the cockpit beta update script.
95
96
  - `pnpm send-release-notes` — Sends release notes using a custom script.
96
97
  - `pnpm update-slack-group-topic` — Updates the Slack group topic using a custom script.
@@ -19,6 +19,7 @@ export interface TransactionPayload extends TransactionIDPayload, AccountBasePay
19
19
  /** Transactions list API: preferred debit integration id for transfer flows */
20
20
  account_integration_id?: string | null;
21
21
  accounting_project_id?: string | null;
22
+ accounting_project_integration_id?: string | null;
22
23
  accounting_project_name?: string | null;
23
24
  amount?: number;
24
25
  attachments?: AttachmentPayload[];
@@ -105,6 +105,11 @@ const toTransaction = (payload) => {
105
105
  payload.accounting_project_id !== ''
106
106
  ? { projectId: payload.accounting_project_id }
107
107
  : {}),
108
+ ...(payload.accounting_project_integration_id != null
109
+ ? {
110
+ accountingProviderProjectId: payload.accounting_project_integration_id,
111
+ }
112
+ : {}),
108
113
  ...(payload.accounting_project_name != null &&
109
114
  payload.accounting_project_name !== ''
110
115
  ? { projectName: payload.accounting_project_name }
@@ -25,6 +25,7 @@ export interface Transaction extends BaseTransaction {
25
25
  createTime: ZeniDate;
26
26
  description: string;
27
27
  isUpdatingTransaction: boolean;
28
+ accountingProviderProjectId?: ID;
28
29
  accountIntegrationId?: string;
29
30
  attachments?: Attachment[];
30
31
  bookCloseDate?: ZeniDate;
@@ -101,6 +101,11 @@ export const toTransaction = (payload) => {
101
101
  payload.accounting_project_id !== ''
102
102
  ? { projectId: payload.accounting_project_id }
103
103
  : {}),
104
+ ...(payload.accounting_project_integration_id != null
105
+ ? {
106
+ accountingProviderProjectId: payload.accounting_project_integration_id,
107
+ }
108
+ : {}),
104
109
  ...(payload.accounting_project_name != null &&
105
110
  payload.accounting_project_name !== ''
106
111
  ? { projectName: payload.accounting_project_name }
@@ -2,6 +2,7 @@ import recordGet from 'lodash/get';
2
2
  import { from } from 'rxjs';
3
3
  import { filter, mergeMap } from 'rxjs/operators';
4
4
  import { getSupportedTransactionById } from '../../../entity/transaction/transactionSelector';
5
+ import { getIsAccountingProjectsEnabled } from '../../../entity/tenant/tenantSelector';
5
6
  import { saveTransactionDetail, updateTransactionDetail, } from '../transactionDetailReducer';
6
7
  import { getTransactionDetailKey } from '../transactionDetailState';
7
8
  import { toTransactionUpdatesFromTransactionDetailLocalData } from './saveTransactionHelper';
@@ -10,12 +11,13 @@ export const saveTransactionDetailEpic = (actions$, state$) => actions$.pipe(fil
10
11
  const fetchActions = [];
11
12
  const transactionDetailState = state$.value.transactionDetailState;
12
13
  const transactionState = state$.value.transactionState;
14
+ const isAccountingProjectsEnabled = getIsAccountingProjectsEnabled(state$.value);
13
15
  const key = getTransactionDetailKey(transactionId);
14
16
  const detail = recordGet(transactionDetailState.transactionDetailById, key, undefined);
15
17
  const transaction = getSupportedTransactionById(transactionState, key);
16
18
  if (detail != null && transaction != null) {
17
19
  const localData = detail.transactionDetailLocalData;
18
- const transactionSaveUpdates = toTransactionUpdatesFromTransactionDetailLocalData(localData, transaction, isExpenseAutomationEnabled);
20
+ const transactionSaveUpdates = toTransactionUpdatesFromTransactionDetailLocalData(localData, transaction, isExpenseAutomationEnabled, isAccountingProjectsEnabled);
19
21
  fetchActions.push(updateTransactionDetail(transactionId, transactionSaveUpdates, isExpenseAutomationEnabled, cotTransactionTracking, isUncategorizedExpenseCategoryEnabled));
20
22
  return from(fetchActions);
21
23
  }
@@ -21,7 +21,7 @@ import { isAccountUncategorized } from '../transactionDetailLocalDataHelper';
21
21
  * 4. Only difference between this and Miscategorized will be that we will
22
22
  * additionally fire “Mark as not miscat” for every point above.
23
23
  */
24
- export const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDetailLocalData, transaction, isExpenseAutomationEnabled) => {
24
+ export const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDetailLocalData, transaction, isExpenseAutomationEnabled, isAccountingProjectsEnabled = false) => {
25
25
  const updates = {
26
26
  updatesByLineId: {},
27
27
  };
@@ -30,6 +30,12 @@ export const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDe
30
30
  const isTransactionMisCategorized = isTransactionMiscategorized(transaction, isExpenseAutomationEnabled);
31
31
  updates.memo = transactionDetailLocalData.memo;
32
32
  updates.documentId = transactionDetailLocalData.documentId;
33
+ // Project is at the transaction level only for invoice and credit_memo,
34
+ // and only when the projects feature is enabled for this tenant.
35
+ if (isAccountingProjectsEnabled &&
36
+ (transaction.type === 'invoice' || transaction.type === 'credit_memo')) {
37
+ updates.project = transactionDetailLocalData.project;
38
+ }
33
39
  const selectedEntity = transactionDetailLocalData.selectedEntity;
34
40
  const updatedEntityId = transactionDetailLocalData.updatePreviousTransactionsWithUpdatedVendor ===
35
41
  true
@@ -52,7 +58,10 @@ export const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDe
52
58
  lineDescription: lineItem.lineDescription,
53
59
  class: lineItem.class,
54
60
  account: lineItem.account,
55
- project: lineItem.project,
61
+ // Project is at the transaction level for invoice/credit_memo; omit from lines.
62
+ ...(transaction.type !== 'invoice' && transaction.type !== 'credit_memo'
63
+ ? { project: lineItem.project }
64
+ : {}),
56
65
  recommendation: convertCOTRecommendationsToRegular(lineItem.recommendationsWithCOT),
57
66
  };
58
67
  if (line.type === 'journal_entry_transaction_line' ||
@@ -19,6 +19,7 @@ import { getSupportedTransactionById } from '../../../entity/transaction/transac
19
19
  import { updateVendors } from '../../../entity/vendor/vendorReducer';
20
20
  import { getVendorByVendorId } from '../../../entity/vendor/vendorSelector';
21
21
  import { createZeniAPIStatus, isActionNotAllowed, isSuccessResponse, } from '../../../responsePayload';
22
+ import { getIsAccountingProjectsEnabled } from '../../../entity/tenant/tenantSelector';
22
23
  import { getUncategorizedAccounts } from '../../accountList/accountListSelector';
23
24
  import { getPendingReviewLineIdsFromTransaction } from '../../expenseAutomationView/helpers/transactionCategorizationLocalDataHelper';
24
25
  import { syncTransactionCategorizationFromDetailSave } from '../../expenseAutomationView/reducers/transactionsViewReducer';
@@ -27,9 +28,10 @@ import { getTransactionDetailKey, } from '../transactionDetailState';
27
28
  import { isTransactionUncategorized } from './saveTransactionHelper';
28
29
  export const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(updateTransactionDetail.match), mergeMap((action) => {
29
30
  const { transactionState, vendorState, transactionDetailState } = state$.value;
31
+ const isAccountingProjectsEnabled = getIsAccountingProjectsEnabled(state$.value);
30
32
  const { cotTransactionTracking, transactionId, transactionSaveUpdates, isUncategorizedExpenseCategoryEnabled, } = action.payload;
31
33
  return zeniAPI
32
- .putAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/transactions/${action.payload.transactionId.id}/categorize`, preparePayload(transactionState, vendorState, transactionDetailState, transactionId, transactionSaveUpdates, cotTransactionTracking))
34
+ .putAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/transactions/${action.payload.transactionId.id}/categorize`, preparePayload(transactionState, vendorState, transactionDetailState, transactionId, transactionSaveUpdates, cotTransactionTracking, isAccountingProjectsEnabled))
33
35
  .pipe(mergeMap((response) => {
34
36
  if (isSuccessResponse(response) && response.data != null) {
35
37
  const transactionIdResp = toTransactionID(response.data.transaction);
@@ -123,7 +125,7 @@ export const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => action
123
125
  }
124
126
  }), catchError((error) => of(updateTransactionDetailSaveStatus(action.payload.transactionId, 'Error', createZeniAPIStatus('Unexpected Error', `REST API call errored out ${error}`)))));
125
127
  }));
126
- function preparePayload(transactionState, vendorState, transactionDetailState, transactionId, saveUpdates, cotTransactionTracking) {
128
+ function preparePayload(transactionState, vendorState, transactionDetailState, transactionId, saveUpdates, cotTransactionTracking, isAccountingProjectsEnabled) {
127
129
  const transactionDetailKey = getTransactionDetailKey(transactionId);
128
130
  const detail = getSupportedTransactionById(transactionState, transactionDetailKey);
129
131
  const tranDetail = recordGet(transactionDetailState.transactionDetailById, transactionDetailKey, undefined);
@@ -155,7 +157,7 @@ function preparePayload(transactionState, vendorState, transactionDetailState, t
155
157
  };
156
158
  }
157
159
  if (detail != null) {
158
- const transUpdates = toTransactionUpdatePayload(saveUpdates.updates, detail, cotTransactionTracking, vendorUpdates, customerUpdates, currentSelectedEntity);
160
+ const transUpdates = toTransactionUpdatePayload(saveUpdates.updates, detail, cotTransactionTracking, isAccountingProjectsEnabled, vendorUpdates, customerUpdates, currentSelectedEntity);
159
161
  const entityUpdates = toEntityUpdatePayload(saveUpdates.entityRelatedUpdates);
160
162
  return {
161
163
  entity_related_updates: entityUpdates,
@@ -165,7 +167,7 @@ function preparePayload(transactionState, vendorState, transactionDetailState, t
165
167
  }
166
168
  return undefined;
167
169
  }
168
- const toTransactionUpdatePayload = (updates, detail, cotTransactionTracking, vendorUpdates, customerUpdates, tranSelectedEntity) => {
170
+ export const toTransactionUpdatePayload = (updates, detail, cotTransactionTracking, isAccountingProjectsEnabled, vendorUpdates, customerUpdates, tranSelectedEntity) => {
169
171
  const transaction = Object.assign({}, detail);
170
172
  transaction.memo = updates.memo ?? '';
171
173
  // Unlike `memo` (always reset to '' when absent), `documentId` falls back to
@@ -226,6 +228,31 @@ const toTransactionUpdatePayload = (updates, detail, cotTransactionTracking, ven
226
228
  let basePayload;
227
229
  if (isCustomerTransaction(transaction)) {
228
230
  basePayload = toCustomerTransactionPayload(transaction);
231
+ // For invoice/credit_memo only, project lives at the transaction level.
232
+ // Guard behind the projects feature flag to avoid touching the field when
233
+ // projects are disabled for this tenant. Clear-project (null injection) is
234
+ // intentionally out of scope for this change — the null write-back path will
235
+ // be added in a follow-up once the backend contract is confirmed.
236
+ if (isAccountingProjectsEnabled &&
237
+ (transaction.type === 'invoice' || transaction.type === 'credit_memo') &&
238
+ updates.project != null) {
239
+ basePayload = {
240
+ ...basePayload,
241
+ accounting_project_id: updates.project.projectId,
242
+ // Only spread integration_id / name when we have a concrete value; omitting
243
+ // them avoids accidentally nulling/blanking out the server-side value when
244
+ // the load path didn't surface the field (e.g. list-shaped data or pre-deploy
245
+ // store where the API returned projectId but not the name/integration_id).
246
+ ...(updates.project.accountingProviderProjectId != null
247
+ ? {
248
+ accounting_project_integration_id: updates.project.accountingProviderProjectId,
249
+ }
250
+ : {}),
251
+ ...(updates.project.projectName
252
+ ? { accounting_project_name: updates.project.projectName }
253
+ : {}),
254
+ };
255
+ }
229
256
  }
230
257
  else if (isVendorTransaction(transaction)) {
231
258
  if (tranSelectedEntity != null && tranSelectedEntity.type === 'customer') {
@@ -12,6 +12,17 @@ export const toTransactionDetailLocalData = (transaction, defaultVendorId, uncat
12
12
  const vendorId = defaultVendorId ?? transaction.vendor?.id;
13
13
  transactionLocal.memo = transaction.memo;
14
14
  transactionLocal.documentId = transaction.documentId;
15
+ // Project is at the transaction level only for invoice and credit_memo.
16
+ if ((transaction.type === 'invoice' || transaction.type === 'credit_memo') &&
17
+ transaction.projectId != null) {
18
+ transactionLocal.project = {
19
+ projectId: transaction.projectId,
20
+ projectName: transaction.projectName ?? '',
21
+ ...(transaction.accountingProviderProjectId != null
22
+ ? { accountingProviderProjectId: transaction.accountingProviderProjectId }
23
+ : {}),
24
+ };
25
+ }
15
26
  transactionLocal.vendorUpdates =
16
27
  isVendorTransaction(transaction) && vendorId != null
17
28
  ? {
@@ -423,6 +434,7 @@ export const toUpdateLineItemsOriginalCategoryClass = (transaction, transactionD
423
434
  ...initialSupportedTransactionDetail.transactionDetailLocalData,
424
435
  memo: transactionDetailLocalData.memo,
425
436
  documentId: transactionDetailLocalData.documentId,
437
+ project: transactionDetailLocalData.project,
426
438
  lineItemById: newLineItems,
427
439
  vendorDetails: transactionDetailLocalData.vendorDetails,
428
440
  vendorUpdates: transactionDetailLocalData.vendorUpdates,
@@ -527,6 +539,7 @@ export const toSetAllLineItemsToCategoryClass = (transaction, transactionDetailL
527
539
  ...initialSupportedTransactionDetail.transactionDetailLocalData,
528
540
  memo: transactionDetailLocalData.memo,
529
541
  documentId: transactionDetailLocalData.documentId,
542
+ project: transactionDetailLocalData.project,
530
543
  lineItemById: newLineItems,
531
544
  vendorUpdates: transactionDetailLocalData.vendorUpdates,
532
545
  vendorDetails: transactionDetailLocalData.vendorDetails,
@@ -572,6 +585,7 @@ export const toSetAllLineItemsLocalDataIsUpdateAllChecked = (transaction, transa
572
585
  ...initialSupportedTransactionDetail.transactionDetailLocalData,
573
586
  memo: transactionDetailLocalData.memo,
574
587
  documentId: transactionDetailLocalData.documentId,
588
+ project: transactionDetailLocalData.project,
575
589
  lineItemById: newLineItems,
576
590
  vendorDetails: transactionDetailLocalData.vendorDetails,
577
591
  vendorUpdates: transactionDetailLocalData.vendorUpdates,
@@ -729,6 +743,7 @@ export const setEntityRecommendationForLineIdInLocalData = (transaction, transac
729
743
  ...initialSupportedTransactionDetail.transactionDetailLocalData,
730
744
  memo: transactionDetailLocalData.memo,
731
745
  documentId: transactionDetailLocalData.documentId,
746
+ project: transactionDetailLocalData.project,
732
747
  lineItemById: newLineItems,
733
748
  vendorUpdates: transactionDetailLocalData.vendorUpdates,
734
749
  vendorDetails: transactionDetailLocalData.vendorDetails,
@@ -782,6 +797,7 @@ export const removeEntityRecommendationForLineIdInLocalData = (transaction, tran
782
797
  ...initialSupportedTransactionDetail.transactionDetailLocalData,
783
798
  memo: transactionDetailLocalData.memo,
784
799
  documentId: transactionDetailLocalData.documentId,
800
+ project: transactionDetailLocalData.project,
785
801
  lineItemById: newLineItems,
786
802
  vendorDetails: transactionDetailLocalData.vendorDetails,
787
803
  vendorUpdates: transactionDetailLocalData.vendorUpdates,
@@ -1002,6 +1018,7 @@ export const toUpdateLineItemsToOriginalVendor = (transaction, transactionDetail
1002
1018
  ...initialSupportedTransactionDetail.transactionDetailLocalData,
1003
1019
  memo: transactionDetailLocalData.memo,
1004
1020
  documentId: transactionDetailLocalData.documentId,
1021
+ project: transactionDetailLocalData.project,
1005
1022
  lineItemById: newLineItems,
1006
1023
  isVendorUpdateAllChecked: false,
1007
1024
  latestUpdatedLineWithEntity: transactionDetailLocalData.latestUpdatedLineWithEntity,
@@ -8,6 +8,7 @@ const get_1 = __importDefault(require("lodash/get"));
8
8
  const rxjs_1 = require("rxjs");
9
9
  const operators_1 = require("rxjs/operators");
10
10
  const transactionSelector_1 = require("../../../entity/transaction/transactionSelector");
11
+ const tenantSelector_1 = require("../../../entity/tenant/tenantSelector");
11
12
  const transactionDetailReducer_1 = require("../transactionDetailReducer");
12
13
  const transactionDetailState_1 = require("../transactionDetailState");
13
14
  const saveTransactionHelper_1 = require("./saveTransactionHelper");
@@ -16,12 +17,13 @@ const saveTransactionDetailEpic = (actions$, state$) => actions$.pipe((0, operat
16
17
  const fetchActions = [];
17
18
  const transactionDetailState = state$.value.transactionDetailState;
18
19
  const transactionState = state$.value.transactionState;
20
+ const isAccountingProjectsEnabled = (0, tenantSelector_1.getIsAccountingProjectsEnabled)(state$.value);
19
21
  const key = (0, transactionDetailState_1.getTransactionDetailKey)(transactionId);
20
22
  const detail = (0, get_1.default)(transactionDetailState.transactionDetailById, key, undefined);
21
23
  const transaction = (0, transactionSelector_1.getSupportedTransactionById)(transactionState, key);
22
24
  if (detail != null && transaction != null) {
23
25
  const localData = detail.transactionDetailLocalData;
24
- const transactionSaveUpdates = (0, saveTransactionHelper_1.toTransactionUpdatesFromTransactionDetailLocalData)(localData, transaction, isExpenseAutomationEnabled);
26
+ const transactionSaveUpdates = (0, saveTransactionHelper_1.toTransactionUpdatesFromTransactionDetailLocalData)(localData, transaction, isExpenseAutomationEnabled, isAccountingProjectsEnabled);
25
27
  fetchActions.push((0, transactionDetailReducer_1.updateTransactionDetail)(transactionId, transactionSaveUpdates, isExpenseAutomationEnabled, cotTransactionTracking, isUncategorizedExpenseCategoryEnabled));
26
28
  return (0, rxjs_1.from)(fetchActions);
27
29
  }
@@ -21,7 +21,7 @@ import { TransactionDetailLocalData } from '../transactionDetailTypes';
21
21
  * 4. Only difference between this and Miscategorized will be that we will
22
22
  * additionally fire “Mark as not miscat” for every point above.
23
23
  */
24
- export declare const toTransactionUpdatesFromTransactionDetailLocalData: (transactionDetailLocalData: TransactionDetailLocalData, transaction: SupportedTransaction, isExpenseAutomationEnabled: boolean) => TransactionSaveUpdates;
24
+ export declare const toTransactionUpdatesFromTransactionDetailLocalData: (transactionDetailLocalData: TransactionDetailLocalData, transaction: SupportedTransaction, isExpenseAutomationEnabled: boolean, isAccountingProjectsEnabled?: boolean) => TransactionSaveUpdates;
25
25
  export declare const toRecommendedAccountBase: (amountValue: number, accountBase?: AccountBase, recommendationBase?: RecommendationBase) => RecommendedAccountBase | undefined;
26
26
  export declare const toRecommendedClassBase: (amountValue: number, classBase?: ClassBase, recommendationBase?: RecommendationBase) => RecommendedClassBase | undefined;
27
27
  export declare const isTransactionMiscategorized: (transaction: SupportedTransaction, isExpenseAutomationEnabled: boolean) => boolean;
@@ -24,7 +24,7 @@ const transactionDetailLocalDataHelper_1 = require("../transactionDetailLocalDat
24
24
  * 4. Only difference between this and Miscategorized will be that we will
25
25
  * additionally fire “Mark as not miscat” for every point above.
26
26
  */
27
- const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDetailLocalData, transaction, isExpenseAutomationEnabled) => {
27
+ const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDetailLocalData, transaction, isExpenseAutomationEnabled, isAccountingProjectsEnabled = false) => {
28
28
  const updates = {
29
29
  updatesByLineId: {},
30
30
  };
@@ -33,6 +33,12 @@ const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDetailLoc
33
33
  const isTransactionMisCategorized = (0, exports.isTransactionMiscategorized)(transaction, isExpenseAutomationEnabled);
34
34
  updates.memo = transactionDetailLocalData.memo;
35
35
  updates.documentId = transactionDetailLocalData.documentId;
36
+ // Project is at the transaction level only for invoice and credit_memo,
37
+ // and only when the projects feature is enabled for this tenant.
38
+ if (isAccountingProjectsEnabled &&
39
+ (transaction.type === 'invoice' || transaction.type === 'credit_memo')) {
40
+ updates.project = transactionDetailLocalData.project;
41
+ }
36
42
  const selectedEntity = transactionDetailLocalData.selectedEntity;
37
43
  const updatedEntityId = transactionDetailLocalData.updatePreviousTransactionsWithUpdatedVendor ===
38
44
  true
@@ -55,7 +61,10 @@ const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDetailLoc
55
61
  lineDescription: lineItem.lineDescription,
56
62
  class: lineItem.class,
57
63
  account: lineItem.account,
58
- project: lineItem.project,
64
+ // Project is at the transaction level for invoice/credit_memo; omit from lines.
65
+ ...(transaction.type !== 'invoice' && transaction.type !== 'credit_memo'
66
+ ? { project: lineItem.project }
67
+ : {}),
59
68
  recommendation: (0, transactionHelper_1.convertCOTRecommendationsToRegular)(lineItem.recommendationsWithCOT),
60
69
  };
61
70
  if (line.type === 'journal_entry_transaction_line' ||
@@ -1,10 +1,18 @@
1
1
  import { ActionsObservable, StateObservable } from 'redux-observable';
2
+ import { COTTransactionTracking } from '../../../commonStateTypes/recommendationBase';
3
+ import { CustomerBase } from '../../../entity/customer/customerState';
4
+ import { Entity } from '../../../entity/genericEntity/entity';
2
5
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
6
+ import { TransactionPayload } from '../../../entity/transaction/payloadTypes/transactionPayload';
3
7
  import { updateTransaction, updateTransactions } from '../../../entity/transaction/transactionReducer';
8
+ import { SupportedTransaction } from '../../../entity/transaction/transactionState';
4
9
  import { updateVendors } from '../../../entity/vendor/vendorReducer';
10
+ import { VendorBase } from '../../../entity/vendor/vendorState';
5
11
  import { RootState } from '../../../reducer';
6
12
  import { ZeniAPI } from '../../../zeniAPI';
7
13
  import { syncTransactionCategorizationFromDetailSave } from '../../expenseAutomationView/reducers/transactionsViewReducer';
8
14
  import { initializeTransactionDetailLocalData, markTransactionAsNotMiscategorized, updateStatusConsolidatedSaveTransactionDetail, updateTransactionDetail, updateTransactionDetailSaveStatus, updateTransactionStateIfUpdateActionNotAllowed } from '../transactionDetailReducer';
15
+ import { TransactionUpdates } from '../transactionDetailState';
9
16
  export type ActionType = ReturnType<typeof updateTransactionDetail> | ReturnType<typeof updateTransaction> | ReturnType<typeof updateTransactions> | ReturnType<typeof updateTransactionStateIfUpdateActionNotAllowed> | ReturnType<typeof openSnackbar> | ReturnType<typeof updateTransactionDetailSaveStatus> | ReturnType<typeof updateStatusConsolidatedSaveTransactionDetail> | ReturnType<typeof initializeTransactionDetailLocalData> | ReturnType<typeof updateVendors> | ReturnType<typeof markTransactionAsNotMiscategorized> | ReturnType<typeof syncTransactionCategorizationFromDetailSave>;
10
17
  export declare const updateTransactionDetailEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => import("rxjs").Observable<ActionType>;
18
+ export declare const toTransactionUpdatePayload: (updates: TransactionUpdates, detail: SupportedTransaction, cotTransactionTracking: COTTransactionTracking, isAccountingProjectsEnabled: boolean, vendorUpdates?: VendorBase, customerUpdates?: CustomerBase, tranSelectedEntity?: Entity) => Partial<TransactionPayload>;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.updateTransactionDetailEpic = void 0;
6
+ exports.toTransactionUpdatePayload = exports.updateTransactionDetailEpic = void 0;
7
7
  const get_1 = __importDefault(require("lodash/get"));
8
8
  const rxjs_1 = require("rxjs");
9
9
  const operators_1 = require("rxjs/operators");
@@ -25,6 +25,7 @@ const transactionSelector_1 = require("../../../entity/transaction/transactionSe
25
25
  const vendorReducer_1 = require("../../../entity/vendor/vendorReducer");
26
26
  const vendorSelector_1 = require("../../../entity/vendor/vendorSelector");
27
27
  const responsePayload_1 = require("../../../responsePayload");
28
+ const tenantSelector_1 = require("../../../entity/tenant/tenantSelector");
28
29
  const accountListSelector_1 = require("../../accountList/accountListSelector");
29
30
  const transactionCategorizationLocalDataHelper_1 = require("../../expenseAutomationView/helpers/transactionCategorizationLocalDataHelper");
30
31
  const transactionsViewReducer_1 = require("../../expenseAutomationView/reducers/transactionsViewReducer");
@@ -33,9 +34,10 @@ const transactionDetailState_1 = require("../transactionDetailState");
33
34
  const saveTransactionHelper_1 = require("./saveTransactionHelper");
34
35
  const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(transactionDetailReducer_1.updateTransactionDetail.match), (0, operators_1.mergeMap)((action) => {
35
36
  const { transactionState, vendorState, transactionDetailState } = state$.value;
37
+ const isAccountingProjectsEnabled = (0, tenantSelector_1.getIsAccountingProjectsEnabled)(state$.value);
36
38
  const { cotTransactionTracking, transactionId, transactionSaveUpdates, isUncategorizedExpenseCategoryEnabled, } = action.payload;
37
39
  return zeniAPI
38
- .putAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/transactions/${action.payload.transactionId.id}/categorize`, preparePayload(transactionState, vendorState, transactionDetailState, transactionId, transactionSaveUpdates, cotTransactionTracking))
40
+ .putAndGetJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/transactions/${action.payload.transactionId.id}/categorize`, preparePayload(transactionState, vendorState, transactionDetailState, transactionId, transactionSaveUpdates, cotTransactionTracking, isAccountingProjectsEnabled))
39
41
  .pipe((0, operators_1.mergeMap)((response) => {
40
42
  if ((0, responsePayload_1.isSuccessResponse)(response) && response.data != null) {
41
43
  const transactionIdResp = (0, transactionIDPayload_1.toTransactionID)(response.data.transaction);
@@ -130,7 +132,7 @@ const updateTransactionDetailEpic = (actions$, state$, zeniAPI) => actions$.pipe
130
132
  }), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, transactionDetailReducer_1.updateTransactionDetailSaveStatus)(action.payload.transactionId, 'Error', (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', `REST API call errored out ${error}`)))));
131
133
  }));
132
134
  exports.updateTransactionDetailEpic = updateTransactionDetailEpic;
133
- function preparePayload(transactionState, vendorState, transactionDetailState, transactionId, saveUpdates, cotTransactionTracking) {
135
+ function preparePayload(transactionState, vendorState, transactionDetailState, transactionId, saveUpdates, cotTransactionTracking, isAccountingProjectsEnabled) {
134
136
  const transactionDetailKey = (0, transactionDetailState_1.getTransactionDetailKey)(transactionId);
135
137
  const detail = (0, transactionSelector_1.getSupportedTransactionById)(transactionState, transactionDetailKey);
136
138
  const tranDetail = (0, get_1.default)(transactionDetailState.transactionDetailById, transactionDetailKey, undefined);
@@ -162,7 +164,7 @@ function preparePayload(transactionState, vendorState, transactionDetailState, t
162
164
  };
163
165
  }
164
166
  if (detail != null) {
165
- const transUpdates = toTransactionUpdatePayload(saveUpdates.updates, detail, cotTransactionTracking, vendorUpdates, customerUpdates, currentSelectedEntity);
167
+ const transUpdates = (0, exports.toTransactionUpdatePayload)(saveUpdates.updates, detail, cotTransactionTracking, isAccountingProjectsEnabled, vendorUpdates, customerUpdates, currentSelectedEntity);
166
168
  const entityUpdates = toEntityUpdatePayload(saveUpdates.entityRelatedUpdates);
167
169
  return {
168
170
  entity_related_updates: entityUpdates,
@@ -172,7 +174,7 @@ function preparePayload(transactionState, vendorState, transactionDetailState, t
172
174
  }
173
175
  return undefined;
174
176
  }
175
- const toTransactionUpdatePayload = (updates, detail, cotTransactionTracking, vendorUpdates, customerUpdates, tranSelectedEntity) => {
177
+ const toTransactionUpdatePayload = (updates, detail, cotTransactionTracking, isAccountingProjectsEnabled, vendorUpdates, customerUpdates, tranSelectedEntity) => {
176
178
  const transaction = Object.assign({}, detail);
177
179
  transaction.memo = updates.memo ?? '';
178
180
  // Unlike `memo` (always reset to '' when absent), `documentId` falls back to
@@ -233,6 +235,31 @@ const toTransactionUpdatePayload = (updates, detail, cotTransactionTracking, ven
233
235
  let basePayload;
234
236
  if ((0, customerTransaction_1.isCustomerTransaction)(transaction)) {
235
237
  basePayload = (0, customerTransactionPayload_1.toCustomerTransactionPayload)(transaction);
238
+ // For invoice/credit_memo only, project lives at the transaction level.
239
+ // Guard behind the projects feature flag to avoid touching the field when
240
+ // projects are disabled for this tenant. Clear-project (null injection) is
241
+ // intentionally out of scope for this change — the null write-back path will
242
+ // be added in a follow-up once the backend contract is confirmed.
243
+ if (isAccountingProjectsEnabled &&
244
+ (transaction.type === 'invoice' || transaction.type === 'credit_memo') &&
245
+ updates.project != null) {
246
+ basePayload = {
247
+ ...basePayload,
248
+ accounting_project_id: updates.project.projectId,
249
+ // Only spread integration_id / name when we have a concrete value; omitting
250
+ // them avoids accidentally nulling/blanking out the server-side value when
251
+ // the load path didn't surface the field (e.g. list-shaped data or pre-deploy
252
+ // store where the API returned projectId but not the name/integration_id).
253
+ ...(updates.project.accountingProviderProjectId != null
254
+ ? {
255
+ accounting_project_integration_id: updates.project.accountingProviderProjectId,
256
+ }
257
+ : {}),
258
+ ...(updates.project.projectName
259
+ ? { accounting_project_name: updates.project.projectName }
260
+ : {}),
261
+ };
262
+ }
236
263
  }
237
264
  else if ((0, vendorTransaction_1.isVendorTransaction)(transaction)) {
238
265
  if (tranSelectedEntity != null && tranSelectedEntity.type === 'customer') {
@@ -251,6 +278,7 @@ const toTransactionUpdatePayload = (updates, detail, cotTransactionTracking, ven
251
278
  // Inject COT flags into the payload
252
279
  return (0, transactionPayload_1.injectCOTFlagsIntoTransactionPayload)(basePayload, transaction);
253
280
  };
281
+ exports.toTransactionUpdatePayload = toTransactionUpdatePayload;
254
282
  const checkIfClassMissingForAnyLineItem = (transactionPayload) => {
255
283
  let isAnyClassMissing = false;
256
284
  if (transactionPayload.lines != null) {
@@ -15,6 +15,17 @@ const toTransactionDetailLocalData = (transaction, defaultVendorId, uncategorize
15
15
  const vendorId = defaultVendorId ?? transaction.vendor?.id;
16
16
  transactionLocal.memo = transaction.memo;
17
17
  transactionLocal.documentId = transaction.documentId;
18
+ // Project is at the transaction level only for invoice and credit_memo.
19
+ if ((transaction.type === 'invoice' || transaction.type === 'credit_memo') &&
20
+ transaction.projectId != null) {
21
+ transactionLocal.project = {
22
+ projectId: transaction.projectId,
23
+ projectName: transaction.projectName ?? '',
24
+ ...(transaction.accountingProviderProjectId != null
25
+ ? { accountingProviderProjectId: transaction.accountingProviderProjectId }
26
+ : {}),
27
+ };
28
+ }
18
29
  transactionLocal.vendorUpdates =
19
30
  (0, vendorTransaction_1.isVendorTransaction)(transaction) && vendorId != null
20
31
  ? {
@@ -429,6 +440,7 @@ const toUpdateLineItemsOriginalCategoryClass = (transaction, transactionDetailLo
429
440
  ...transactionDetailState_1.initialSupportedTransactionDetail.transactionDetailLocalData,
430
441
  memo: transactionDetailLocalData.memo,
431
442
  documentId: transactionDetailLocalData.documentId,
443
+ project: transactionDetailLocalData.project,
432
444
  lineItemById: newLineItems,
433
445
  vendorDetails: transactionDetailLocalData.vendorDetails,
434
446
  vendorUpdates: transactionDetailLocalData.vendorUpdates,
@@ -534,6 +546,7 @@ const toSetAllLineItemsToCategoryClass = (transaction, transactionDetailLocalDat
534
546
  ...transactionDetailState_1.initialSupportedTransactionDetail.transactionDetailLocalData,
535
547
  memo: transactionDetailLocalData.memo,
536
548
  documentId: transactionDetailLocalData.documentId,
549
+ project: transactionDetailLocalData.project,
537
550
  lineItemById: newLineItems,
538
551
  vendorUpdates: transactionDetailLocalData.vendorUpdates,
539
552
  vendorDetails: transactionDetailLocalData.vendorDetails,
@@ -580,6 +593,7 @@ const toSetAllLineItemsLocalDataIsUpdateAllChecked = (transaction, transactionDe
580
593
  ...transactionDetailState_1.initialSupportedTransactionDetail.transactionDetailLocalData,
581
594
  memo: transactionDetailLocalData.memo,
582
595
  documentId: transactionDetailLocalData.documentId,
596
+ project: transactionDetailLocalData.project,
583
597
  lineItemById: newLineItems,
584
598
  vendorDetails: transactionDetailLocalData.vendorDetails,
585
599
  vendorUpdates: transactionDetailLocalData.vendorUpdates,
@@ -738,6 +752,7 @@ const setEntityRecommendationForLineIdInLocalData = (transaction, transactionDet
738
752
  ...transactionDetailState_1.initialSupportedTransactionDetail.transactionDetailLocalData,
739
753
  memo: transactionDetailLocalData.memo,
740
754
  documentId: transactionDetailLocalData.documentId,
755
+ project: transactionDetailLocalData.project,
741
756
  lineItemById: newLineItems,
742
757
  vendorUpdates: transactionDetailLocalData.vendorUpdates,
743
758
  vendorDetails: transactionDetailLocalData.vendorDetails,
@@ -792,6 +807,7 @@ const removeEntityRecommendationForLineIdInLocalData = (transaction, transaction
792
807
  ...transactionDetailState_1.initialSupportedTransactionDetail.transactionDetailLocalData,
793
808
  memo: transactionDetailLocalData.memo,
794
809
  documentId: transactionDetailLocalData.documentId,
810
+ project: transactionDetailLocalData.project,
795
811
  lineItemById: newLineItems,
796
812
  vendorDetails: transactionDetailLocalData.vendorDetails,
797
813
  vendorUpdates: transactionDetailLocalData.vendorUpdates,
@@ -1015,6 +1031,7 @@ const toUpdateLineItemsToOriginalVendor = (transaction, transactionDetailLocalDa
1015
1031
  ...transactionDetailState_1.initialSupportedTransactionDetail.transactionDetailLocalData,
1016
1032
  memo: transactionDetailLocalData.memo,
1017
1033
  documentId: transactionDetailLocalData.documentId,
1034
+ project: transactionDetailLocalData.project,
1018
1035
  lineItemById: newLineItems,
1019
1036
  isVendorUpdateAllChecked: false,
1020
1037
  latestUpdatedLineWithEntity: transactionDetailLocalData.latestUpdatedLineWithEntity,
@@ -62,6 +62,7 @@ export interface TransactionUpdates {
62
62
  customerId?: ID;
63
63
  documentId?: string;
64
64
  memo?: string;
65
+ project?: ProjectBase;
65
66
  vendorId?: ID;
66
67
  }
67
68
  export interface EntityRelatedRecommendationUpdates {
@@ -54,6 +54,7 @@ export interface TransactionDetailLocalData {
54
54
  latestSelectedEntityOnLineItem?: Entity;
55
55
  latestUpdatedLineWithEntity?: ID;
56
56
  memo?: string;
57
+ project?: ProjectBase;
57
58
  selectedEntity?: Entity;
58
59
  updatePreviousTransactionsWithUpdatedVendor?: boolean;
59
60
  vendorDetails?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.1.25",
3
+ "version": "5.1.27",
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",
@@ -132,6 +132,7 @@
132
132
  "clean-overrides": "node ./scripts/clean_overrides.js",
133
133
  "create-or-update-release-branch": "chmod +x ./scripts/create_or_update_release_branch.sh && ./scripts/create_or_update_release_branch.sh",
134
134
  "bump-versions-master": "chmod +x ./scripts/bump_versions_master.sh && ./scripts/bump_versions_master.sh",
135
+ "bump-versions-beta": "chmod +x ./scripts/bump_versions_beta.sh && ./scripts/bump_versions_beta.sh",
135
136
  "bump-update-web-app-cockpit-beta": "chmod +x ./scripts/bump_and_update_web_app_ui_beta.sh && ./scripts/bump_and_update_web_app_ui_beta.sh",
136
137
  "send-release-notes": "chmod +x ./scripts/send_release_notes.sh && ./scripts/send_release_notes.sh",
137
138
  "update-slack-group-topic": "chmod +x ./scripts/update-slack-group-topic.sh && ./scripts/update-slack-group-topic.sh",