@zeniai/client-epic-state 5.0.65-betaML2 → 5.0.65-betaRR1

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 (115) hide show
  1. package/lib/entity/aiCfo/aiCfoPayload.d.ts +135 -1
  2. package/lib/entity/aiCfo/aiCfoReducer.js +111 -2
  3. package/lib/entity/aiCfo/aiCfoState.d.ts +141 -4
  4. package/lib/entity/aiCfo/aiCfoState.js +10 -1
  5. package/lib/entity/cardPolicy/cardPolicyPayload.d.ts +171 -0
  6. package/lib/entity/cardPolicy/cardPolicyPayload.js +112 -0
  7. package/lib/entity/cardPolicy/cardPolicyReducer.d.ts +13 -0
  8. package/lib/entity/cardPolicy/cardPolicyReducer.js +184 -0
  9. package/lib/entity/cardPolicy/cardPolicySelector.d.ts +28 -0
  10. package/lib/entity/cardPolicy/cardPolicySelector.js +86 -0
  11. package/lib/entity/cardPolicy/cardPolicyState.d.ts +162 -0
  12. package/lib/entity/cardPolicy/cardPolicyState.js +14 -0
  13. package/lib/entity/cardPolicy/extractPolicyDocumentEpic.d.ts +18 -0
  14. package/lib/entity/cardPolicy/extractPolicyDocumentEpic.js +54 -0
  15. package/lib/entity/cardPolicy/fetchCardPolicyVendorOptionsEpic.d.ts +26 -0
  16. package/lib/entity/cardPolicy/fetchCardPolicyVendorOptionsEpic.js +47 -0
  17. package/lib/entity/tenant/clearAllEpic.d.ts +6 -2
  18. package/lib/entity/tenant/clearAllEpic.js +8 -0
  19. package/lib/epic.d.ts +9 -2
  20. package/lib/epic.js +9 -2
  21. package/lib/esm/entity/aiCfo/aiCfoReducer.js +112 -3
  22. package/lib/esm/entity/aiCfo/aiCfoState.js +7 -0
  23. package/lib/esm/entity/cardPolicy/cardPolicyPayload.js +101 -0
  24. package/lib/esm/entity/cardPolicy/cardPolicyReducer.js +180 -0
  25. package/lib/esm/entity/cardPolicy/cardPolicySelector.js +66 -0
  26. package/lib/esm/entity/cardPolicy/cardPolicyState.js +9 -0
  27. package/lib/esm/entity/cardPolicy/extractPolicyDocumentEpic.js +50 -0
  28. package/lib/esm/entity/cardPolicy/fetchCardPolicyVendorOptionsEpic.js +43 -0
  29. package/lib/esm/entity/tenant/clearAllEpic.js +8 -0
  30. package/lib/esm/epic.js +9 -2
  31. package/lib/esm/index.js +21 -7
  32. package/lib/esm/reducer.js +12 -0
  33. package/lib/esm/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.js +1 -21
  34. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +14 -117
  35. package/lib/esm/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +0 -28
  36. package/lib/esm/view/recommendation/recommendationHelper.js +0 -29
  37. package/lib/esm/view/recommendation/recommendationReducer.js +1 -36
  38. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/cardPolicyDetailReducer.js +70 -0
  39. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/cardPolicyDetailSelector.js +22 -0
  40. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/cardPolicyDetailState.js +1 -0
  41. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/fetchCardPolicyDetailEpic.js +44 -0
  42. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/updateCardPolicyEpic.js +49 -0
  43. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/archiveCardPolicyEpic.js +42 -0
  44. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/cardPolicyListReducer.js +72 -0
  45. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/cardPolicyListSelector.js +21 -0
  46. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/cardPolicyListState.js +1 -0
  47. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/fetchCardPolicyListEpic.js +46 -0
  48. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.js +50 -0
  49. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.js +6 -0
  50. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyState.js +1 -0
  51. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyTemplateEpic.js +34 -0
  52. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/fetchCardPolicyMccCategoriesEpic.js +17 -0
  53. package/lib/esm/view/transactionDetail/transactionDetailReducer.js +1 -110
  54. package/lib/esm/view/transactionDetail/transactionDetailSelector.js +0 -22
  55. package/lib/index.d.ts +20 -8
  56. package/lib/index.js +119 -35
  57. package/lib/reducer.d.ts +12 -0
  58. package/lib/reducer.js +12 -0
  59. package/lib/view/companyHealthMetricView/companyHealthMetricViewSelector.d.ts +1 -1
  60. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +0 -1
  61. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.js +2 -23
  62. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +1 -14
  63. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +14 -117
  64. package/lib/view/expenseAutomationView/selectorTypes/transactionsViewSelectorTypes.d.ts +0 -2
  65. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.d.ts +0 -3
  66. package/lib/view/expenseAutomationView/selectors/transactionCategorizationSelector.js +0 -29
  67. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +0 -1
  68. package/lib/view/onboardingView/cockpitView/types/onboardingCockpitViewTypes.d.ts +1 -1
  69. package/lib/view/people/peopleTypes.d.ts +1 -1
  70. package/lib/view/recommendation/recommendationHelper.d.ts +0 -9
  71. package/lib/view/recommendation/recommendationHelper.js +1 -31
  72. package/lib/view/recommendation/recommendationReducer.d.ts +1 -18
  73. package/lib/view/recommendation/recommendationReducer.js +2 -37
  74. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/cardPolicyDetailReducer.d.ts +14 -0
  75. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/cardPolicyDetailReducer.js +74 -0
  76. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/cardPolicyDetailSelector.d.ts +21 -0
  77. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/cardPolicyDetailSelector.js +28 -0
  78. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/cardPolicyDetailState.d.ts +25 -0
  79. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/cardPolicyDetailState.js +2 -0
  80. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/fetchCardPolicyDetailEpic.d.ts +19 -0
  81. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/fetchCardPolicyDetailEpic.js +48 -0
  82. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/updateCardPolicyEpic.d.ts +18 -0
  83. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/updateCardPolicyEpic.js +53 -0
  84. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/archiveCardPolicyEpic.d.ts +16 -0
  85. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/archiveCardPolicyEpic.js +46 -0
  86. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/cardPolicyListReducer.d.ts +15 -0
  87. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/cardPolicyListReducer.js +76 -0
  88. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/cardPolicyListSelector.d.ts +21 -0
  89. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/cardPolicyListSelector.js +27 -0
  90. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/cardPolicyListState.d.ts +19 -0
  91. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/cardPolicyListState.js +2 -0
  92. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/fetchCardPolicyListEpic.d.ts +19 -0
  93. package/lib/view/spendManagement/chargeCards/cardPolicy/cardPolicyList/fetchCardPolicyListEpic.js +50 -0
  94. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.d.ts +12 -0
  95. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.js +54 -0
  96. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.d.ts +4 -0
  97. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.js +10 -0
  98. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyState.d.ts +15 -0
  99. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyState.js +2 -0
  100. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyTemplateEpic.d.ts +8 -0
  101. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyTemplateEpic.js +38 -0
  102. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/fetchCardPolicyMccCategoriesEpic.d.ts +15 -0
  103. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/fetchCardPolicyMccCategoriesEpic.js +21 -0
  104. package/lib/view/spendManagement/chargeCards/chargeCardList/chargeCardList.d.ts +1 -1
  105. package/lib/view/taskManager/taskListView/taskListReducer.d.ts +1 -1
  106. package/lib/view/transactionDetail/transactionDetailReducer.d.ts +1 -11
  107. package/lib/view/transactionDetail/transactionDetailReducer.js +2 -111
  108. package/lib/view/transactionDetail/transactionDetailSelector.d.ts +0 -2
  109. package/lib/view/transactionDetail/transactionDetailSelector.js +1 -24
  110. package/lib/view/transactionDetail/transactionDetailTypes.d.ts +0 -1
  111. package/package.json +1 -1
  112. package/lib/esm/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.js +0 -88
  113. package/lib/tsconfig.typecheck.tsbuildinfo +0 -1
  114. package/lib/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.d.ts +0 -9
  115. package/lib/view/recommendation/fetchEntityRecommendationsForLineUpdateEpic.js +0 -92
