@zeniai/client-epic-state 5.0.81-betaAN1 → 5.0.81-betaAR1

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 (48) hide show
  1. package/lib/entity/aiCfo/aiCfoPayload.d.ts +0 -1
  2. package/lib/entity/aiCfo/aiCfoReducer.js +1 -3
  3. package/lib/entity/aiCfo/aiCfoState.d.ts +0 -1
  4. package/lib/entity/approvalRule/approvalRulePayload.d.ts +27 -5
  5. package/lib/entity/approvalRule/approvalRulePayload.js +131 -10
  6. package/lib/entity/approvalRule/approvalRuleSelector.d.ts +25 -1
  7. package/lib/entity/approvalRule/approvalRuleSelector.js +40 -0
  8. package/lib/entity/approvalRule/approvalRuleState.d.ts +48 -7
  9. package/lib/esm/entity/aiCfo/aiCfoReducer.js +1 -3
  10. package/lib/esm/entity/approvalRule/approvalRulePayload.js +131 -10
  11. package/lib/esm/entity/approvalRule/approvalRuleSelector.js +35 -0
  12. package/lib/esm/index.js +2 -1
  13. package/lib/esm/view/spendManagement/billPay/billPaySetupApproverView/billPaySetupApproverViewReducer.js +29 -11
  14. package/lib/esm/view/spendManagement/billPay/billPaySetupApproverView/epic/deleteBillPayApprovalRuleEpic.js +11 -2
  15. package/lib/esm/view/spendManagement/billPay/billPaySetupApproverView/epic/fetchBillPaySetupApproverViewEpic.js +9 -4
  16. package/lib/esm/view/spendManagement/billPay/billPaySetupApproverView/epic/initializeBillPaySetupApproverViewUpdateDataEpic.js +11 -7
  17. package/lib/esm/view/spendManagement/billPay/billPaySetupApproverView/epic/saveBillPaySetupApproverViewUpdatesEpic.js +14 -4
  18. package/lib/esm/view/spendManagement/billPay/billPaySetupApproverView/types/commonPayload.js +121 -16
  19. package/lib/esm/view/spendManagement/billPay/editBillView/editBillViewSelector.js +7 -2
  20. package/lib/esm/view/spendManagement/reimbursement/remiSetupApproverView/epic/deleteRemiApprovalRuleEpic.js +11 -2
  21. package/lib/esm/view/spendManagement/reimbursement/remiSetupApproverView/epic/fetchRemiSetupApproverViewEpic.js +9 -2
  22. package/lib/esm/view/spendManagement/reimbursement/remiSetupApproverView/epic/initializeRemiSetupApproverViewUpdateDataEpic.js +11 -7
  23. package/lib/esm/view/spendManagement/reimbursement/remiSetupApproverView/epic/saveRemiSetupApproverViewUpdatesEpic.js +14 -4
  24. package/lib/esm/view/spendManagement/reimbursement/remiSetupApproverView/remiSetupApproverViewReducer.js +28 -10
  25. package/lib/index.d.ts +3 -2
  26. package/lib/index.js +22 -16
  27. package/lib/view/companyTaskManagerView/companyTaskManagerViewReducer.d.ts +2 -2
  28. package/lib/view/companyView/types/cockpitTypes.d.ts +1 -1
  29. package/lib/view/people/peopleTypes.d.ts +1 -1
  30. package/lib/view/spendManagement/billPay/billPaySetupApproverView/billPaySetupApproverViewReducer.d.ts +7 -3
  31. package/lib/view/spendManagement/billPay/billPaySetupApproverView/billPaySetupApproverViewReducer.js +29 -11
  32. package/lib/view/spendManagement/billPay/billPaySetupApproverView/epic/deleteBillPayApprovalRuleEpic.js +11 -2
  33. package/lib/view/spendManagement/billPay/billPaySetupApproverView/epic/fetchBillPaySetupApproverViewEpic.js +9 -4
  34. package/lib/view/spendManagement/billPay/billPaySetupApproverView/epic/initializeBillPaySetupApproverViewUpdateDataEpic.js +10 -6
  35. package/lib/view/spendManagement/billPay/billPaySetupApproverView/epic/saveBillPaySetupApproverViewUpdatesEpic.js +13 -3
  36. package/lib/view/spendManagement/billPay/billPaySetupApproverView/types/commonPayload.d.ts +48 -1
  37. package/lib/view/spendManagement/billPay/billPaySetupApproverView/types/commonPayload.js +123 -17
  38. package/lib/view/spendManagement/billPay/billPaySetupApproverView/types/commonState.d.ts +31 -5
  39. package/lib/view/spendManagement/billPay/editBillView/editBillViewSelector.js +7 -2
  40. package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardList.d.ts +1 -1
  41. package/lib/view/spendManagement/reimbursement/remiSetupApproverView/epic/deleteRemiApprovalRuleEpic.js +11 -2
  42. package/lib/view/spendManagement/reimbursement/remiSetupApproverView/epic/fetchRemiSetupApproverViewEpic.js +9 -2
  43. package/lib/view/spendManagement/reimbursement/remiSetupApproverView/epic/initializeRemiSetupApproverViewUpdateDataEpic.js +10 -6
  44. package/lib/view/spendManagement/reimbursement/remiSetupApproverView/epic/saveRemiSetupApproverViewUpdatesEpic.js +13 -3
  45. package/lib/view/spendManagement/reimbursement/remiSetupApproverView/remiSetupApproverViewReducer.d.ts +7 -3
  46. package/lib/view/spendManagement/reimbursement/remiSetupApproverView/remiSetupApproverViewReducer.js +28 -10
  47. package/lib/view/taskManager/taskListView/taskList.d.ts +3 -3
  48. package/package.json +1 -2
