@zeniai/client-epic-state 5.1.24 → 5.1.25-betaRD2

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 (39) hide show
  1. package/README.md +2 -1
  2. package/lib/entity/transaction/payloadTypes/transactionPayload.d.ts +1 -0
  3. package/lib/entity/transaction/payloadTypes/transactionPayload.js +5 -0
  4. package/lib/entity/transaction/stateTypes/transaction.d.ts +1 -0
  5. package/lib/epic.d.ts +2 -1
  6. package/lib/epic.js +2 -1
  7. package/lib/esm/entity/transaction/payloadTypes/transactionPayload.js +5 -0
  8. package/lib/esm/epic.js +2 -1
  9. package/lib/esm/index.js +2 -2
  10. package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +29 -3
  11. package/lib/esm/view/aiCfoView/aiCfoViewSelector.js +11 -0
  12. package/lib/esm/view/aiCfoView/epics/createSessionAndSubmitEpic.js +2 -2
  13. package/lib/esm/view/aiCfoView/epics/fetchSkillsEpic.js +34 -0
  14. package/lib/esm/view/aiCfoView/epics/submitQuestionEpic.js +7 -1
  15. package/lib/esm/view/transactionDetail/epics/saveTransactionDetailEpic.js +3 -1
  16. package/lib/esm/view/transactionDetail/epics/saveTransactionHelper.js +11 -2
  17. package/lib/esm/view/transactionDetail/epics/updateTransactionDetailEpic.js +28 -4
  18. package/lib/esm/view/transactionDetail/transactionDetailLocalDataHelper.js +17 -0
  19. package/lib/index.d.ts +4 -4
  20. package/lib/index.js +9 -5
  21. package/lib/view/aiCfoView/aiCfoViewPayload.d.ts +14 -0
  22. package/lib/view/aiCfoView/aiCfoViewReducer.d.ts +8 -4
  23. package/lib/view/aiCfoView/aiCfoViewReducer.js +30 -4
  24. package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +2 -1
  25. package/lib/view/aiCfoView/aiCfoViewSelector.js +12 -0
  26. package/lib/view/aiCfoView/aiCfoViewState.d.ts +15 -0
  27. package/lib/view/aiCfoView/epics/createSessionAndSubmitEpic.js +2 -2
  28. package/lib/view/aiCfoView/epics/fetchSkillsEpic.d.ts +8 -0
  29. package/lib/view/aiCfoView/epics/fetchSkillsEpic.js +38 -0
  30. package/lib/view/aiCfoView/epics/submitQuestionEpic.js +7 -1
  31. package/lib/view/transactionDetail/epics/saveTransactionDetailEpic.js +3 -1
  32. package/lib/view/transactionDetail/epics/saveTransactionHelper.d.ts +1 -1
  33. package/lib/view/transactionDetail/epics/saveTransactionHelper.js +11 -2
  34. package/lib/view/transactionDetail/epics/updateTransactionDetailEpic.d.ts +8 -0
  35. package/lib/view/transactionDetail/epics/updateTransactionDetailEpic.js +30 -5
  36. package/lib/view/transactionDetail/transactionDetailLocalDataHelper.js +17 -0
  37. package/lib/view/transactionDetail/transactionDetailState.d.ts +1 -0
  38. package/lib/view/transactionDetail/transactionDetailTypes.d.ts +1 -0
  39. package/package.json +4 -2
@@ -69,7 +69,7 @@ function waitForChannelSubscription(pusherInstance, channelName, timeoutMs = 500
69
69
  }));
70
70
  }
