@zeniai/client-epic-state 5.2.11-betaRD7 → 5.2.11-betaRD8

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.
@@ -142,6 +142,7 @@ export interface JEOneTimeAccrualPayload {
142
142
  created_by_user?: UserPayload | null;
143
143
  currency?: string | null;
144
144
  expected_reversal_date?: string | null;
145
+ is_ignored?: boolean;
145
146
  je_credit_account_id?: ID | null;
146
147
  je_credit_account_name?: string | null;
147
148
  je_credit_accounting_class_id?: ID | null;
@@ -347,17 +347,20 @@ function toJEAccruedScheduledTransaction(payload) {
347
347
  label: payload.status.label,
348
348
  },
349
349
  vendorId: payload.vendor.vendor_id,
350
+ // The accrued payload extends JEScheduledTransactionPayload, so it carries currency_code and
351
+ // currency_symbol like every other schedule -- this mapper was hardcoding USD over fields it
352
+ // already had.
350
353
  recurringAmount: {
351
354
  amount: payload.recurring_amount,
352
- currencyCode: 'USD',
353
- currencySymbol: '$',
355
+ currencyCode: payload.currency_code,
356
+ currencySymbol: payload.currency_symbol,
354
357
  },
355
358
  otherAttributes: {
356
359
  startingBalance: payload.other_attributes?.starting_balance != null
357
360
  ? {
358
361
  amount: payload.other_attributes?.starting_balance ?? 0,
359
- currencyCode: 'USD',
360
- currencySymbol: '$',
362
+ currencyCode: payload.currency_code,
363
+ currencySymbol: payload.currency_symbol,
361
364
  }
362
365
  : undefined,
363
366
  },
@@ -466,6 +469,7 @@ function toJEOneTimeAccrual(payload) {
466
469
  estimatedAmount: toOneTimeAccrualAmount(payload.estimated_amount, payload.currency),
467
470
  memo: payload.memo ?? '',
468
471
  profileSnapshot: toJEOneTimeAccrualProfileSnapshot(payload.profile_snapshot, payload.currency),
472
+ isIgnored: payload.is_ignored === true,
469
473
  aiSuggestion: toJEOneTimeAccrualAiSuggestion(payload.ai_suggestion, payload.currency),
470
474
  // Exclusive by contract: a scheduled row reverses on expectedReversalDate and bills never
471
475
  // drive it, so the two never need reconciling. Defaulting to on_bill_match matches the
@@ -201,6 +201,11 @@ export interface JEOneTimeAccrualState {
201
201
  accrualId: ID;
202
202
  accrualStatus: OneTimeAccrualStatusCodeType;
203
203
  estimatedAmount: Amount;
204
+ /**
205
+ * Ignoring an accrual sets this flag and leaves the status on `draft`, so the status alone cannot
206
+ * tell an ignored row from an actionable one.
207
+ */
208
+ isIgnored: boolean;
204
209
  memo: string;
205
210
  origin: OneTimeAccrualOriginType;
206
211
  reversalPolicy: OneTimeAccrualReversalPolicyType;
@@ -15,7 +15,7 @@ export declare const toScheduleJournalEntryStatusCodeType: (v: string) => "unkno
15
15
  export type ScheduleJournalEntryStatusCodeType = ReturnType<typeof toScheduleJournalEntryStatusCodeType>;
16
16
  export declare const toScheduleStatusCodeType: (v: string) => "completed" | "draft" | "new" | "ongoing" | "marked_as_completed";
17
17
  export type ScheduleStatusCodeType = ReturnType<typeof toScheduleStatusCodeType>;
18
- export declare const toOneTimeAccrualStatusCodeType: (v: string) => "draft" | "posted" | "posting" | "posting_blocked" | "reversing" | "reversed";
18
+ export declare const toOneTimeAccrualStatusCodeType: (v: string) => "draft" | "posted" | "posting" | "posting_blocked" | "reversing" | "reversed" | "reverted";
19
19
  export type OneTimeAccrualStatusCodeType = ReturnType<typeof toOneTimeAccrualStatusCodeType>;
20
20
  export declare const toOneTimeAccrualOriginType: (v: string) => "agent" | "human";
21
21
  export type OneTimeAccrualOriginType = ReturnType<typeof toOneTimeAccrualOriginType>;
@@ -35,6 +35,13 @@ exports.toScheduleStatusCodeType = toScheduleStatusCodeType;
35
35
  // One-time accruals come from agent-je's Mongo `accruals` collection, not the JE schedule
36
36
  // datastore, so they carry their own status vocabulary rather than SCHEDULE_STATUS_CODE.
37
37
  // `posting` and `reversing` are transient claims held while a JE posts.
38
+ /**
39
+ * Mirrors agent-je's own status constants. `reverted` is the terminal state a manual revert writes
40
+ * alongside is_deleted; without it stringToUnion logs an error and passes the raw string through.
41
+ *
42
+ * There is deliberately no `ignored` member -- ignoring an accrual sets the `is_ignored` flag and
43
+ * leaves the status on `draft`.
44
+ */
38
45
  const ONE_TIME_ACCRUAL_STATUS_CODE = [
39
46
  'draft',
40
47
  'posting',
@@ -42,6 +49,7 @@ const ONE_TIME_ACCRUAL_STATUS_CODE = [
42
49
  'posting_blocked',
43
50
  'reversing',
44
51
  'reversed',
52
+ 'reverted',
45
53
  ];
46
54
  const toOneTimeAccrualStatusCodeType = (v) => (0, stringToUnion_1.stringToUnion)(v, ONE_TIME_ACCRUAL_STATUS_CODE);
47
55
  exports.toOneTimeAccrualStatusCodeType = toOneTimeAccrualStatusCodeType;
@@ -336,17 +336,20 @@ export function toJEAccruedScheduledTransaction(payload) {
336
336
  label: payload.status.label,
337
337
  },
338
338
  vendorId: payload.vendor.vendor_id,
339
+ // The accrued payload extends JEScheduledTransactionPayload, so it carries currency_code and
340
+ // currency_symbol like every other schedule -- this mapper was hardcoding USD over fields it
341
+ // already had.
339
342
  recurringAmount: {
340
343
  amount: payload.recurring_amount,
341
- currencyCode: 'USD',
342
- currencySymbol: '$',
344
+ currencyCode: payload.currency_code,
345
+ currencySymbol: payload.currency_symbol,
343
346
  },
344
347
  otherAttributes: {
345
348
  startingBalance: payload.other_attributes?.starting_balance != null
346
349
  ? {
347
350
  amount: payload.other_attributes?.starting_balance ?? 0,
348
- currencyCode: 'USD',
349
- currencySymbol: '$',
351
+ currencyCode: payload.currency_code,
352
+ currencySymbol: payload.currency_symbol,
350
353
  }
351
354
  : undefined,
352
355
  },
@@ -455,6 +458,7 @@ export function toJEOneTimeAccrual(payload) {
455
458
  estimatedAmount: toOneTimeAccrualAmount(payload.estimated_amount, payload.currency),
456
459
  memo: payload.memo ?? '',
457
460
  profileSnapshot: toJEOneTimeAccrualProfileSnapshot(payload.profile_snapshot, payload.currency),
461
+ isIgnored: payload.is_ignored === true,
458
462
  aiSuggestion: toJEOneTimeAccrualAiSuggestion(payload.ai_suggestion, payload.currency),
459
463
  // Exclusive by contract: a scheduled row reverses on expectedReversalDate and bills never
460
464
  // drive it, so the two never need reconciling. Defaulting to on_bill_match matches the
@@ -27,6 +27,13 @@ export const toScheduleStatusCodeType = (v) => stringToUnion(v, SCHEDULE_STATUS_
27
27
  // One-time accruals come from agent-je's Mongo `accruals` collection, not the JE schedule
28
28
  // datastore, so they carry their own status vocabulary rather than SCHEDULE_STATUS_CODE.
29
29
  // `posting` and `reversing` are transient claims held while a JE posts.
30
+ /**
31
+ * Mirrors agent-je's own status constants. `reverted` is the terminal state a manual revert writes
32
+ * alongside is_deleted; without it stringToUnion logs an error and passes the raw string through.
33
+ *
34
+ * There is deliberately no `ignored` member -- ignoring an accrual sets the `is_ignored` flag and
35
+ * leaves the status on `draft`.
36
+ */
30
37
  const ONE_TIME_ACCRUAL_STATUS_CODE = [
31
38
  'draft',
32
39
  'posting',
@@ -34,6 +41,7 @@ const ONE_TIME_ACCRUAL_STATUS_CODE = [
34
41
  'posting_blocked',
35
42
  'reversing',
36
43
  'reversed',
44
+ 'reverted',
37
45
  ];
38
46
  export const toOneTimeAccrualStatusCodeType = (v) => stringToUnion(v, ONE_TIME_ACCRUAL_STATUS_CODE);
39
47
  // Who created the accrual. This — not the creator's email — is the authoritative
@@ -12,7 +12,9 @@ import { prepareNewSchedulePayload, } from '../../../helpers/newScheduleLocalDat
12
12
  import { fetchJeSchedules, resetNewScheduleLocalData, saveNewSchedule, saveNewScheduleFailure, saveNewScheduleSuccess, } from '../../../reducers/jeSchedulesViewReducer';
13
13
  const FAILURE_REASON_MESSAGES = {
14
14
  missing_third_party_id: 'This transaction is missing a QuickBooks reference and cannot be scheduled.',
15
- missing_vendor: 'Select a vendor before saving this schedule.',
15
+ // The form cannot satisfy this: the vendor field is hard-disabled and inherited from the base
16
+ // transaction, so the fix is on the transaction, not here.
17
+ missing_vendor: 'This transaction has no vendor and cannot be scheduled. Add a vendor to the transaction first.',
16
18
  };
17
19
  export const saveNewScheduleEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(saveNewSchedule.match), mergeMap((action) => {
18
20
  const { scheduleType } = action.payload;
@@ -38,7 +38,14 @@ const ONE_TIME_ACCRUAL_REVIEW_STATUSES = [
38
38
  * these rows do arrive.
39
39
  */
40
40
  export function isRenderableOneTimeAccrual(accrual) {
41
- return accrual.accrualStatus !== 'posting_blocked';
41
+ return (accrual.accrualStatus !== 'posting_blocked' &&
42
+ // Terminal states the user has already decided. The GET list excludes both server-side, so
43
+ // these only appear in the window between an ignore/revert response being written back and the
44
+ // background refresh landing -- during which an ignored row would otherwise sit in Pending
45
+ // Review offering a Post button. Dropped here rather than at render time so group counts and
46
+ // KPI totals do not count rows nobody can see.
47
+ accrual.accrualStatus !== 'reverted' &&
48
+ !accrual.isIgnored);
42
49
  }
43
50
  export function getJEScheduleRowType(schedule) {
44
51
  return isOneTimeAccrualRow(schedule)
@@ -89,6 +96,16 @@ export function isAccruedScheduleRow(schedule) {
89
96
  export function isOneTimeAccrualRow(schedule) {
90
97
  return 'jeOneTimeAccrualKey' in schedule;
91
98
  }
99
+ /** The row's own Amount, so callers can read its currency and not just its value. */
100
+ function getScheduleAmountObject(schedule) {
101
+ if (isOneTimeAccrualRow(schedule)) {
102
+ return schedule.estimatedAmount;
103
+ }
104
+ if (isAccruedScheduleRow(schedule)) {
105
+ return schedule.recurringAmount;
106
+ }
107
+ return schedule.baseTransaction.amount;
108
+ }
92
109
  function getScheduleAmount(schedule) {
93
110
  if (isOneTimeAccrualRow(schedule)) {
94
111
  return schedule.estimatedAmount.amount;
@@ -307,25 +324,41 @@ export function getExpenseAutomationJESchedulesView(state) {
307
324
  const sumAmount = (schedules) => schedules.reduce((total, schedule) => total + (getScheduleAmount(schedule) ?? 0), 0);
308
325
  const pendingReviewTotal = sumAmount(pendingReviewSchedules);
309
326
  const createdTotal = sumAmount(createdSchedules);
327
+ /**
328
+ * The currency the tiles render in. Taken from the schedules themselves rather than assumed:
329
+ * every row on a tab shares a tenant currency, so the first one that states it is the answer.
330
+ */
331
+ const sumToAmount = (schedules) => {
332
+ const first = schedules[0];
333
+ const currency = first != null ? getScheduleAmountObject(first) : allScheduleAmountFallback;
334
+ return {
335
+ ...currency,
336
+ amount: sumAmount(schedules),
337
+ };
338
+ };
339
+ // An empty bucket still has to render a currency; borrow it from any row on the page.
340
+ const allScheduleAmountFallback = allScheduleRows.length > 0
341
+ ? getScheduleAmountObject(allScheduleRows[0])
342
+ : { amount: 0, currencyCode: '', currencySymbol: '' };
310
343
  const aiAccountantSchedules = allSchedules.filter(isAiAccountantSchedule);
311
344
  const manualSchedules = allSchedules.filter((schedule) => !isAiAccountantSchedule(schedule));
312
345
  const pendingUnfiltered = allSchedules.filter((schedule) => isPendingReviewRow(schedule));
313
346
  const kpiSummary = {
314
347
  aiAccountant: {
315
348
  count: aiAccountantSchedules.length,
316
- amount: sumAmount(aiAccountantSchedules),
349
+ amount: sumToAmount(aiAccountantSchedules),
317
350
  },
318
351
  manual: {
319
352
  count: manualSchedules.length,
320
- amount: sumAmount(manualSchedules),
353
+ amount: sumToAmount(manualSchedules),
321
354
  },
322
355
  needsReview: {
323
356
  count: pendingUnfiltered.length,
324
- amount: sumAmount(pendingUnfiltered),
357
+ amount: sumToAmount(pendingUnfiltered),
325
358
  },
326
359
  total: {
327
360
  count: allSchedules.length,
328
- amount: sumAmount(allSchedules),
361
+ amount: sumToAmount(allSchedules),
329
362
  },
330
363
  };
331
364
  // Each tab's badge counts from that tab's own bucket. A tab that has not been fetched yet
@@ -15,7 +15,9 @@ const newScheduleLocalDataHelper_1 = require("../../../helpers/newScheduleLocalD
15
15
  const jeSchedulesViewReducer_1 = require("../../../reducers/jeSchedulesViewReducer");
16
16
  const FAILURE_REASON_MESSAGES = {
17
17
  missing_third_party_id: 'This transaction is missing a QuickBooks reference and cannot be scheduled.',
18
- missing_vendor: 'Select a vendor before saving this schedule.',
18
+ // The form cannot satisfy this: the vendor field is hard-disabled and inherited from the base
19
+ // transaction, so the fix is on the transaction, not here.
20
+ missing_vendor: 'This transaction has no vendor and cannot be scheduled. Add a vendor to the transaction first.',
19
21
  };
20
22
  const saveNewScheduleEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(jeSchedulesViewReducer_1.saveNewSchedule.match), (0, operators_1.mergeMap)((action) => {
21
23
  const { scheduleType } = action.payload;
@@ -1,3 +1,4 @@
1
+ import { Amount } from '../../../commonStateTypes/amount';
1
2
  import { NestedAccountHierarchyForReport } from '../../../commonStateTypes/accountView/nestedAccountID';
2
3
  import { NestedClassHierarchyForReport } from '../../../commonStateTypes/classesView/nestedClassID';
3
4
  import { CompletionStatusType, FetchStateAndError, ID } from '../../../commonStateTypes/common';
@@ -11,21 +12,25 @@ import { JEScheduledTransactionWithFailedEntries } from '../selectors/jeSchedule
11
12
  import { NewScheduleView } from '../selectorTypes/jeNewScheduleViewSelectorTypes';
12
13
  import { JEScheduleRow } from '../selectors/jeSchedulesViewSelector';
13
14
  import { JEPageTab, JEScheduleLocalData, JESchedulesViewUIState } from '../types/jeSchedulesViewState';
15
+ /**
16
+ * Amount rather than a bare number: the KPI strip formats these for display, and a number carries
17
+ * no currency, which is what made the tiles print a hardcoded dollar sign.
18
+ */
14
19
  export interface JEKpiSummary {
15
20
  aiAccountant: {
16
- amount: number;
21
+ amount: Amount;
17
22
  count: number;
18
23
  };
19
24
  manual: {
20
- amount: number;
25
+ amount: Amount;
21
26
  count: number;
22
27
  };
23
28
  needsReview: {
24
- amount: number;
29
+ amount: Amount;
25
30
  count: number;
26
31
  };
27
32
  total: {
28
- amount: number;
33
+ amount: Amount;
29
34
  count: number;
30
35
  };
31
36
  }
@@ -52,7 +52,14 @@ const ONE_TIME_ACCRUAL_REVIEW_STATUSES = [
52
52
  * these rows do arrive.
53
53
  */
54
54
  function isRenderableOneTimeAccrual(accrual) {
55
- return accrual.accrualStatus !== 'posting_blocked';
55
+ return (accrual.accrualStatus !== 'posting_blocked' &&
56
+ // Terminal states the user has already decided. The GET list excludes both server-side, so
57
+ // these only appear in the window between an ignore/revert response being written back and the
58
+ // background refresh landing -- during which an ignored row would otherwise sit in Pending
59
+ // Review offering a Post button. Dropped here rather than at render time so group counts and
60
+ // KPI totals do not count rows nobody can see.
61
+ accrual.accrualStatus !== 'reverted' &&
62
+ !accrual.isIgnored);
56
63
  }
57
64
  function getJEScheduleRowType(schedule) {
58
65
  return isOneTimeAccrualRow(schedule)
@@ -104,6 +111,16 @@ function isAccruedScheduleRow(schedule) {
104
111
  function isOneTimeAccrualRow(schedule) {
105
112
  return 'jeOneTimeAccrualKey' in schedule;
106
113
  }
114
+ /** The row's own Amount, so callers can read its currency and not just its value. */
115
+ function getScheduleAmountObject(schedule) {
116
+ if (isOneTimeAccrualRow(schedule)) {
117
+ return schedule.estimatedAmount;
118
+ }
119
+ if (isAccruedScheduleRow(schedule)) {
120
+ return schedule.recurringAmount;
121
+ }
122
+ return schedule.baseTransaction.amount;
123
+ }
107
124
  function getScheduleAmount(schedule) {
108
125
  if (isOneTimeAccrualRow(schedule)) {
109
126
  return schedule.estimatedAmount.amount;
@@ -322,25 +339,41 @@ function getExpenseAutomationJESchedulesView(state) {
322
339
  const sumAmount = (schedules) => schedules.reduce((total, schedule) => total + (getScheduleAmount(schedule) ?? 0), 0);
323
340
  const pendingReviewTotal = sumAmount(pendingReviewSchedules);
324
341
  const createdTotal = sumAmount(createdSchedules);
342
+ /**
343
+ * The currency the tiles render in. Taken from the schedules themselves rather than assumed:
344
+ * every row on a tab shares a tenant currency, so the first one that states it is the answer.
345
+ */
346
+ const sumToAmount = (schedules) => {
347
+ const first = schedules[0];
348
+ const currency = first != null ? getScheduleAmountObject(first) : allScheduleAmountFallback;
349
+ return {
350
+ ...currency,
351
+ amount: sumAmount(schedules),
352
+ };
353
+ };
354
+ // An empty bucket still has to render a currency; borrow it from any row on the page.
355
+ const allScheduleAmountFallback = allScheduleRows.length > 0
356
+ ? getScheduleAmountObject(allScheduleRows[0])
357
+ : { amount: 0, currencyCode: '', currencySymbol: '' };
325
358
  const aiAccountantSchedules = allSchedules.filter(exports.isAiAccountantSchedule);
326
359
  const manualSchedules = allSchedules.filter((schedule) => !(0, exports.isAiAccountantSchedule)(schedule));
327
360
  const pendingUnfiltered = allSchedules.filter((schedule) => isPendingReviewRow(schedule));
328
361
  const kpiSummary = {
329
362
  aiAccountant: {
330
363
  count: aiAccountantSchedules.length,
331
- amount: sumAmount(aiAccountantSchedules),
364
+ amount: sumToAmount(aiAccountantSchedules),
332
365
  },
333
366
  manual: {
334
367
  count: manualSchedules.length,
335
- amount: sumAmount(manualSchedules),
368
+ amount: sumToAmount(manualSchedules),
336
369
  },
337
370
  needsReview: {
338
371
  count: pendingUnfiltered.length,
339
- amount: sumAmount(pendingUnfiltered),
372
+ amount: sumToAmount(pendingUnfiltered),
340
373
  },
341
374
  total: {
342
375
  count: allSchedules.length,
343
- amount: sumAmount(allSchedules),
376
+ amount: sumToAmount(allSchedules),
344
377
  },
345
378
  };
346
379
  // Each tab's badge counts from that tab's own bucket. A tab that has not been fetched yet
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.2.11-betaRD7",
3
+ "version": "5.2.11-betaRD8",
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",