@@ -41,7 +41,6 @@ export interface AiCfoAnswerPayload {
41
41
  type: string;
42
42
  visualization: AiCfoVisualizationPayload | null;
43
43
  artifact?: string | null;
44
- artifact_host_url?: string | null;
45
44
  artifact_title?: string | null;
46
45
  follow_up_questions?: string[];
47
46
  table?: AiCfoVisualizationPayload | null;
@@ -117,7 +117,6 @@ function chatHistoryToQA(history) {
117
117
  chainOfThoughtActionSteps: [],
118
118
  artifact: parsedAgentMessage.artifact ?? undefined,
119
119
  artifactTitle: parsedAgentMessage.artifact_title ?? undefined,
120
- artifactHostUrl: parsedAgentMessage.artifact_host_url ?? undefined,
121
120
  },
122
121
  createdAt: (0, zeniDayJS_1.date)(agentMessage.created_at),
123
122
  };
@@ -188,7 +187,7 @@ const toAiCfoVisualizationData = (data) => {
188
187
  };
189
188
  };
190
189
  const toResponseBlockType = (answer, userId) => {
191
- const { metadata, state, type, text, visualization, table, follow_up_questions, assumptions, insights, summarized_title, artifact, artifact_title, artifact_host_url, } = answer;
190
+ const { metadata, state, type, text, visualization, table, follow_up_questions, assumptions, insights, summarized_title, artifact, artifact_title, } = answer;
192
191
  const { chat_session_id, message_id, timestamp, chain_of_thought_action_steps: chainOfThoughtActionSteps = [], } = metadata;
193
192
  return {
194
193
  messageId: message_id,
@@ -208,7 +207,6 @@ const toResponseBlockType = (answer, userId) => {
208
207
  chainOfThoughtActionSteps,
209
208
  artifact: artifact ?? undefined,
210
209
  artifactTitle: artifact_title ?? undefined,
211
- artifactHostUrl: artifact_host_url ?? undefined,
212
210
  },
213
211
  createdAt: (0, zeniDayJS_1.date)(timestamp),
214
212
  };
@@ -78,7 +78,6 @@ export interface AnswerContentBlockBase {
78
78
  insights: string;
79
79
  text: string;
80
80
  artifact?: string;
81
- artifactHostUrl?: string;
82
81
  artifactTitle?: string;
83
82
  summarizedTitle?: string;
84
83
  table?: AiCfoVisualization;
@@ -1,11 +1,27 @@
1
1
  import { ApprovalRule } from './approvalRuleState';
2
+ /**
3
+ * Condition Payload — one entry inside criteria.conditions[].
4
+ *
5
+ * field examples: 'amount' | 'vendor_id' | 'department_id'
6
+ * type examples: 'gte' | 'lte' | 'eq' | 'in' | 'not_in'
7
+ * value: number for amount comparisons; string[] for in / not_in lookups.
8
+ */
9
+ export interface ConditionPayload {
10
+ field: string;
11
+ type: string;
12
+ value: number | string[];
13
+ }
14
+ /**
15
+ * Criteria Payload — list of conditions joined by `criteria_operator`.
16
+ *
17
+ * `currency_code` / `currency_symbol` apply to amount conditions inside
18
+ * `conditions[]`. Kept here pending confirmation that they should remain.
19
+ */
2
20
  export interface CriteriaPayload {
21
+ conditions: ConditionPayload[];
22
+ criteria_operator: string;
3
23
  currency_code: string;
4
24
  currency_symbol: string;
5
- min: number;
6
- range_entity: string;
7
- type: string;
8
- max?: number | null;
9
25
  }
10
26
  export interface ActorPayload {
11
27
  type: string;
@@ -25,11 +41,17 @@ export interface StepPayload {
25
41
  export interface ApprovalRulePayload {
26
42
  approval_rule_id: string;
27
43
  create_time: string;
28
- criteria: CriteriaPayload[];
44
+ criteria: CriteriaPayload;
29
45
  entity_type: string;
30
46
  name: string | null;
31
47
  steps: StepPayload[];
32
48
  update_time: string;
49
+ are_approvals_serialized?: boolean;
50
+ description?: string | null;
51
+ is_fallback?: boolean;
52
+ pending_approvals_count?: number;
53
+ pending_entity_approval_update_status?: string;
54
+ priority?: number;
33
55
  version?: number | string;
34
56
  }
35
57
  /**
@@ -17,19 +17,140 @@ const toApprovalRule = (payload) => ({
17
17
  version: payload.version,
18
18
  criteria: toApprovalCriteria(payload.criteria),
19
19
  steps: toApprovalSteps(payload.steps),
20
+ description: payload.description ?? undefined,
21
+ priority: payload.priority,
22
+ isFallback: payload.is_fallback ?? false,
23
+ // separationOfDuties: backend doesn't ship this field yet; left undefined
24
+ // until the spec is finalized.
25
+ areApprovalsSerialized: payload.are_approvals_serialized,
26
+ pendingApprovalsCount: payload.pending_approvals_count,
27
+ pendingEntityApprovalUpdateStatus: payload.pending_entity_approval_update_status,
20
28
  });
21
29
  exports.toApprovalRule = toApprovalRule;
30
+ /**
31
+ * Maps the wire-level criteria block into the state-side discriminated
32
+ * `Criteria[]` union.
33
+ *
34
+ * The wire keeps each condition as a flat row inside `conditions[]`.
35
+ * Amount comparisons can show up as a `gte` only, a `lte` only, or a
36
+ * `gte` + `lte` pair — those collapse into a single AmountCriteria whose
37
+ * `comparator` is `greater_than`, `less_than`, or `range` respectively.
38
+ *
39
+ * Vendor and department conditions map one-to-one: `in` becomes `is`,
40
+ * `not_in` becomes `is_not`.
41
+ *
42
+ * Resilient to two forms of upstream drift:
43
+ * - Missing / null criteria → returns [].
44
+ * - Legacy criteria shape (array of amount-range objects, the pre-3.0
45
+ * wire) → converted to an AmountCriteria so existing rules in production
46
+ * still render while the backend rolls out.
47
+ */
22
48
  const toApprovalCriteria = (payload) => {
23
- return payload.map((criteria) => ({
24
- rangeType: criteria.type,
25
- rangeEntity: criteria.range_entity,
26
- range: {
27
- min: (0, amount_1.toAmount)(criteria.min, criteria.currency_code, criteria.currency_symbol),
28
- max: criteria.max != null
29
- ? (0, amount_1.toAmount)(criteria.max, criteria.currency_code, criteria.currency_symbol)
30
- : undefined,
31
- },
32
- }));
49
+ if (payload == null) {
50
+ return [];
51
+ }
52
+ if (Array.isArray(payload)) {
53
+ // Legacy pre-3.0 wire: an array of amount-range entries.
54
+ return legacyAmountRangeToCriteria(payload);
55
+ }
56
+ if (!Array.isArray(payload.conditions)) {
57
+ return [];
58
+ }
59
+ const results = [];
60
+ const amountCriteria = toAmountCriteria(payload);
61
+ if (amountCriteria != null) {
62
+ results.push(amountCriteria);
63
+ }
64
+ payload.conditions
65
+ .filter((condition) => condition.field === 'vendor_id')
66
+ .forEach((condition) => {
67
+ const vendor = toVendorCriteria(condition);
68
+ if (vendor != null) {
69
+ results.push(vendor);
70
+ }
71
+ });
72
+ payload.conditions
73
+ .filter((condition) => condition.field === 'department_id')
74
+ .forEach((condition) => {
75
+ const department = toDepartmentCriteria(condition);
76
+ if (department != null) {
77
+ results.push(department);
78
+ }
79
+ });
80
+ return results;
81
+ };
82
+ const legacyAmountRangeToCriteria = (legacy) => {
83
+ if (legacy.length === 0) {
84
+ return [];
85
+ }
86
+ const first = legacy[0] ?? {};
87
+ const currency = first.currency_code ?? 'USD';
88
+ const symbol = first.currency_symbol ?? '$';
89
+ const min = typeof first.min === 'number'
90
+ ? (0, amount_1.toAmount)(first.min, currency, symbol)
91
+ : undefined;
92
+ const max = typeof first.max === 'number'
93
+ ? (0, amount_1.toAmount)(first.max, currency, symbol)
94
+ : undefined;
95
+ if (min == null && max == null) {
96
+ return [];
97
+ }
98
+ const comparator = min != null && max != null
99
+ ? 'range'
100
+ : min != null
101
+ ? 'greater_than'
102
+ : 'less_than';
103
+ return [{ type: 'amount', comparator, min, max }];
104
+ };
105
+ const toAmountCriteria = (payload) => {
106
+ if (!Array.isArray(payload.conditions)) {
107
+ return undefined;
108
+ }
109
+ const amountConditions = payload.conditions.filter((condition) => condition.field === 'amount');
110
+ const gteCondition = amountConditions.find((condition) => condition.type === 'gte' || condition.type === 'eq');
111
+ const lteCondition = amountConditions.find((condition) => condition.type === 'lte');
112
+ if (gteCondition == null && lteCondition == null) {
113
+ return undefined;
114
+ }
115
+ const currencyCode = payload.currency_code ?? 'USD';
116
+ const currencySymbol = payload.currency_symbol ?? '$';
117
+ const min = typeof gteCondition?.value === 'number'
118
+ ? (0, amount_1.toAmount)(gteCondition.value, currencyCode, currencySymbol)
119
+ : undefined;
120
+ const max = typeof lteCondition?.value === 'number'
121
+ ? (0, amount_1.toAmount)(lteCondition.value, currencyCode, currencySymbol)
122
+ : undefined;
123
+ let comparator;
124
+ if (min != null && max != null) {
125
+ comparator = 'range';
126
+ }
127
+ else if (min != null) {
128
+ comparator = 'greater_than';
129
+ }
130
+ else {
131
+ comparator = 'less_than';
132
+ }
133
+ return { type: 'amount', comparator, min, max };
134
+ };
135
+ const toVendorCriteria = (condition) => {
136
+ if (!Array.isArray(condition.value)) {
137
+ return undefined;
138
+ }
139
+ return {
140
+ type: 'vendor',
141
+ operator: condition.type === 'not_in' ? 'is_not' : 'is',
142
+ vendorIds: condition.value,
143
+ };
144
+ };
145
+ const toDepartmentCriteria = (condition) => {
146
+ if (!Array.isArray(condition.value)) {
147
+ return undefined;
148
+ }
149
+ return {
150
+ type: 'department',
151
+ operator: condition.type === 'not_in' ? 'is_not' : 'is',
152
+ departmentIds: condition.value,
153
+ };
33
154
  };
34
155
  const toApprovalSteps = (payload) => {
35
156
  return payload.map((step) => ({
@@ -1,4 +1,28 @@
1
1
  import { ID } from '../../commonStateTypes/common';
2
- import { ApprovalRule, ApprovalRuleState } from './approvalRuleState';
2
+ import { EntityType } from '../entityApprovalStatus/entityApprovalStatusState';
3
+ import { AmountCriteria, ApprovalRule, ApprovalRuleState, Criteria, DepartmentCriteria, VendorCriteria } from './approvalRuleState';
3
4
  export declare function getApprovalRuleById(approvalRuleState: ApprovalRuleState, approvalRuleId: ID): ApprovalRule | undefined;
4
5
  export declare function getApprovalRulesByIds(approvalRuleState: ApprovalRuleState, approvalRuleIds: ID[]): ApprovalRule[];
6
+ /**
7
+ * Returns the amount-range portion of a rule's criteria, if any.
8
+ *
9
+ * Use this whenever a caller needs to read amount min/max from the new
10
+ * discriminated `Criteria` union without inlining a type guard.
11
+ */
12
+ export declare function getAmountCriteria(criteria: Criteria[]): AmountCriteria | undefined;
13
+ export declare function getVendorCriteria(criteria: Criteria[]): VendorCriteria | undefined;
14
+ export declare function getDepartmentCriteria(criteria: Criteria[]): DepartmentCriteria | undefined;
15
+ /**
16
+ * Returns all rules of the given entityType, sorted by `priority` ascending.
17
+ *
18
+ * Rules without a `priority` value sort to the end (treated as Infinity).
19
+ * Use this for the list page where rules render in evaluation order.
20
+ */
21
+ export declare function getApprovalRulesByEntityType(approvalRuleState: ApprovalRuleState, entityType: EntityType): ApprovalRule[];
22
+ /**
23
+ * Returns the tenant's fallback rule for the given entityType, if one exists.
24
+ *
25
+ * The fallback rule is shown by the "fallback engine active" banner on the
26
+ * list page and is used to handle bills/reimbursements that match no other rule.
27
+ */
28
+ export declare function getFallbackApprovalRule(approvalRuleState: ApprovalRuleState, entityType: EntityType): ApprovalRule | undefined;
@@ -2,6 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getApprovalRuleById = getApprovalRuleById;
4
4
  exports.getApprovalRulesByIds = getApprovalRulesByIds;
5
+ exports.getAmountCriteria = getAmountCriteria;
6
+ exports.getVendorCriteria = getVendorCriteria;
7
+ exports.getDepartmentCriteria = getDepartmentCriteria;
8
+ exports.getApprovalRulesByEntityType = getApprovalRulesByEntityType;
9
+ exports.getFallbackApprovalRule = getFallbackApprovalRule;
5
10
  function getApprovalRuleById(approvalRuleState, approvalRuleId) {
6
11
  return approvalRuleState.approvalRuleById[approvalRuleId] ?? undefined;
7
12
  }
@@ -11,3 +16,38 @@ function getApprovalRulesByIds(approvalRuleState, approvalRuleIds) {
11
16
  .filter((value) => value != null);
12
17
  return approvalRulesByIds;
13
18
  }
19
+ /**
20
+ * Returns the amount-range portion of a rule's criteria, if any.
21
+ *
22
+ * Use this whenever a caller needs to read amount min/max from the new
23
+ * discriminated `Criteria` union without inlining a type guard.
24
+ */
25
+ function getAmountCriteria(criteria) {
26
+ return criteria.find((c) => c.type === 'amount');
27
+ }
28
+ function getVendorCriteria(criteria) {
29
+ return criteria.find((c) => c.type === 'vendor');
30
+ }
31
+ function getDepartmentCriteria(criteria) {
32
+ return criteria.find((c) => c.type === 'department');
33
+ }
34
+ /**
35
+ * Returns all rules of the given entityType, sorted by `priority` ascending.
36
+ *
37
+ * Rules without a `priority` value sort to the end (treated as Infinity).
38
+ * Use this for the list page where rules render in evaluation order.
39
+ */
40
+ function getApprovalRulesByEntityType(approvalRuleState, entityType) {
41
+ return Object.values(approvalRuleState.approvalRuleById)
42
+ .filter((rule) => rule.entityType === entityType)
43
+ .sort((a, b) => (a.priority ?? Infinity) - (b.priority ?? Infinity));
44
+ }
45
+ /**
46
+ * Returns the tenant's fallback rule for the given entityType, if one exists.
47
+ *
48
+ * The fallback rule is shown by the "fallback engine active" banner on the
49
+ * list page and is used to handle bills/reimbursements that match no other rule.
50
+ */
51
+ function getFallbackApprovalRule(approvalRuleState, entityType) {
52
+ return Object.values(approvalRuleState.approvalRuleById).find((rule) => rule.entityType === entityType && rule.isFallback === true);
53
+ }
@@ -25,14 +25,37 @@ export interface Actor {
25
25
  subType?: AttributeType | RoleType;
26
26
  userId?: ID;
27
27
  }
28
- export interface Criteria {
29
- range: {
30
- min: Amount;
31
- max?: Amount;
32
- };
33
- rangeEntity: string;
34
- rangeType: string;
28
+ /**
29
+ * Approval rule criteria — discriminated union over the three condition
30
+ * types the wire format supports: amount, vendor, and department.
31
+ *
32
+ * State-side, each rule carries a `Criteria[]` with at most one variant of
33
+ * each kind (e.g. one AmountCriteria + one VendorCriteria + one DepartmentCriteria).
34
+ *
35
+ * Form-side, the rule create/edit form uses `ApprovalRuleFormCriteria`
36
+ * (defined in commonState) — a structured object with named slots for
37
+ * amount/vendor/department. Epics translate between the two.
38
+ */
39
+ export type AmountComparator = 'greater_than' | 'less_than' | 'range';
40
+ export interface AmountCriteria {
41
+ comparator: AmountComparator;
42
+ type: 'amount';
43
+ /** Upper bound. Present for `less_than` and `range`. */
44
+ max?: Amount;
45
+ /** Lower bound. Present for `greater_than` and `range`. */
46
+ min?: Amount;
47
+ }
48
+ export interface VendorCriteria {
49
+ operator: 'is' | 'is_not';
50
+ type: 'vendor';
51
+ vendorIds: ID[];
52
+ }
53
+ export interface DepartmentCriteria {
54
+ departmentIds: ID[];
55
+ operator: 'is' | 'is_not';
56
+ type: 'department';
35
57
  }
58
+ export type Criteria = AmountCriteria | VendorCriteria | DepartmentCriteria;
36
59
  export interface Step {
37
60
  actors: Actor[];
38
61
  operator: StepOperatorType;
@@ -51,7 +74,25 @@ export interface ApprovalRule {
51
74
  entityType: EntityType;
52
75
  steps: Step[];
53
76
  updateTime: ZeniDate;
77
+ /** Backend bookkeeping: whether approval steps must run serialized. */
78
+ areApprovalsSerialized?: boolean;
79
+ /** Free-text description shown beneath the rule name on the list page. */
80
+ description?: string;
81
+ /** True when this rule is the tenant's default fallback rule. */
82
+ isFallback?: boolean;
54
83
  name?: string;
84
+ /** Backend bookkeeping: number of entities currently pending against this rule. */
85
+ pendingApprovalsCount?: number;
86
+ /** Backend bookkeeping: status of background updates against this rule. */
87
+ pendingEntityApprovalUpdateStatus?: string;
88
+ /** Rule's position in the evaluation order; lower number = higher priority. */
89
+ priority?: number;
90
+ /**
91
+ * When on, the creator of a bill/reimbursement cannot auto-approve
92
+ * their own request even if they are also an approver. Backend spec
93
+ * is still pending — kept optional for now.
94
+ */
95
+ separationOfDuties?: boolean;
55
96
  version?: number | string;
56
97
  }
57
98
  export interface Approvers {
@@ -113,7 +113,6 @@ function chatHistoryToQA(history) {
113
113
  chainOfThoughtActionSteps: [],
114
114
  artifact: parsedAgentMessage.artifact ?? undefined,
115
115
  artifactTitle: parsedAgentMessage.artifact_title ?? undefined,
116
- artifactHostUrl: parsedAgentMessage.artifact_host_url ?? undefined,
117
116
  },
118
117
  createdAt: zeniDate(agentMessage.created_at),
119
118
  };
@@ -183,7 +182,7 @@ const toAiCfoVisualizationData = (data) => {
183
182
  };
184
183
  };
185
184
  const toResponseBlockType = (answer, userId) => {
186
- const { metadata, state, type, text, visualization, table, follow_up_questions, assumptions, insights, summarized_title, artifact, artifact_title, artifact_host_url, } = answer;
185
+ const { metadata, state, type, text, visualization, table, follow_up_questions, assumptions, insights, summarized_title, artifact, artifact_title, } = answer;
187
186
  const { chat_session_id, message_id, timestamp, chain_of_thought_action_steps: chainOfThoughtActionSteps = [], } = metadata;
188
187
  return {
189
188
  messageId: message_id,
@@ -203,7 +202,6 @@ const toResponseBlockType = (answer, userId) => {
203
202
  chainOfThoughtActionSteps,
204
203
  artifact: artifact ?? undefined,
205
204
  artifactTitle: artifact_title ?? undefined,
206
- artifactHostUrl: artifact_host_url ?? undefined,
207
205
  },
208
206
  createdAt: zeniDate(timestamp),
209
207
  };
@@ -14,18 +14,139 @@ export const toApprovalRule = (payload) => ({
14
14
  version: payload.version,
15
15
  criteria: toApprovalCriteria(payload.criteria),
16
16
  steps: toApprovalSteps(payload.steps),
17
+ description: payload.description ?? undefined,
18
+ priority: payload.priority,
19
+ isFallback: payload.is_fallback ?? false,
20
+ // separationOfDuties: backend doesn't ship this field yet; left undefined
21
+ // until the spec is finalized.
22
+ areApprovalsSerialized: payload.are_approvals_serialized,
23
+ pendingApprovalsCount: payload.pending_approvals_count,
24
+ pendingEntityApprovalUpdateStatus: payload.pending_entity_approval_update_status,
17
25
  });
26
+ /**
27
+ * Maps the wire-level criteria block into the state-side discriminated
28
+ * `Criteria[]` union.
29
+ *
30
+ * The wire keeps each condition as a flat row inside `conditions[]`.
31
+ * Amount comparisons can show up as a `gte` only, a `lte` only, or a
32
+ * `gte` + `lte` pair — those collapse into a single AmountCriteria whose
33
+ * `comparator` is `greater_than`, `less_than`, or `range` respectively.
34
+ *
35
+ * Vendor and department conditions map one-to-one: `in` becomes `is`,
36
+ * `not_in` becomes `is_not`.
37
+ *
38
+ * Resilient to two forms of upstream drift:
39
+ * - Missing / null criteria → returns [].
40
+ * - Legacy criteria shape (array of amount-range objects, the pre-3.0
41
+ * wire) → converted to an AmountCriteria so existing rules in production
42
+ * still render while the backend rolls out.
43
+ */
18
44
  const toApprovalCriteria = (payload) => {
19
- return payload.map((criteria) => ({
20
- rangeType: criteria.type,
21
- rangeEntity: criteria.range_entity,
22
- range: {
23
- min: toAmount(criteria.min, criteria.currency_code, criteria.currency_symbol),
24
- max: criteria.max != null
25
- ? toAmount(criteria.max, criteria.currency_code, criteria.currency_symbol)
26
- : undefined,
27
- },
28
- }));
45
+ if (payload == null) {
46
+ return [];
47
+ }
48
+ if (Array.isArray(payload)) {
49
+ // Legacy pre-3.0 wire: an array of amount-range entries.
50
+ return legacyAmountRangeToCriteria(payload);
51
+ }
52
+ if (!Array.isArray(payload.conditions)) {
53
+ return [];
54
+ }
55
+ const results = [];
56
+ const amountCriteria = toAmountCriteria(payload);
57
+ if (amountCriteria != null) {
58
+ results.push(amountCriteria);
59
+ }
60
+ payload.conditions
61
+ .filter((condition) => condition.field === 'vendor_id')
62
+ .forEach((condition) => {
63
+ const vendor = toVendorCriteria(condition);
64
+ if (vendor != null) {
65
+ results.push(vendor);
66
+ }
67
+ });
68
+ payload.conditions
69
+ .filter((condition) => condition.field === 'department_id')
70
+ .forEach((condition) => {
71
+ const department = toDepartmentCriteria(condition);
72
+ if (department != null) {
73
+ results.push(department);
74
+ }
75
+ });
76
+ return results;
77
+ };
78
+ const legacyAmountRangeToCriteria = (legacy) => {
79
+ if (legacy.length === 0) {
80
+ return [];
81
+ }
82
+ const first = legacy[0] ?? {};
83
+ const currency = first.currency_code ?? 'USD';
84
+ const symbol = first.currency_symbol ?? '$';
85
+ const min = typeof first.min === 'number'
86
+ ? toAmount(first.min, currency, symbol)
87
+ : undefined;
88
+ const max = typeof first.max === 'number'
89
+ ? toAmount(first.max, currency, symbol)
90
+ : undefined;
91
+ if (min == null && max == null) {
92
+ return [];
93
+ }
94
+ const comparator = min != null && max != null
95
+ ? 'range'
96
+ : min != null
97
+ ? 'greater_than'
98
+ : 'less_than';
99
+ return [{ type: 'amount', comparator, min, max }];
100
+ };
101
+ const toAmountCriteria = (payload) => {
102
+ if (!Array.isArray(payload.conditions)) {
103
+ return undefined;
104
+ }
105
+ const amountConditions = payload.conditions.filter((condition) => condition.field === 'amount');
106
+ const gteCondition = amountConditions.find((condition) => condition.type === 'gte' || condition.type === 'eq');
107
+ const lteCondition = amountConditions.find((condition) => condition.type === 'lte');
108
+ if (gteCondition == null && lteCondition == null) {
109
+ return undefined;
110
+ }
111
+ const currencyCode = payload.currency_code ?? 'USD';
112
+ const currencySymbol = payload.currency_symbol ?? '$';
113
+ const min = typeof gteCondition?.value === 'number'
114
+ ? toAmount(gteCondition.value, currencyCode, currencySymbol)
115
+ : undefined;
116
+ const max = typeof lteCondition?.value === 'number'
117
+ ? toAmount(lteCondition.value, currencyCode, currencySymbol)
118
+ : undefined;
119
+ let comparator;
120
+ if (min != null && max != null) {
121
+ comparator = 'range';
122
+ }
123
+ else if (min != null) {
124
+ comparator = 'greater_than';
125
+ }
126
+ else {
127
+ comparator = 'less_than';
128
+ }
129
+ return { type: 'amount', comparator, min, max };
130
+ };
131
+ const toVendorCriteria = (condition) => {
132
+ if (!Array.isArray(condition.value)) {
133
+ return undefined;
134
+ }
135
+ return {
136
+ type: 'vendor',
137
+ operator: condition.type === 'not_in' ? 'is_not' : 'is',
138
+ vendorIds: condition.value,
139
+ };
140
+ };
141
+ const toDepartmentCriteria = (condition) => {
142
+ if (!Array.isArray(condition.value)) {
143
+ return undefined;
144
+ }
145
+ return {
146
+ type: 'department',
147
+ operator: condition.type === 'not_in' ? 'is_not' : 'is',
148
+ departmentIds: condition.value,
149
+ };
29
150
  };
30
151
  const toApprovalSteps = (payload) => {
31
152
  return payload.map((step) => ({
@@ -7,3 +7,38 @@ export function getApprovalRulesByIds(approvalRuleState, approvalRuleIds) {
7
7
  .filter((value) => value != null);
8
8
  return approvalRulesByIds;
9
9
  }
10
+ /**
11
+ * Returns the amount-range portion of a rule's criteria, if any.
12
+ *
13
+ * Use this whenever a caller needs to read amount min/max from the new
14
+ * discriminated `Criteria` union without inlining a type guard.
15
+ */
16
+ export function getAmountCriteria(criteria) {
17
+ return criteria.find((c) => c.type === 'amount');
18
+ }
19
+ export function getVendorCriteria(criteria) {
20
+ return criteria.find((c) => c.type === 'vendor');
21
+ }
22
+ export function getDepartmentCriteria(criteria) {
23
+ return criteria.find((c) => c.type === 'department');
24
+ }
25
+ /**
26
+ * Returns all rules of the given entityType, sorted by `priority` ascending.
27
+ *
28
+ * Rules without a `priority` value sort to the end (treated as Infinity).
29
+ * Use this for the list page where rules render in evaluation order.
30
+ */
31
+ export function getApprovalRulesByEntityType(approvalRuleState, entityType) {
32
+ return Object.values(approvalRuleState.approvalRuleById)
33
+ .filter((rule) => rule.entityType === entityType)
34
+ .sort((a, b) => (a.priority ?? Infinity) - (b.priority ?? Infinity));
35
+ }
36
+ /**
37
+ * Returns the tenant's fallback rule for the given entityType, if one exists.
38
+ *
39
+ * The fallback rule is shown by the "fallback engine active" banner on the
40
+ * list page and is used to handle bills/reimbursements that match no other rule.
41
+ */
42
+ export function getFallbackApprovalRule(approvalRuleState, entityType) {
43
+ return Object.values(approvalRuleState.approvalRuleById).find((rule) => rule.entityType === entityType && rule.isFallback === true);
44
+ }
package/lib/esm/index.js CHANGED
@@ -33,6 +33,7 @@ import { getAccountGroupKey, toAccountGroupType, } from './entity/accountGroup/a
33
33
  import { getAccountReconByAccountIdAndSelectedPeriod, } from './entity/accountRecon/accountReconSelector';
34
34
  import { getAddressByAddressId } from './entity/address/addressSelector';
35
35
  import { toAttributeOrRoleTypeStrict, toAttributeTypeStrict, toRoleTypeStrict, } from './entity/approvalRule/approvalRuleState';
36
+ import { getAmountCriteria, getApprovalRulesByEntityType, getDepartmentCriteria, getFallbackApprovalRule, getVendorCriteria, } from './entity/approvalRule/approvalRuleSelector';
36
37
  import { toOutsideZeniPaymentModeType, } from './entity/billPay/billTransaction/billTransactionState';
37
38
  import { toRecurringBillFrequency, toRecurringBillFrequencyStrict, } from './entity/billPay/recurringBills/recurringBillsState';
38
39
  import { toCardAddressType, toCardType, toCreditLimitFrequencyCodeType, toShippingAddressType, } from './entity/chargeCard/chargeCard';
@@ -569,7 +570,7 @@ export { getUserFinancialAccount };
569
570
  export { fetchRemiSetupView, updateMileageDetails, saveRemiSetupViewDataInLocalStore, acceptRemiTerms, acceptEmployeeRemiTerms, clearRemiSetupView, getRemiSetupViewDetails, getRemiBusinessVerificationDetails, };
570
571
  export { VERIFIED_PAYMENT_ACCOUNT_PROVIDER_VERIFICATION_STATUS, };
571
572
  export { isSuccessResponse, isInvalidSessionError, isAccessDeniedError, };
572
- export { toAttributeTypeStrict, toAttributeOrRoleTypeStrict, toRoleTypeStrict, };
573
+ export { getAmountCriteria, getApprovalRulesByEntityType, getDepartmentCriteria, getFallbackApprovalRule, getVendorCriteria, toAttributeTypeStrict, toAttributeOrRoleTypeStrict, toRoleTypeStrict, };
573
574
  export { getBillPaySetupApproverView, getBillPaySetupApproverUpdateDataView, fetchBillPaySetupApproverView, fetchBillPayApproversDetails, fetchBillPayApproversList, deleteBillPayApprovalRule, saveBillPaySetupApproverViewUpdateData, saveBillPaySetupApproverViewUpdates, setBillsSetupApproverViewListeningToPusherEvent, initializeBillPaySetupApproverViewUpdateData, clearBillPaySetupApproverViewUpdateData, clearBillPaySetupApproverView, };
574
575
  export { getRemiSetupApproverView, getRemiSetupApproverUpdateDataView, fetchRemiSetupApproverView, fetchRemiApproversList, fetchRemiApproversDetails, deleteRemiApprovalRule, saveRemiSetupApproverViewUpdateData, saveRemiSetupApproverViewUpdates, initializeRemiSetupApproverViewUpdateData, setRemiSetupApproverViewListeningToPusherEvent, clearRemiSetupApproverViewUpdateData, clearRemiSetupApproverView, };
575
576
  export { getRemiDetailView, checkApproveRejectBtnShowForRemi, };