71
71
  const createSessionAndSubmitEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(aiCfoViewReducer_1.createSessionAndSubmit.match), (0, operators_1.switchMap)((action) => {
72
- const { agentId, userId, questionAnswerId, questionId, createdAt, question, files, pageContext, } = action.payload;
72
+ const { agentId, userId, questionAnswerId, questionId, createdAt, question, files, pageContext, macroId, } = action.payload;
73
73
  const payload = {
74
74
  agent_id: agentId,
75
75
  ...(pageContext != null && { page_context: pageContext }),
@@ -116,7 +116,7 @@ const createSessionAndSubmitEpic = (actions$, _state$, zeniAPI) => actions$.pipe
116
116
  // This allows fetchChatHistory to run after the question is submitted
117
117
  (0, rxjs_1.of)((0, aiCfoViewReducer_1.createSessionAndSubmitSuccess)(),
118
118
  // Submit the question after handlers are bound
119
- (0, aiCfoViewReducer_1.submitQuestion)(sessionId, questionAnswerId, userId, questionId, createdAt, question, files),
119
+ (0, aiCfoViewReducer_1.submitQuestion)(sessionId, questionAnswerId, userId, questionId, createdAt, question, files, macroId),
120
120
  // Add question payload optimistically after submitQuestion (matches original order)
121
121
  // This is idempotent - won't create duplicates if called multiple times
122
122
  // submitQuestionEpic will find the question already exists and only update the response
@@ -0,0 +1,8 @@
1
+ import { ActionsObservable, StateObservable } from 'redux-observable';
2
+ import { Observable } from 'rxjs';
3
+ import { RootState } from '../../../reducer';
4
+ import { ZeniAPI } from '../../../zeniAPI';
5
+ import { fetchSkills, fetchSkillsFailure, fetchSkillsSuccess } from '../aiCfoViewReducer';
6
+ export type ActionType = ReturnType<typeof fetchSkills> | ReturnType<typeof fetchSkillsSuccess> | ReturnType<typeof fetchSkillsFailure>;
7
+ /** GET {aiCfoMicroServiceBaseUrl}/1.0/skills — chat-visible skills for the `/` menu. */
8
+ export declare const fetchSkillsEpic: (actions$: ActionsObservable<ActionType>, _state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchSkillsEpic = void 0;
4
+ const rxjs_1 = require("rxjs");
5
+ const operators_1 = require("rxjs/operators");
6
+ const responsePayload_1 = require("../../../responsePayload");
7
+ const aiCfoViewReducer_1 = require("../aiCfoViewReducer");
8
+ const toSkill = (s) => ({
9
+ macroId: s.macro_id,
10
+ name: s.name,
11
+ displayName: s.display_name,
12
+ description: s.description,
13
+ semver: s.semver,
14
+ parameterHints: s.parameter_hints,
15
+ hasScripts: s.has_scripts,
16
+ });
17
+ /** GET {aiCfoMicroServiceBaseUrl}/1.0/skills — chat-visible skills for the `/` menu. */
18
+ const fetchSkillsEpic = (actions$,
19
+ // state$ intentionally unused: the `/` menu dispatches this once per slash-entry
20
+ // (the composer dedupes), so we always re-fetch the (small, singleton) list.
21
+ _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(aiCfoViewReducer_1.fetchSkills.match),
22
+ // switchMap: a singleton list endpoint with no key — cancel any in-flight
23
+ // fetch so the latest dispatch wins (no racing writes to `skills`).
24
+ (0, operators_1.switchMap)(() => {
25
+ // aiCfoMicroServiceBaseUrl already targets the chat service (it serves the
26
+ // /1.0/agents/{id} submit route too); /1.0/skills lives on the same service.
27
+ const url = `${zeniAPI.apiEndPoints.aiCfoMicroServiceBaseUrl}/1.0/skills`;
28
+ return zeniAPI.getJSON(url).pipe((0, operators_1.mergeMap)((response) => {
29
+ if ((0, responsePayload_1.isSuccessResponse)(response) && response.data != null) {
30
+ // Guard the array: a malformed 2xx body without `skills` must not crash .map.
31
+ return (0, rxjs_1.of)((0, aiCfoViewReducer_1.fetchSkillsSuccess)({
32
+ skills: (response.data.skills ?? []).map(toSkill),
33
+ }));
34
+ }
35
+ return (0, rxjs_1.of)((0, aiCfoViewReducer_1.fetchSkillsFailure)(response.status));
36
+ }), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, aiCfoViewReducer_1.fetchSkillsFailure)((0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Fetch skills REST API call failed', error instanceof Error ? { message: error.message } : undefined)))));
37
+ }));
38
+ exports.fetchSkillsEpic = fetchSkillsEpic;
@@ -7,7 +7,7 @@ const aiCfoReducer_1 = require("../../../entity/aiCfo/aiCfoReducer");
7
7
  const responsePayload_1 = require("../../../responsePayload");
8
8
  const aiCfoViewReducer_1 = require("../aiCfoViewReducer");
9
9
  const submitQuestionEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(aiCfoViewReducer_1.submitQuestion.match), (0, operators_1.mergeMap)((action) => {
10
- const { questionAnswerId, userId, sessionId, questionId, createdAt, question, files, } = action.payload;
10
+ const { questionAnswerId, userId, sessionId, questionId, createdAt, question, files, macroId, } = action.payload;
11
11
  const { aiCfoViewState } = state$.value;
12
12
  const { uiState } = aiCfoViewState;
13
13
  const agentId = uiState.agentId;
@@ -20,15 +20,21 @@ const submitQuestionEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, oper
20
20
  const formData = new FormData();
21
21
  formData.append('chat_session_id', sessionId);
22
22
  formData.append('message', question);
23
+ if (macroId != null) {
24
+ formData.append('macro_id', macroId);
25
+ }
23
26
  for (const file of files) {
24
27
  formData.append('files', file, file.name);
25
28
  }
26
29
  apiCall = zeniAPI.postFormData(endpoint, formData);
27
30
  }
28
31
  else {
32
+ // `message` is the display token (e.g. "/flux"); `macro_id` tells the chat
33
+ // service to expand the skill's instructions as the actual agent prompt.
29
34
  apiCall = zeniAPI.postAndGetJSON(endpoint, {
30
35
  chat_session_id: sessionId,
31
36
  message: question,
37
+ ...(macroId != null && { macro_id: macroId }),
32
38
  });
33
39
  }
34
40
  return apiCall.pipe((0, operators_1.mergeMap)((response) => {
@@ -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,28 @@ 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 when we have a concrete value; omitting it
250
+ // avoids accidentally nulling out the server-side value when the load
251
+ // path didn't surface the field (e.g. list-shaped data or pre-deploy store).
252
+ ...(updates.project.accountingProviderProjectId != null
253
+ ? {
254
+ accounting_project_integration_id: updates.project.accountingProviderProjectId,
255
+ }
256
+ : {}),
257
+ accounting_project_name: updates.project.projectName,
258
+ };
259
+ }
236
260
  }
237
261
  else if ((0, vendorTransaction_1.isVendorTransaction)(transaction)) {
238
262
  if (tranSelectedEntity != null && tranSelectedEntity.type === 'customer') {
@@ -251,6 +275,7 @@ const toTransactionUpdatePayload = (updates, detail, cotTransactionTracking, ven
251
275
  // Inject COT flags into the payload
252
276
  return (0, transactionPayload_1.injectCOTFlagsIntoTransactionPayload)(basePayload, transaction);
253
277
  };
278
+ exports.toTransactionUpdatePayload = toTransactionUpdatePayload;
254
279
  const checkIfClassMissingForAnyLineItem = (transactionPayload) => {
255
280
  let isAnyClassMissing = false;
256
281
  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.24",
3
+ "version": "5.1.25-betaRD2",
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",
@@ -79,6 +79,7 @@
79
79
  "ts-prune": "^0.10.3",
80
80
  "ts-unused-exports": "^11.0.1",
81
81
  "typescript": "^5.7.3",
82
+ "vite": "^7.3.5",
82
83
  "vitest": "^4.1.8"
83
84
  },
84
85
  "dependencies": {
@@ -129,7 +130,8 @@
129
130
  "postversion": "git push && git push --tags",
130
131
  "check-dependencies": "node ./scripts/check_dependencies.js",
131
132
  "clean-overrides": "node ./scripts/clean_overrides.js",
132
- "create-release-branch": "chmod +x ./scripts/create_release_branch.sh && ./scripts/create_release_branch.sh",
133
+ "create-or-update-release-branch": "chmod +x ./scripts/create_or_update_release_branch.sh && ./scripts/create_or_update_release_branch.sh",
134
+ "bump-versions-master": "chmod +x ./scripts/bump_versions_master.sh && ./scripts/bump_versions_master.sh",
133
135
  "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",
134
136
  "send-release-notes": "chmod +x ./scripts/send_release_notes.sh && ./scripts/send_release_notes.sh",
135
137
  "update-slack-group-topic": "chmod +x ./scripts/update-slack-group-topic.sh && ./scripts/update-slack-group-topic.sh",