@@ -1,6 +1,6 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
2
  import { date as zeniDate } from '../../zeniDayJS';
3
- import { ALL_AI_CFO_ANSWER_RESPONSE_TYPES, toAiCfoAnswerResponseType, toAiCfoAnswerResponseTypeStrict, toAiCfoAnswerStateType, toAiCfoVisualizationTypeStrict, toMessageSender, toMessageType, toYFormatScaleStrict, toYFormatTypeStrict, toYFormatUnitStrict, } from './aiCfoState';
3
+ import { ALL_AI_CFO_ANSWER_RESPONSE_TYPES, toAiCfoAnswerResponseType, toAiCfoAnswerResponseTypeStrict, toAiCfoAnswerStateType, toAiCfoVisualizationTypeStrict, toInteractiveFormTypeStrict, toMessageSender, toMessageType, toYFormatScaleStrict, toYFormatTypeStrict, toYFormatUnitStrict, } from './aiCfoState';
4
4
  export const initialAiCfoState = {
5
5
  aiCfoByChatSessionId: {},
6
6
  partialQuestionAnswers: {},
@@ -135,11 +135,120 @@ export const toAiCfoVisualization = (visualization) => {
135
135
  if (visualization.type == null || visualization.data == null) {
136
136
  return undefined;
137
137
  }
138
+ const normalizedType = toAiCfoVisualizationTypeStrict(visualization.type) ?? 'table';
138
139
  return {
139
- type: toAiCfoVisualizationTypeStrict(visualization.type) ?? 'table',
140
- data: toAiCfoVisualizationData(visualization.data),
140
+ type: normalizedType,
141
+ data: normalizedType === 'interactive_form'
142
+ ? toInteractiveFormVisualization(visualization.data)
143
+ : toAiCfoVisualizationData(visualization.data),
141
144
  };
142
145
  };
146
+ const toInteractiveFormVisualization = (data) => {
147
+ const formType = toInteractiveFormTypeStrict(data.form_type) ?? 'cards_creation';
148
+ if (formType === 'card_policy') {
149
+ return {
150
+ formType: 'card_policy',
151
+ payload: toCardPolicyInitialData(data.payload),
152
+ };
153
+ }
154
+ return {
155
+ formType: 'cards_creation',
156
+ payload: toCardsCreationInitialData(data.payload),
157
+ };
158
+ };
159
+ const toCardsCreationInitialData = (payload) => {
160
+ const cardOptions = (payload.card_options ?? []).map((option) => ({
161
+ id: option.id,
162
+ label: option.label,
163
+ description: option.description ?? undefined,
164
+ }));
165
+ if (payload.mode === 'cards-only') {
166
+ return {
167
+ mode: 'cards-only',
168
+ cardOptions,
169
+ cardRows: (payload.card_rows ?? []).map((row) => ({
170
+ cardOptionId: row.card_option_id,
171
+ id: row.id,
172
+ label: row.label ?? undefined,
173
+ recommendedLimit: row.recommended_limit ?? undefined,
174
+ suggestedOwnerUserId: row.suggested_owner_user_id ?? undefined,
175
+ })),
176
+ };
177
+ }
178
+ return {
179
+ mode: 'team-then-cards',
180
+ cardOptions,
181
+ suggestedOwnerUserIdByTeamId: Object.fromEntries(Object.entries(payload.suggested_owner_user_id_by_team_id ?? {}).map(([teamId, userId]) => [teamId, userId ?? undefined])),
182
+ teamRows: (payload.team_rows ?? []).map((row) => ({
183
+ avgMonthlySpend: row.avg_monthly_spend,
184
+ id: row.id,
185
+ label: row.label,
186
+ recommendedLimit: row.recommended_limit,
187
+ recommendedLimitCadence: row.recommended_limit_cadence ?? undefined,
188
+ })),
189
+ };
190
+ };
191
+ const toCardPolicyInitialData = (payload) => {
192
+ const suggestedPolicies = (payload.suggested_policies ?? []).map((policy) => ({
193
+ id: policy.id,
194
+ isChecked: policy.is_checked,
195
+ name: policy.name,
196
+ chipGroups: (policy.chip_groups ?? []).map((group) => ({
197
+ id: group.id,
198
+ label: group.label,
199
+ chips: (group.chips ?? []).map((chip) => ({
200
+ id: chip.id,
201
+ label: chip.label,
202
+ chipKind: chip.chip_kind,
203
+ })),
204
+ })),
205
+ }));
206
+ if (payload.policy_kind === 'review-only') {
207
+ return {
208
+ policyKind: 'review-only',
209
+ suggestedPolicies,
210
+ };
211
+ }
212
+ const toCardRowSeed = (card) => ({
213
+ cardName: card.card_name,
214
+ id: card.id,
215
+ isChecked: card.is_checked,
216
+ last4: card.last4,
217
+ limitAmount: card.limit_amount,
218
+ limitCadence: card.limit_cadence ?? undefined,
219
+ owner: {
220
+ userId: card.owner.user_id,
221
+ displayName: card.owner.display_name,
222
+ roleLabel: card.owner.role_label ?? undefined,
223
+ },
224
+ utilizationAmount: card.utilization_amount,
225
+ });
226
+ if (payload.policy_kind === 'doc-upload') {
227
+ return {
228
+ policyKind: 'doc-upload',
229
+ suggestedPolicies,
230
+ cards: (payload.cards ?? []).map(toCardRowSeed),
231
+ };
232
+ }
233
+ return {
234
+ policyKind: 'full-builder',
235
+ suggestedPolicies,
236
+ vendors: (payload.vendors ?? []).map(toCardPolicyTagSeed),
237
+ categories: (payload.categories ?? []).map(toCardPolicyTagSeed),
238
+ cards: (payload.cards ?? []).map(toCardRowSeed),
239
+ defaultLimits: (payload.default_limits ?? []).map((row) => ({
240
+ id: row.id,
241
+ isEnabled: row.is_enabled,
242
+ amount: row.amount ?? undefined,
243
+ })),
244
+ };
245
+ };
246
+ const toCardPolicyTagSeed = (tag) => ({
247
+ id: tag.id,
248
+ isSelected: tag.is_selected,
249
+ label: tag.label,
250
+ optionalCode: tag.optional_code ?? undefined,
251
+ });
143
252
  const toAiCfoVisualizationData = (data) => {
144
253
  if ('x' in data && 'y' in data) {
145
254
  // Normalize y to always be an array
@@ -7,7 +7,14 @@ export const ALL_AI_CFO_CHARTS_TYPES = [
7
7
  export const ALL_AI_CFO_VISUALIZATION_TYPES = [
8
8
  ...ALL_AI_CFO_CHARTS_TYPES,
9
9
  'table',
10
+ 'interactive_form',
10
11
  ];
12
+ export const ALL_INTERACTIVE_FORM_TYPES = [
13
+ 'cards_creation',
14
+ 'card_policy',
15
+ ];
16
+ export const toInteractiveFormType = (v) => stringToUnion(v, ALL_INTERACTIVE_FORM_TYPES);
17
+ export const toInteractiveFormTypeStrict = (v) => stringToUnionStrict(v, ALL_INTERACTIVE_FORM_TYPES);
11
18
  export const toAiCfoVisualizationType = (v) => stringToUnion(v, ALL_AI_CFO_VISUALIZATION_TYPES);
12
19
  export const toAiCfoVisualizationTypeStrict = (v) => stringToUnionStrict(v, ALL_AI_CFO_VISUALIZATION_TYPES);
13
20
  export const toAiCfoChartType = (v) => stringToUnion(v, ALL_AI_CFO_CHARTS_TYPES);
@@ -0,0 +1,101 @@
1
+ import { date } from '../../zeniDayJS';
2
+ import { toCardPolicyTemplateMode, toCardPolicyTemplateStatus, } from './cardPolicyState';
3
+ export const toMccCategory = (payload) => ({
4
+ count: payload.count,
5
+ mccCodes: payload.mcc_codes ?? [],
6
+ name: payload.name,
7
+ });
8
+ export const toCardPolicyVendorSearchOption = (payload) => ({
9
+ vendorId: payload.vendor_id,
10
+ name: payload.name,
11
+ });
12
+ export const toCardPolicyStats = (payload) => ({
13
+ activePolicies: payload.active_policies,
14
+ totalCards: payload.total_cards,
15
+ totalTemplates: payload.total_templates,
16
+ });
17
+ export const toCardPolicyTemplateList = (data) => (data.templates ?? []).map((row) => toCardPolicyTemplate(row.data.template));
18
+ const toCardPolicyTemplateEntityList = (payload) => ({
19
+ categoryCodes: payload.category_codes ?? [],
20
+ merchantNames: payload.merchant_names ?? [],
21
+ });
22
+ export const toCardPolicyTemplate = (payload) => ({
23
+ allowedEntity: toCardPolicyTemplateEntityList(payload.allowed_entity),
24
+ appliedCards: payload.applied_cards ?? [],
25
+ blockedEntity: toCardPolicyTemplateEntityList(payload.blocked_entity),
26
+ cardsCount: payload.cards_count,
27
+ categoryRestrictions: payload.category_restrictions ?? [],
28
+ createdAt: date(payload.created_at),
29
+ createdBy: payload.created_by,
30
+ description: payload.description,
31
+ mode: toCardPolicyTemplateMode(payload.mode),
32
+ name: payload.name,
33
+ requiredReceiptThreshold: payload.required_receipt_threshold,
34
+ spendLimits: {
35
+ transaction: payload.spend_limits.transaction,
36
+ },
37
+ status: toCardPolicyTemplateStatus(payload.status),
38
+ templateId: payload.template_id,
39
+ tenantId: payload.tenant_id,
40
+ updatedAt: date(payload.updated_at),
41
+ });
42
+ /**
43
+ * Re-shape a camelCase create request into the snake_case wire body.
44
+ * Used by the epic at request time.
45
+ */
46
+ export const toCreateCardPolicyTemplateRequestBody = (request) => ({
47
+ allowed_entity: {
48
+ category_codes: request.allowedEntity.categoryCodes,
49
+ merchant_names: request.allowedEntity.merchantNames,
50
+ },
51
+ apply_to_cards: request.applyToCards,
52
+ blocked_entity: {
53
+ category_codes: request.blockedEntity.categoryCodes,
54
+ merchant_names: request.blockedEntity.merchantNames,
55
+ },
56
+ description: request.description,
57
+ mode: request.mode ?? 'strict',
58
+ name: request.name,
59
+ required_receipt_threshold: request.requiredReceiptThreshold,
60
+ spend_limits: {
61
+ transaction: request.spendLimits.transaction,
62
+ },
63
+ });
64
+ // Update body uses the exact same wire shape as create — the PUT
65
+ // endpoint is a full replace.
66
+ export const toUpdateCardPolicyTemplateRequestBody = toCreateCardPolicyTemplateRequestBody;
67
+ /**
68
+ * Seed an edit-form draft (`CreateCardPolicyTemplateRequest`) from an
69
+ * existing camelCased `CardPolicyTemplate`. Used by the detail-fetch
70
+ * epic so the edit page can render with the saved values pre-populated.
71
+ */
72
+ export const toCardPolicyEditFormDraft = (template) => ({
73
+ allowedEntity: {
74
+ categoryCodes: [...template.allowedEntity.categoryCodes],
75
+ merchantNames: [...template.allowedEntity.merchantNames],
76
+ },
77
+ applyToCards: [...template.appliedCards],
78
+ blockedEntity: {
79
+ categoryCodes: [...template.blockedEntity.categoryCodes],
80
+ merchantNames: [...template.blockedEntity.merchantNames],
81
+ },
82
+ description: template.description,
83
+ mode: template.mode,
84
+ name: template.name,
85
+ requiredReceiptThreshold: template.requiredReceiptThreshold,
86
+ spendLimits: { transaction: template.spendLimits.transaction },
87
+ });
88
+ export const toExtractedCardPolicyRules = (payload) => ({
89
+ allowedEntity: {
90
+ categoryCodes: payload.allowed_entity?.category_codes ?? [],
91
+ merchantNames: payload.allowed_entity?.merchant_names ?? [],
92
+ },
93
+ blockedEntity: {
94
+ categoryCodes: payload.blocked_entity?.category_codes ?? [],
95
+ merchantNames: payload.blocked_entity?.merchant_names ?? [],
96
+ },
97
+ confidenceScore: payload.confidence_score,
98
+ policyName: payload.policy_name,
99
+ requiredReceiptThreshold: payload.required_receipt_threshold,
100
+ transactionLimit: payload.transaction_limit,
101
+ });
@@ -0,0 +1,180 @@
1
+ import { createSlice } from '@reduxjs/toolkit';
2
+ import { toCardPolicyTemplate, toCardPolicyVendorSearchOption, toExtractedCardPolicyRules, toMccCategory, } from './cardPolicyPayload';
3
+ const initialPolicyDocumentExtractionState = {
4
+ fetchState: 'Not-Started',
5
+ error: undefined,
6
+ extractedRules: undefined,
7
+ uploadedFileName: undefined,
8
+ };
9
+ const initialVendorSearchState = {
10
+ fetchState: 'Not-Started',
11
+ searchString: '',
12
+ vendors: [],
13
+ error: undefined,
14
+ };
15
+ export const initialState = {
16
+ cardPolicyTemplateById: {},
17
+ mccCategories: [],
18
+ mccCategoriesError: undefined,
19
+ mccCategoriesFetchState: 'Not-Started',
20
+ policyDocumentExtraction: initialPolicyDocumentExtractionState,
21
+ suggestedAllowCategories: [],
22
+ suggestedAllowMerchants: [],
23
+ suggestedBlockCategories: [],
24
+ suggestedBlockMerchants: [],
25
+ vendorSearch: initialVendorSearchState,
26
+ cardPolicyStats: undefined,
27
+ };
28
+ const cardPolicy = createSlice({
29
+ name: 'cardPolicy',
30
+ initialState,
31
+ reducers: {
32
+ fetchCardPolicyMccCategories(draft) {
33
+ draft.mccCategoriesFetchState = 'In-Progress';
34
+ draft.mccCategoriesError = undefined;
35
+ },
36
+ updateCardPolicyMccCategories(draft, action) {
37
+ draft.mccCategories = (action.payload.categories ?? []).map(toMccCategory);
38
+ // Persist BE-suggested allow / block seeds alongside the universe.
39
+ // Both flows (manual create page + AI CFO interactive form) read
40
+ // these to pre-populate the chip cloud body before the user edits.
41
+ draft.suggestedAllowMerchants =
42
+ action.payload.suggested_allow_merchants ?? [];
43
+ draft.suggestedBlockMerchants =
44
+ action.payload.suggested_block_merchants ?? [];
45
+ draft.suggestedAllowCategories = (action.payload.suggested_allow_catagories ?? []).map(toMccCategory);
46
+ draft.suggestedBlockCategories = (action.payload.suggested_block_catagories ?? []).map(toMccCategory);
47
+ draft.mccCategoriesFetchState = 'Completed';
48
+ draft.mccCategoriesError = undefined;
49
+ },
50
+ updateCardPolicyMccCategoriesFailure(draft, action) {
51
+ draft.mccCategoriesFetchState = 'Error';
52
+ draft.mccCategoriesError = action.payload;
53
+ },
54
+ /**
55
+ * Store a newly-created (or refreshed) card-policy template keyed by
56
+ * `templateId`. Dispatched by the create / list / get epics; consumed
57
+ * by both manual and AI-CFO policy flows via `getCardPolicyTemplateById`.
58
+ */
59
+ updateCreatedCardPolicyTemplate(draft, action) {
60
+ const template = toCardPolicyTemplate(action.payload);
61
+ draft.cardPolicyTemplateById[template.templateId] = template;
62
+ },
63
+ /**
64
+ * Replace the entity store's `cardPolicyTemplateById` map with the
65
+ * full list returned by the templates-list endpoint. Full replace is
66
+ * intentional — templates that no longer exist on the server should be
67
+ * dropped from local state too. The view slice keeps the server-order
68
+ * ID list separately for stable list rendering.
69
+ */
70
+ updateCardPolicyTemplates(draft, action) {
71
+ const byId = {};
72
+ for (const template of action.payload) {
73
+ byId[template.templateId] = template;
74
+ }
75
+ draft.cardPolicyTemplateById = byId;
76
+ },
77
+ /**
78
+ * Drop a single template from the entity map. Dispatched by the
79
+ * archive (DELETE) epic on success; also usable by any future flow
80
+ * that needs to evict a template (e.g. AI-CFO rejection).
81
+ */
82
+ removeCardPolicyTemplate(draft, action) {
83
+ delete draft.cardPolicyTemplateById[action.payload];
84
+ },
85
+ /**
86
+ * Replace the tenant-wide policy stats. Dispatched alongside
87
+ * `updateCardPolicyTemplates` from the list epic, but lives on the
88
+ * entity slice so any consumer can read it without re-fetching the
89
+ * list.
90
+ */
91
+ updateCardPolicyStats(draft, action) {
92
+ draft.cardPolicyStats = action.payload;
93
+ },
94
+ /**
95
+ * Kick off the `POST /cards/1.0/policy-documents/extract` request.
96
+ *
97
+ * Synchronously flips the sub-slice to `In-Progress` and captures the
98
+ * first uploaded file's name (used by the manual `CardPolicyCreatePage`
99
+ * to render the "Policy.pdf ×" pill once the request completes) so
100
+ * downstream callers don't have to keep their own file refs.
101
+ *
102
+ * The accompanying `extractPolicyDocumentEpic` listens for this action,
103
+ * POSTs multipart form-data to the cards micro-service, and dispatches
104
+ * either `updatePolicyDocumentExtractionSuccess` or
105
+ * `updatePolicyDocumentExtractionFailure`.
106
+ *
107
+ * Designed to be dispatched from both the manual create page and a
108
+ * future AI CFO composer/upload surface — selectors return a single
109
+ * source of truth.
110
+ */
111
+ extractPolicyDocument: {
112
+ reducer(draft, action) {
113
+ draft.policyDocumentExtraction.fetchState = 'In-Progress';
114
+ draft.policyDocumentExtraction.error = undefined;
115
+ draft.policyDocumentExtraction.extractedRules = undefined;
116
+ draft.policyDocumentExtraction.uploadedFileName =
117
+ action.meta.uploadedFileName;
118
+ },
119
+ prepare(files) {
120
+ return {
121
+ payload: { files },
122
+ meta: { uploadedFileName: files[0]?.name },
123
+ };
124
+ },
125
+ },
126
+ updatePolicyDocumentExtractionSuccess(draft, action) {
127
+ draft.policyDocumentExtraction.fetchState = 'Completed';
128
+ draft.policyDocumentExtraction.error = undefined;
129
+ draft.policyDocumentExtraction.extractedRules =
130
+ toExtractedCardPolicyRules(action.payload);
131
+ },
132
+ updatePolicyDocumentExtractionFailure(draft, action) {
133
+ draft.policyDocumentExtraction.fetchState = 'Error';
134
+ draft.policyDocumentExtraction.error = action.payload;
135
+ draft.policyDocumentExtraction.extractedRules = undefined;
136
+ },
137
+ clearPolicyDocumentExtraction(draft) {
138
+ draft.policyDocumentExtraction = {
139
+ ...initialPolicyDocumentExtractionState,
140
+ };
141
+ },
142
+ /**
143
+ * Kick off `GET /accounting/1.0/vendors` (card-policy mode). When
144
+ * `searchString` is empty the BE returns the top vendors sorted by
145
+ * year-to-date spend; with a non-empty value it filters server-side.
146
+ *
147
+ * The accompanying `fetchCardPolicyVendorOptionsEpic` listens for
148
+ * this action, builds the `query` JSON (matching `fetchVendorsList`'s
149
+ * `view: "vendor_list"` shape) and dispatches success / failure.
150
+ *
151
+ * Designed to be dispatched from both the manual `CardPolicyCreate /
152
+ * EditScreen` (on mount + on every chip-picker keystroke, debounced)
153
+ * and the AI CFO interactive form's vendor step.
154
+ */
155
+ fetchCardPolicyVendorOptions: {
156
+ reducer(draft, action) {
157
+ draft.vendorSearch.fetchState = 'In-Progress';
158
+ draft.vendorSearch.searchString = action.payload.searchString;
159
+ draft.vendorSearch.error = undefined;
160
+ },
161
+ prepare(searchString) {
162
+ return { payload: { searchString: searchString ?? '' } };
163
+ },
164
+ },
165
+ updateCardPolicyVendorOptions(draft, action) {
166
+ draft.vendorSearch.vendors = (action.payload.vendors ?? []).map(toCardPolicyVendorSearchOption);
167
+ draft.vendorSearch.fetchState = 'Completed';
168
+ draft.vendorSearch.error = undefined;
169
+ },
170
+ updateCardPolicyVendorOptionsFailure(draft, action) {
171
+ draft.vendorSearch.fetchState = 'Error';
172
+ draft.vendorSearch.error = action.payload;
173
+ },
174
+ clearCardPolicy(draft) {
175
+ Object.assign(draft, initialState);
176
+ },
177
+ },
178
+ });
179
+ export const { fetchCardPolicyMccCategories, fetchCardPolicyVendorOptions, updateCardPolicyMccCategories, updateCardPolicyMccCategoriesFailure, updateCardPolicyVendorOptions, updateCardPolicyVendorOptionsFailure, updateCreatedCardPolicyTemplate, updateCardPolicyTemplates, removeCardPolicyTemplate, updateCardPolicyStats, extractPolicyDocument, updatePolicyDocumentExtractionSuccess, updatePolicyDocumentExtractionFailure, clearPolicyDocumentExtraction, clearCardPolicy, } = cardPolicy.actions;
180
+ export default cardPolicy.reducer;
@@ -0,0 +1,66 @@
1
+ export function getCardPolicyMccCategories(state) {
2
+ return state.mccCategories;
3
+ }
4
+ export function getCardPolicyMccCategoriesFetchState(state) {
5
+ return state.mccCategoriesFetchState;
6
+ }
7
+ export function getCardPolicyMccCategoriesError(state) {
8
+ return state.mccCategoriesError;
9
+ }
10
+ export function getCardPolicyTemplateById(state, templateId) {
11
+ return state.cardPolicyTemplateById[templateId];
12
+ }
13
+ export function getAllCardPolicyTemplates(state) {
14
+ return Object.values(state.cardPolicyTemplateById);
15
+ }
16
+ /**
17
+ * Resolve an ordered list of `templateId`s against the entity store and
18
+ * return the corresponding `CardPolicyTemplate[]` in the same order.
19
+ * Missing IDs (e.g. stale local state during a refetch) are silently
20
+ * dropped. Pair with `getCardPolicyTemplateIds` from the view slice to
21
+ * render the Policy List page in server order.
22
+ */
23
+ export function getCardPolicyTemplatesByIds(state, templateIds) {
24
+ return templateIds
25
+ .map((id) => state.cardPolicyTemplateById[id])
26
+ .filter((template) => template != null);
27
+ }
28
+ export function getCardPolicyStats(state) {
29
+ return state.cardPolicyStats;
30
+ }
31
+ // ── Policy document extraction ───────────────────────────────────────
32
+ export function getPolicyDocumentExtractionFetchState(state) {
33
+ return state.policyDocumentExtraction.fetchState;
34
+ }
35
+ export function getPolicyDocumentExtractionError(state) {
36
+ return state.policyDocumentExtraction.error;
37
+ }
38
+ export function getExtractedCardPolicyRules(state) {
39
+ return state.policyDocumentExtraction.extractedRules;
40
+ }
41
+ export function getUploadedPolicyDocumentFileName(state) {
42
+ return state.policyDocumentExtraction.uploadedFileName;
43
+ }
44
+ // ── Suggested allow / block seeds (shipped alongside MCC categories) ─
45
+ export function getCardPolicySuggestedAllowMerchants(state) {
46
+ return state.suggestedAllowMerchants;
47
+ }
48
+ export function getCardPolicySuggestedBlockMerchants(state) {
49
+ return state.suggestedBlockMerchants;
50
+ }
51
+ export function getCardPolicySuggestedAllowCategories(state) {
52
+ return state.suggestedAllowCategories;
53
+ }
54
+ export function getCardPolicySuggestedBlockCategories(state) {
55
+ return state.suggestedBlockCategories;
56
+ }
57
+ // ── Vendor search dropdown ───────────────────────────────────────────
58
+ export function getCardPolicyVendorSearchOptions(state) {
59
+ return state.vendorSearch.vendors;
60
+ }
61
+ export function getCardPolicyVendorSearchFetchState(state) {
62
+ return state.vendorSearch.fetchState;
63
+ }
64
+ export function getCardPolicyVendorSearchString(state) {
65
+ return state.vendorSearch.searchString;
66
+ }
@@ -0,0 +1,9 @@
1
+ import { stringToUnion } from '../../commonStateTypes/stringToUnion';
2
+ export const ALL_CARD_POLICY_TEMPLATE_MODES = ['strict', 'flexible'];
3
+ export const toCardPolicyTemplateMode = (v) => stringToUnion(v, ALL_CARD_POLICY_TEMPLATE_MODES);
4
+ export const ALL_CARD_POLICY_TEMPLATE_STATUSES = [
5
+ 'active',
6
+ 'inactive',
7
+ 'archived',
8
+ ];
9
+ export const toCardPolicyTemplateStatus = (v) => stringToUnion(v, ALL_CARD_POLICY_TEMPLATE_STATUSES);
@@ -0,0 +1,50 @@
1
+ import { of } from 'rxjs';
2
+ import { catchError, filter, mergeMap } from 'rxjs/operators';
3
+ import { createZeniAPIStatus, isSuccessStatus } from '../../responsePayload';
4
+ import { extractPolicyDocument, updatePolicyDocumentExtractionFailure, updatePolicyDocumentExtractionSuccess, } from './cardPolicyReducer';
5
+ /**
6
+ * Build a multipart `FormData` body for `POST /cards/1.0/policy-documents/extract`.
7
+ *
8
+ * Uses the same `attachment_file_<i>` / `attachment_json_<i>` field-naming
9
+ * convention as the charge-card receipt upload (`uploadReceipts.ts` in
10
+ * `web-components`) so backends that already support that wire shape
11
+ * accept this endpoint without bespoke parsing.
12
+ */
13
+ const buildPolicyDocumentFormData = (files) => {
14
+ const data = new FormData();
15
+ files.forEach((file, index) => {
16
+ data.append(`attachment_file_${index}`, file);
17
+ const attachmentJson = JSON.stringify({
18
+ file_name: file.name,
19
+ content_type: file.type,
20
+ size: file.size,
21
+ });
22
+ data.append(`attachment_json_${index}`, attachmentJson);
23
+ });
24
+ return data;
25
+ };
26
+ /**
27
+ * Listens for `extractPolicyDocument`, POSTs the file(s) as multipart
28
+ * form-data to the cards micro-service's policy-document extract endpoint,
29
+ * and dispatches success / failure into the entity slice.
30
+ *
31
+ * Consumed by:
32
+ * - Manual `CardPolicyCreatePage` (read result via
33
+ * `getExtractedCardPolicyRules` then pre-fill the RHF form via
34
+ * `applyExtractedPolicyRulesToFormValues` from `@zeniai/web-components`).
35
+ * - (Future) AI CFO upload surface — same action + selector contract.
36
+ */
37
+ export const extractPolicyDocumentEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(extractPolicyDocument.match), mergeMap((action) => {
38
+ const formData = buildPolicyDocumentFormData(action.payload.files);
39
+ return zeniAPI
40
+ .postFormData(`${zeniAPI.apiEndPoints.cardMicroServiceBaseUrl}/1.0/policy-documents/extract`, formData)
41
+ .pipe(mergeMap((response) => {
42
+ if (isSuccessStatus(response) &&
43
+ response.data != null &&
44
+ response.data.extracted_rules != null) {
45
+ return of(updatePolicyDocumentExtractionSuccess(response.data.extracted_rules));
46
+ }
47
+ return of(updatePolicyDocumentExtractionFailure(response.status));
48
+ }), catchError((error) => of(updatePolicyDocumentExtractionFailure(createZeniAPIStatus('Unexpected Error', 'Extract card-policy document REST API call errored out' +
49
+ JSON.stringify(error))))));
50
+ }));
@@ -0,0 +1,43 @@
1
+ import { of } from 'rxjs';
2
+ import { catchError, filter, switchMap } from 'rxjs/operators';
3
+ import { createZeniAPIStatus, isSuccessStatus } from '../../responsePayload';
4
+ import { fetchCardPolicyVendorOptions, updateCardPolicyVendorOptions, updateCardPolicyVendorOptionsFailure, } from './cardPolicyReducer';
5
+ const buildQuery = (searchString) => ({
6
+ view: 'vendor_list',
7
+ page_token: null,
8
+ sort_by: 'year_to_date_spend',
9
+ sort_order: 'desc',
10
+ search_text: searchString,
11
+ vendor_name: searchString,
12
+ });
13
+ /**
14
+ * Card-policy vendor search.
15
+ *
16
+ * Listens for `fetchCardPolicyVendorOptions` and hits the same
17
+ * `/accounting/1.0/vendors` endpoint the Vendors-tab uses, but writes
18
+ * results onto the dedicated `cardPolicyState.vendorSearch` slice — no
19
+ * fan-out to the global `vendor` / `account` / `class` / `vendorList`
20
+ * entities. That isolation is important: the user may be configuring a
21
+ * card policy in parallel with browsing the Vendors tab, and we don't
22
+ * want either flow to clobber the other's list state.
23
+ *
24
+ * Uses `switchMap` so the latest typed search wins — earlier in-flight
25
+ * requests are abandoned, which is the right semantics for a
26
+ * search-as-you-type UX.
27
+ *
28
+ * Consumers:
29
+ * - Manual `CardPolicyCreate / EditScreen`
30
+ * - AI CFO `CardPolicyInteractiveForm` (vendor step)
31
+ */
32
+ export const fetchCardPolicyVendorOptionsEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchCardPolicyVendorOptions.match), switchMap((action) => {
33
+ const query = buildQuery(action.payload.searchString);
34
+ const apiUrl = `${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/vendors` +
35
+ `?query=${encodeURIComponent(JSON.stringify(query))}`;
36
+ return zeniAPI.getJSON(apiUrl).pipe(switchMap((response) => {
37
+ if (isSuccessStatus(response) && response.data != null) {
38
+ return of(updateCardPolicyVendorOptions(response.data));
39
+ }
40
+ return of(updateCardPolicyVendorOptionsFailure(response.status));
41
+ }), catchError((error) => of(updateCardPolicyVendorOptionsFailure(createZeniAPIStatus('Unexpected Error', 'Fetch card policy vendor options REST API call errored out' +
42
+ JSON.stringify(error))))));
43
+ }));
@@ -97,6 +97,9 @@ import { clearInternationalWire } from '../../view/spendManagement/billPay/inter
97
97
  import { clearInternationalVerificationView } from '../../view/spendManagement/billPay/internationalWireVerification/internationalWireVerificationReducer';
98
98
  import { clearPreviousBills } from '../../view/spendManagement/billPay/previousBills/previousBillsReducer';
99
99
  import { clearWiseRedirectStatus } from '../../view/spendManagement/billPay/wiseRedirectView/wiseRedirectReducer';
100
+ import { clearCardPolicyDetail } from '../../view/spendManagement/chargeCards/cardPolicy/cardPolicyDetail/cardPolicyDetailReducer';
101
+ import { clearCardPolicyList } from '../../view/spendManagement/chargeCards/cardPolicy/cardPolicyList/cardPolicyListReducer';
102
+ import { clearCreateCardPolicy } from '../../view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer';
100
103
  import { clearCardUserOnboarding } from '../../view/spendManagement/chargeCards/cardUserOnboarding/cardUserOnboardingReducer';
101
104
  import { clearCashbackDetail } from '../../view/spendManagement/chargeCards/cashbackDetail/cashbackDetailReducer';
102
105
  import { clearChargeCardConfig } from '../../view/spendManagement/chargeCards/chargeCardConfig/chargeCardConfigReducer';
@@ -167,6 +170,7 @@ import { clearAllBillTransactions } from '../billPay/billTransaction/billTransac
167
170
  import { clearContacts } from '../billPay/contact/contactReducer';
168
171
  import { clearAllRecurringBills } from '../billPay/recurringBills/recurringBillsReducer';
169
172
  import { clearAllCardPayments } from '../cardPayment/cardPaymentReducer';
173
+ import { clearCardPolicy } from '../cardPolicy/cardPolicyReducer';
170
174
  import { clearAllChargeCards } from '../chargeCard/chargeCardReducer';
171
175
  import { clearAllChargeCardRepayments } from '../chargeCardRepayment/chargeCardRepaymentReducer';
172
176
  import { clearAllChargeCardTransactions } from '../chargeCardTransaction/chargeCardTransactionReducer';
@@ -243,6 +247,8 @@ export const clearAllEpic = (actions$) => actions$.pipe(filter(clearAll.match),
243
247
  clearBillPaySetupView(),
244
248
  clearCardBalance(),
245
249
  clearCardPaymentView(),
250
+ clearCardPolicyDetail(),
251
+ clearCardPolicyList(),
246
252
  clearCardUserOnboarding(),
247
253
  clearCashbackDetail(),
248
254
  clearCashBalance(),
@@ -262,6 +268,7 @@ export const clearAllEpic = (actions$) => actions$.pipe(filter(clearAll.match),
262
268
  clearCompanyMonthEndReportView(),
263
269
  clearCompanyTaskManagerView(),
264
270
  clearCompanyView(),
271
+ clearCreateCardPolicy(),
265
272
  clearAllCreditAgent(),
266
273
  clearCreditAgentView(),
267
274
  clearDashboard(),
@@ -422,6 +429,7 @@ export const clearAllEpic = (actions$) => actions$.pipe(filter(clearAll.match),
422
429
  clearAllVendorExpenseTrends(),
423
430
  clearAllVendors(),
424
431
  clearCannedResponses(),
432
+ clearCardPolicy(),
425
433
  clearClassList(),
426
434
  clearContacts(),
427
435
  clearCountryList(),