@zeniai/client-epic-state 5.0.83-betaRR5 → 5.0.83

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 (97) hide show
  1. package/lib/entity/aiCfo/aiCfoPayload.d.ts +4 -1
  2. package/lib/entity/aiCfo/aiCfoReducer.js +17 -4
  3. package/lib/entity/aiCfo/aiCfoState.d.ts +19 -0
  4. package/lib/entity/aiCfo/aiCfoState.js +8 -1
  5. package/lib/entity/cardPolicy/cardPolicyReducer.js +99 -9
  6. package/lib/entity/cardPolicy/cardPolicySelector.js +4 -0
  7. package/lib/entity/cardPolicy/policyRecommendationFromUploadEpic.d.ts +2 -3
  8. package/lib/entity/cardPolicy/policyRecommendationFromUploadEpic.js +10 -4
  9. package/lib/epic.d.ts +12 -15
  10. package/lib/epic.js +12 -15
  11. package/lib/esm/entity/aiCfo/aiCfoReducer.js +18 -5
  12. package/lib/esm/entity/aiCfo/aiCfoState.js +7 -0
  13. package/lib/esm/entity/cardPolicy/cardPolicyReducer.js +99 -9
  14. package/lib/esm/entity/cardPolicy/cardPolicySelector.js +4 -0
  15. package/lib/esm/entity/cardPolicy/policyRecommendationFromUploadEpic.js +11 -5
  16. package/lib/esm/epic.js +12 -15
  17. package/lib/esm/index.js +6 -21
  18. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.js +19 -138
  19. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.js +0 -18
  20. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardReducer.js +5 -38
  21. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardSelector.js +0 -23
  22. package/lib/index.d.ts +7 -28
  23. package/lib/index.js +14 -68
  24. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.d.ts +1 -40
  25. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.js +20 -139
  26. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.d.ts +0 -15
  27. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.js +0 -20
  28. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyState.d.ts +0 -28
  29. package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardReducer.d.ts +1 -7
  30. package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardReducer.js +6 -39
  31. package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardSelector.d.ts +0 -15
  32. package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardSelector.js +1 -26
  33. package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardState.d.ts +0 -16
  34. package/package.json +1 -1
  35. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyAiCardPolicyFormDraftUpdate.js +0 -328
  36. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToAiCardPolicyDraft.js +0 -120
  37. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToDraftEpic.js +0 -60
  38. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToManualCardPolicyDraft.js +0 -118
  39. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyUploadPlanToAiCardPolicyDraft.js +0 -33
  40. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildAiCardPolicyFormDraftSeed.js +0 -385
  41. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildManualCardPolicyFormDraftSeed.js +0 -34
  42. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyChipIds.js +0 -33
  43. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyFormDraftTypes.js +0 -17
  44. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyRequestParts.js +0 -144
  45. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/deriveAiPolicyReviewRowsFromInputs.js +0 -148
  46. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/seedAiCardPolicyFormDraftEpic.js +0 -112
  47. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toBulkCardPolicyTemplateRequestsFromDraft.js +0 -101
  48. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toCardPolicyTemplateRequestFromDraft.js +0 -71
  49. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toManualCardPolicyTemplateRequestFromDraft.js +0 -34
  50. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/aiCardCreationFormDraftTypes.js +0 -1
  51. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/applyAiCardCreationFormDraftUpdate.js +0 -42
  52. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/buildAiCardCreationFormDraftSeed.js +0 -69
  53. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/deriveAiCardRowsFromTeams.js +0 -45
  54. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/seedAiCardCreationFormDraftEpic.js +0 -65
  55. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/toIssueChargeCardLocalDataFromDraft.js +0 -64
  56. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyAiCardPolicyFormDraftUpdate.d.ts +0 -38
  57. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyAiCardPolicyFormDraftUpdate.js +0 -333
  58. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToAiCardPolicyDraft.d.ts +0 -25
  59. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToAiCardPolicyDraft.js +0 -124
  60. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToDraftEpic.d.ts +0 -28
  61. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToDraftEpic.js +0 -64
  62. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToManualCardPolicyDraft.d.ts +0 -17
  63. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToManualCardPolicyDraft.js +0 -122
  64. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyUploadPlanToAiCardPolicyDraft.d.ts +0 -23
  65. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyUploadPlanToAiCardPolicyDraft.js +0 -37
  66. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildAiCardPolicyFormDraftSeed.d.ts +0 -38
  67. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildAiCardPolicyFormDraftSeed.js +0 -391
  68. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildManualCardPolicyFormDraftSeed.d.ts +0 -11
  69. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildManualCardPolicyFormDraftSeed.js +0 -38
  70. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyChipIds.d.ts +0 -24
  71. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyChipIds.js +0 -40
  72. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyFormDraftTypes.d.ts +0 -253
  73. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyFormDraftTypes.js +0 -21
  74. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyRequestParts.d.ts +0 -66
  75. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyRequestParts.js +0 -151
  76. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/deriveAiPolicyReviewRowsFromInputs.d.ts +0 -30
  77. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/deriveAiPolicyReviewRowsFromInputs.js +0 -152
  78. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/seedAiCardPolicyFormDraftEpic.d.ts +0 -26
  79. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/seedAiCardPolicyFormDraftEpic.js +0 -116
  80. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toBulkCardPolicyTemplateRequestsFromDraft.d.ts +0 -35
  81. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toBulkCardPolicyTemplateRequestsFromDraft.js +0 -105
  82. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toCardPolicyTemplateRequestFromDraft.d.ts +0 -23
  83. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toCardPolicyTemplateRequestFromDraft.js +0 -75
  84. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toManualCardPolicyTemplateRequestFromDraft.d.ts +0 -15
  85. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toManualCardPolicyTemplateRequestFromDraft.js +0 -38
  86. package/lib/view/spendManagement/chargeCards/issueChargeCard/aiCardCreationFormDraftTypes.d.ts +0 -76
  87. package/lib/view/spendManagement/chargeCards/issueChargeCard/aiCardCreationFormDraftTypes.js +0 -2
  88. package/lib/view/spendManagement/chargeCards/issueChargeCard/applyAiCardCreationFormDraftUpdate.d.ts +0 -7
  89. package/lib/view/spendManagement/chargeCards/issueChargeCard/applyAiCardCreationFormDraftUpdate.js +0 -46
  90. package/lib/view/spendManagement/chargeCards/issueChargeCard/buildAiCardCreationFormDraftSeed.d.ts +0 -28
  91. package/lib/view/spendManagement/chargeCards/issueChargeCard/buildAiCardCreationFormDraftSeed.js +0 -73
  92. package/lib/view/spendManagement/chargeCards/issueChargeCard/deriveAiCardRowsFromTeams.d.ts +0 -23
  93. package/lib/view/spendManagement/chargeCards/issueChargeCard/deriveAiCardRowsFromTeams.js +0 -49
  94. package/lib/view/spendManagement/chargeCards/issueChargeCard/seedAiCardCreationFormDraftEpic.d.ts +0 -11
  95. package/lib/view/spendManagement/chargeCards/issueChargeCard/seedAiCardCreationFormDraftEpic.js +0 -69
  96. package/lib/view/spendManagement/chargeCards/issueChargeCard/toIssueChargeCardLocalDataFromDraft.d.ts +0 -11
  97. package/lib/view/spendManagement/chargeCards/issueChargeCard/toIssueChargeCardLocalDataFromDraft.js +0 -68
@@ -1,328 +0,0 @@
1
- import { deriveAiPolicyReviewRowsFromInputs } from './deriveAiPolicyReviewRowsFromInputs';
2
- /**
3
- * Single source of truth for "what the next AI card-policy form draft
4
- * should look like after a `watchHandler` flush".
5
- *
6
- * Branches on `next.policyKind`:
7
- *
8
- * - `auto`: pass-through. Chip groups on `policiesReview` are the
9
- * canonical source of truth; the source slices stay empty.
10
- *
11
- * - `guided`: mirrors review-step chip edits (diffed against
12
- * `prev.policiesReview`) back into the source slices
13
- * (`vendorPolicy` / `categoryPolicy` / `cardSelections` / `limits`),
14
- * then re-derives `policiesReview` from the bridged source slices
15
- * via `deriveAiPolicyReviewRowsFromInputs` so the review always
16
- * reflects the freshest live state. Replaces the
17
- * `useReviewChipEditBridge` hook that previously lived in
18
- * `CardPolicyInteractiveForm`.
19
- *
20
- * - `upload`: passes through the user's chip edits on
21
- * `policiesReview` (the wire reads from those chip groups). The
22
- * "Applied to Cards" chip group is patched in-place from
23
- * `cardSelections` (the user's Step 2 picks).
24
- */
25
- export const applyAiCardPolicyFormDraftUpdate = ({ next, prev, context, }) => {
26
- if (next.policyKind === 'auto') {
27
- return next;
28
- }
29
- if (next.policyKind === 'upload') {
30
- return applyUploadUpdate({ next, prev });
31
- }
32
- return applyGuidedUpdate({ next, prev, context });
33
- };
34
- const applyGuidedUpdate = ({ next, prev, context, }) => {
35
- const bridged = bridgeGuidedPoliciesReviewToSources({ next, prev });
36
- if (context == null) {
37
- return bridged;
38
- }
39
- return {
40
- ...bridged,
41
- policiesReview: deriveAiPolicyReviewRowsFromInputs({
42
- vendorPolicy: bridged.vendorPolicy,
43
- categoryPolicy: bridged.categoryPolicy,
44
- cardSelections: bridged.cardSelections,
45
- limits: bridged.limits,
46
- suggestedPoliciesSeed: context.suggestedPoliciesSeed,
47
- prevPoliciesReview: bridged.policiesReview,
48
- }),
49
- };
50
- };
51
- const APPLIED_TO_CARDS_GROUP_ID = 'applied-to-cards';
52
- const LIMITS_GROUP_ID = 'limits';
53
- const applyUploadUpdate = ({ next, prev, }) => {
54
- // Mirror review-side avatar / limits edits into source slices so the
55
- // bulk-submit builder (which reads `cardSelections` for upload's
56
- // applyToCards) sees the user's latest intent.
57
- const bridged = bridgeUploadPoliciesReviewToSources({ next, prev });
58
- if (!haveCardSelectionsChanged(prev?.cardSelections, bridged.cardSelections)) {
59
- return bridged;
60
- }
61
- return {
62
- ...bridged,
63
- policiesReview: patchUploadReviewWithAppliedCards(bridged.policiesReview, bridged.cardSelections),
64
- };
65
- };
66
- const haveCardSelectionsChanged = (prevSelections, nextSelections) => {
67
- const prevCheckedIds = (prevSelections ?? [])
68
- .filter((row) => row.isChecked)
69
- .map((row) => row.id);
70
- const nextCheckedIds = nextSelections
71
- .filter((row) => row.isChecked)
72
- .map((row) => row.id);
73
- if (prevCheckedIds.length !== nextCheckedIds.length) {
74
- return true;
75
- }
76
- return prevCheckedIds.some((id, idx) => id !== nextCheckedIds[idx]);
77
- };
78
- const buildAppliedOwnerSummaries = (cardSelections) => {
79
- const summaries = new Map();
80
- for (const card of cardSelections) {
81
- if (!card.isChecked) {
82
- continue;
83
- }
84
- const existing = summaries.get(card.owner.userId);
85
- if (existing != null) {
86
- existing.cardIds.push(card.id);
87
- continue;
88
- }
89
- summaries.set(card.owner.userId, {
90
- userId: card.owner.userId,
91
- displayName: card.owner.displayName,
92
- cardIds: [card.id],
93
- });
94
- }
95
- return Array.from(summaries.values());
96
- };
97
- /**
98
- * Replace (or insert) the "Applied to Cards" chip group on every
99
- * upload review row with avatar chips derived from the current
100
- * `cardSelections`. The new group is inserted before the "Limits"
101
- * group so the visual order stays Vendors → Categories → Cards →
102
- * Limits.
103
- */
104
- export const patchUploadReviewWithAppliedCards = (reviewRows, cardSelections) => {
105
- const owners = buildAppliedOwnerSummaries(cardSelections);
106
- const cardChips = owners.map((owner) => ({
107
- id: owner.userId,
108
- label: owner.displayName,
109
- chipKind: 'avatar',
110
- cardIds: owner.cardIds,
111
- isSelected: true,
112
- }));
113
- const cardsGroup = cardChips.length > 0
114
- ? {
115
- id: APPLIED_TO_CARDS_GROUP_ID,
116
- label: 'Applied to Cards',
117
- chips: cardChips,
118
- }
119
- : null;
120
- return reviewRows.map((row) => {
121
- const withoutCards = row.chipGroups.filter((group) => group.id !== APPLIED_TO_CARDS_GROUP_ID);
122
- if (cardsGroup == null) {
123
- return { ...row, chipGroups: withoutCards };
124
- }
125
- const limitsIdx = withoutCards.findIndex((group) => group.id === LIMITS_GROUP_ID);
126
- if (limitsIdx === -1) {
127
- return { ...row, chipGroups: [...withoutCards, cardsGroup] };
128
- }
129
- const before = withoutCards.slice(0, limitsIdx);
130
- const after = withoutCards.slice(limitsIdx);
131
- return { ...row, chipGroups: [...before, cardsGroup, ...after] };
132
- });
133
- };
134
- /**
135
- * Mirror diffs in `next.policiesReview` chips back into the source
136
- * slices for guided mode. Detects per-chip `isSelected` and
137
- * `optionalCode` changes vs `prev` and writes them to:
138
- * - `limits` for `kind: "limits"` group chips,
139
- * - `cardSelections` for avatar chips, and
140
- * - `vendorPolicy` / `categoryPolicy` for positive/negative chips.
141
- */
142
- const bridgeGuidedPoliciesReviewToSources = ({ next, prev, }) => {
143
- if (prev == null || prev.policiesReview.length === 0) {
144
- return next;
145
- }
146
- const reviewDiffs = collectReviewChipDiffs(prev.policiesReview, next.policiesReview);
147
- if (reviewDiffs.length === 0) {
148
- return next;
149
- }
150
- let limits = next.limits;
151
- let cardSelections = next.cardSelections;
152
- let vendorPolicy = next.vendorPolicy;
153
- let categoryPolicy = next.categoryPolicy;
154
- for (const diff of reviewDiffs) {
155
- if (diff.groupKind === 'limits' || diff.chip.id.startsWith('limit-')) {
156
- limits = applyLimitChipDiffToLimits(limits, diff);
157
- continue;
158
- }
159
- if (diff.chip.chipKind === 'avatar') {
160
- cardSelections = applyAvatarChipDiffToCardSelections(cardSelections, diff);
161
- continue;
162
- }
163
- if (diff.changedFields.has('isSelected')) {
164
- const writtenToVendor = tryWriteAllowBlock(vendorPolicy, diff);
165
- if (writtenToVendor != null) {
166
- vendorPolicy = writtenToVendor;
167
- continue;
168
- }
169
- const writtenToCategory = tryWriteAllowBlock(categoryPolicy, diff);
170
- if (writtenToCategory != null) {
171
- categoryPolicy = writtenToCategory;
172
- }
173
- }
174
- }
175
- return {
176
- ...next,
177
- limits,
178
- cardSelections,
179
- vendorPolicy,
180
- categoryPolicy,
181
- };
182
- };
183
- /**
184
- * Upload-mode bridge — narrower than guided. Vendor / category chips in
185
- * the review are the wire's source of truth (no source slice to write
186
- * back to); avatar chips mirror into `cardSelections` (the wire's
187
- * `applyToCards` projection for upload reads from there); limit chips
188
- * are read from the review chip group directly by the wire, so no
189
- * mirror is needed.
190
- */
191
- const bridgeUploadPoliciesReviewToSources = ({ next, prev, }) => {
192
- if (prev == null || prev.policiesReview.length === 0) {
193
- return next;
194
- }
195
- const reviewDiffs = collectReviewChipDiffs(prev.policiesReview, next.policiesReview);
196
- if (reviewDiffs.length === 0) {
197
- return next;
198
- }
199
- let cardSelections = next.cardSelections;
200
- for (const diff of reviewDiffs) {
201
- if (diff.chip.chipKind === 'avatar') {
202
- cardSelections = applyAvatarChipDiffToCardSelections(cardSelections, diff);
203
- }
204
- }
205
- if (cardSelections === next.cardSelections) {
206
- return next;
207
- }
208
- return { ...next, cardSelections };
209
- };
210
- const collectReviewChipDiffs = (prevRows, nextRows) => {
211
- const prevById = new Map(prevRows.map((row) => [row.id, row]));
212
- const diffs = [];
213
- for (const row of nextRows) {
214
- const prevRow = prevById.get(row.id);
215
- if (prevRow == null) {
216
- continue;
217
- }
218
- const prevGroupsById = new Map(prevRow.chipGroups.map((group) => [group.id, group]));
219
- for (const group of row.chipGroups) {
220
- const prevGroup = prevGroupsById.get(group.id);
221
- if (prevGroup == null) {
222
- continue;
223
- }
224
- const prevChipsById = new Map(prevGroup.chips.map((chip) => [chip.id, chip]));
225
- for (const chip of group.chips) {
226
- const prevChip = prevChipsById.get(chip.id);
227
- if (prevChip == null) {
228
- continue;
229
- }
230
- const changed = new Set();
231
- if (chip.isSelected !== prevChip.isSelected) {
232
- changed.add('isSelected');
233
- }
234
- if (chip.optionalCode !== prevChip.optionalCode) {
235
- changed.add('optionalCode');
236
- }
237
- if (changed.size > 0) {
238
- diffs.push({ chip, changedFields: changed, groupKind: group.kind });
239
- }
240
- }
241
- }
242
- }
243
- return diffs;
244
- };
245
- const REVIEW_LIMIT_CHIP_ID_TO_LIMIT_ROW_ID = {
246
- 'limit-transaction': 'transactionLimit',
247
- 'limit-receipt': 'requireReceipt',
248
- };
249
- const applyLimitChipDiffToLimits = (limits, diff) => {
250
- const mapped = REVIEW_LIMIT_CHIP_ID_TO_LIMIT_ROW_ID[diff.chip.id];
251
- const directMatch = limits.find((row) => row.id === diff.chip.id) != null;
252
- const rowId = mapped ?? (directMatch ? diff.chip.id : undefined);
253
- if (rowId == null) {
254
- return limits;
255
- }
256
- const idx = limits.findIndex((row) => row.id === rowId);
257
- if (idx === -1) {
258
- return limits;
259
- }
260
- let nextRow = limits[idx];
261
- if (diff.changedFields.has('isSelected')) {
262
- nextRow = { ...nextRow, isEnabled: diff.chip.isSelected === true };
263
- }
264
- if (diff.changedFields.has('optionalCode')) {
265
- const raw = diff.chip.optionalCode;
266
- const numericNext = raw != null && raw !== '' ? Number(raw) : undefined;
267
- nextRow = { ...nextRow, amount: numericNext };
268
- }
269
- if (nextRow === limits[idx]) {
270
- return limits;
271
- }
272
- const copy = limits.slice();
273
- copy[idx] = nextRow;
274
- return copy;
275
- };
276
- const applyAvatarChipDiffToCardSelections = (cardSelections, diff) => {
277
- if (!diff.changedFields.has('isSelected')) {
278
- return cardSelections;
279
- }
280
- const targetIds = diff.chip.cardIds != null && diff.chip.cardIds.length > 0
281
- ? diff.chip.cardIds
282
- : [diff.chip.id];
283
- const targetSet = new Set(targetIds);
284
- let mutated = false;
285
- const next = cardSelections.map((row) => {
286
- if (!targetSet.has(row.id)) {
287
- return row;
288
- }
289
- const desired = diff.chip.isSelected === true;
290
- if (row.isChecked === desired) {
291
- return row;
292
- }
293
- mutated = true;
294
- return { ...row, isChecked: desired };
295
- });
296
- return mutated ? next : cardSelections;
297
- };
298
- const tryWriteAllowBlock = (field, diff) => {
299
- const desired = diff.chip.isSelected === true;
300
- const written = tryFlipItemInSide(field.allowItems, diff, desired);
301
- if (written != null) {
302
- return { ...field, allowItems: written };
303
- }
304
- const writtenBlock = tryFlipItemInSide(field.blockItems, diff, desired);
305
- if (writtenBlock != null) {
306
- return { ...field, blockItems: writtenBlock };
307
- }
308
- return null;
309
- };
310
- const tryFlipItemInSide = (items, diff, desired) => {
311
- let idx = items.findIndex((item) => item.id === diff.chip.id);
312
- if (idx === -1 && diff.chip.optionalCode != null) {
313
- // Auto / upload mode category chips use the raw MCC code as
314
- // `chip.id` while the source items use `mcc-{name}`; fall back to
315
- // matching by code so the bridge round-trips both shapes.
316
- idx = items.findIndex((item) => item.optionalCode === diff.chip.optionalCode ||
317
- (item.optionalCodes ?? []).includes(diff.chip.optionalCode ?? ''));
318
- }
319
- if (idx === -1) {
320
- return null;
321
- }
322
- if (items[idx].isSelected === desired) {
323
- return items;
324
- }
325
- const copy = items.slice();
326
- copy[idx] = { ...items[idx], isSelected: desired };
327
- return copy;
328
- };
@@ -1,120 +0,0 @@
1
- import { buildMccCategoryChipId, buildVendorChipId } from './cardPolicyChipIds';
2
- import { CARD_POLICY_LIMIT_ROW_ID_REQUIRE_RECEIPT, CARD_POLICY_LIMIT_ROW_ID_TRANSACTION, } from './cardPolicyFormDraftTypes';
3
- /**
4
- * Apply an extracted policy document response to the AI CFO card-policy
5
- * draft. Touches only the slices the extract API populates:
6
- * - `vendorPolicy` from allow/block `merchantNames` (with allow-wins
7
- * flip).
8
- * - `categoryPolicy` from allow/block `categoryCodes` (matched on
9
- * `optionalCode`).
10
- * - `limits` rows from `transactionLimit` / `requiredReceiptThreshold`.
11
- *
12
- * `cardSelections` and `policiesReview` are preserved verbatim — the
13
- * user still picks cards on Step 2 and the review step is seeded
14
- * separately.
15
- */
16
- export const applyExtractedPolicyToAiCardPolicyDraft = ({ extracted, current, vendorSearchOptions, categorySearchOptions, }) => ({
17
- ...current,
18
- vendorPolicy: buildAllowBlockField({
19
- allowList: extracted.allowedEntity.merchantNames,
20
- blockList: extracted.blockedEntity.merchantNames,
21
- currentAllowItems: current.vendorPolicy.allowItems,
22
- currentBlockItems: current.vendorPolicy.blockItems,
23
- universe: vendorSearchOptions,
24
- matchOnCode: false,
25
- }),
26
- categoryPolicy: buildAllowBlockField({
27
- allowList: extracted.allowedEntity.categoryCodes,
28
- blockList: extracted.blockedEntity.categoryCodes,
29
- currentAllowItems: current.categoryPolicy.allowItems,
30
- currentBlockItems: current.categoryPolicy.blockItems,
31
- universe: categorySearchOptions,
32
- matchOnCode: true,
33
- }),
34
- limits: buildLimitRows({
35
- current: current.limits,
36
- transactionLimit: extracted.transactionLimit,
37
- requiredReceiptThreshold: extracted.requiredReceiptThreshold,
38
- }),
39
- });
40
- const buildAllowBlockField = ({ allowList, blockList, currentAllowItems, currentBlockItems, universe, matchOnCode, }) => {
41
- const allowItems = buildSideItems({
42
- selectedValues: allowList,
43
- currentItems: currentAllowItems,
44
- universe,
45
- matchOnCode,
46
- });
47
- const blockItems = buildSideItems({
48
- selectedValues: blockList,
49
- currentItems: currentBlockItems,
50
- universe,
51
- matchOnCode,
52
- });
53
- const mode = allowList.length === 0 && blockList.length > 0 ? 'block' : 'allow';
54
- return { mode, allowItems, blockItems };
55
- };
56
- const buildSideItems = ({ selectedValues, currentItems, universe, matchOnCode, }) => {
57
- const selectedKeys = new Set(selectedValues
58
- .map((value) => normalizeKey(value))
59
- .filter((key) => key != null));
60
- const items = [];
61
- const seenIds = new Set();
62
- for (const item of currentItems) {
63
- const key = normalizeKey(matchOnCode ? item.optionalCode : item.label);
64
- const isSelected = key != null && selectedKeys.has(key);
65
- items.push({ ...item, isSelected });
66
- seenIds.add(item.id);
67
- if (isSelected && key != null) {
68
- selectedKeys.delete(key);
69
- }
70
- }
71
- for (const option of universe) {
72
- if (selectedKeys.size === 0) {
73
- break;
74
- }
75
- const key = normalizeKey(matchOnCode ? option.optionalCode : option.label);
76
- if (key == null || !selectedKeys.has(key) || seenIds.has(option.id)) {
77
- continue;
78
- }
79
- items.push({ ...option, isSelected: true });
80
- seenIds.add(option.id);
81
- selectedKeys.delete(key);
82
- }
83
- for (const value of selectedValues) {
84
- const key = normalizeKey(value);
85
- if (key == null || !selectedKeys.has(key)) {
86
- continue;
87
- }
88
- const id = matchOnCode
89
- ? buildMccCategoryChipId(value)
90
- : buildVendorChipId(value);
91
- if (seenIds.has(id)) {
92
- continue;
93
- }
94
- items.push({
95
- id,
96
- label: value,
97
- isSelected: true,
98
- ...(matchOnCode ? { optionalCode: value } : {}),
99
- });
100
- seenIds.add(id);
101
- selectedKeys.delete(key);
102
- }
103
- return items;
104
- };
105
- const buildLimitRows = ({ current, transactionLimit, requiredReceiptThreshold, }) => current.map((row) => {
106
- if (row.id === CARD_POLICY_LIMIT_ROW_ID_TRANSACTION) {
107
- return amountToLimitRow(row, transactionLimit);
108
- }
109
- if (row.id === CARD_POLICY_LIMIT_ROW_ID_REQUIRE_RECEIPT) {
110
- return amountToLimitRow(row, requiredReceiptThreshold);
111
- }
112
- return row;
113
- });
114
- const amountToLimitRow = (row, amount) => {
115
- if (amount > 0) {
116
- return { ...row, isEnabled: true, amount };
117
- }
118
- return { ...row, isEnabled: false, amount: row.amount };
119
- };
120
- const normalizeKey = (value) => value == null || value.length === 0 ? undefined : value.toLowerCase().trim();
@@ -1,60 +0,0 @@
1
- import { EMPTY, of } from 'rxjs';
2
- import { mergeMap } from 'rxjs/operators';
3
- import { updatePolicyDocumentExtractionSuccess } from '../../../../../entity/cardPolicy/cardPolicyReducer';
4
- import { toMccCategoryChipFieldValue, toVendorChipFieldValue, } from './cardPolicyChipIds';
5
- import { toMccCategoryLike, } from './cardPolicyFormDraftTypes';
6
- import { applyExtractedPolicyToAiCardPolicyDraft, applyExtractedPolicyToManualCardPolicyDraft, } from './createCardPolicyReducer';
7
- const toExtractedView = (rules) => ({
8
- allowedEntity: {
9
- categoryCodes: rules.allowedEntity.categoryCodes ?? [],
10
- merchantNames: rules.allowedEntity.merchantNames ?? [],
11
- },
12
- blockedEntity: {
13
- categoryCodes: rules.blockedEntity.categoryCodes ?? [],
14
- merchantNames: rules.blockedEntity.merchantNames ?? [],
15
- },
16
- confidenceScore: rules.confidenceScore,
17
- policyName: rules.policyName,
18
- requiredReceiptThreshold: rules.requiredReceiptThreshold,
19
- transactionLimit: rules.transactionLimit,
20
- });
21
- const readVendorSearchOptions = (state) => state.cardPolicyState.vendorSearch.vendors.map((vendor) => toVendorChipFieldValue(vendor.name, false));
22
- const readCategorySearchOptions = (state) => state.cardPolicyState.mccCategories.map((category) => toMccCategoryChipFieldValue(toMccCategoryLike(category), false));
23
- /**
24
- * Bridge epic — fans `updatePolicyDocumentExtractionSuccess` out to the
25
- * right card-policy form draft based on which surface kicked the upload
26
- * off:
27
- *
28
- * - `aiCfoAnswerId != null` → the AI CFO upload-mode wizard owns it;
29
- * dispatch `applyExtractedPolicyToAiCardPolicyDraft` so the AI draft
30
- * picks up the extracted allow/block + limits. The wizard's `step_5`
31
- * review chips are separately patched by
32
- * `policyRecommendationFromUploadEpic` (the second hop in the chain).
33
- * - `aiCfoAnswerId == null` → the manual `CardPolicyCreatePage` owns
34
- * it; dispatch `applyExtractedPolicyToManualCardPolicyDraft` so the
35
- * manual draft picks up name + allow/block + limits.
36
- *
37
- * The bridge reads vendor + category universes off CES
38
- * (`cardPolicyState.vendorSearch.vendors`,
39
- * `cardPolicyState.mccCategories`) instead of taking them through the
40
- * payload, so any caller that drops the action wires up automatically.
41
- */
42
- export const applyExtractedPolicyToDraftEpic = (actions$, state$) => actions$.pipe(mergeMap((action) => {
43
- if (!updatePolicyDocumentExtractionSuccess.match(action)) {
44
- return EMPTY;
45
- }
46
- const state = state$.value;
47
- const extraction = state.cardPolicyState.policyDocumentExtraction;
48
- const rules = extraction.extractedRules;
49
- if (rules == null) {
50
- return EMPTY;
51
- }
52
- const extracted = toExtractedView(rules);
53
- const vendorSearchOptions = readVendorSearchOptions(state);
54
- const categorySearchOptions = readCategorySearchOptions(state);
55
- const payload = { extracted, vendorSearchOptions, categorySearchOptions };
56
- if (extraction.aiCfoAnswerId != null) {
57
- return of(applyExtractedPolicyToAiCardPolicyDraft(payload));
58
- }
59
- return of(applyExtractedPolicyToManualCardPolicyDraft(payload));
60
- }));
@@ -1,118 +0,0 @@
1
- import { buildMccCategoryChipId, buildVendorChipId } from './cardPolicyChipIds';
2
- import { CARD_POLICY_LIMIT_ROW_ID_REQUIRE_RECEIPT, CARD_POLICY_LIMIT_ROW_ID_TRANSACTION, } from './cardPolicyFormDraftTypes';
3
- /**
4
- * Apply an extracted policy document response to the manual
5
- * `CardPolicyCreatePage` draft. Mirrors the AI variant plus:
6
- * - `name` is overwritten with `extracted.policyName` when present.
7
- * - `description` is left as-is (the extract API doesn't return one).
8
- * - `mode` and `cardSelections` are preserved.
9
- *
10
- * Pure / referentially transparent.
11
- */
12
- export const applyExtractedPolicyToManualCardPolicyDraft = ({ extracted, current, vendorSearchOptions, categorySearchOptions, }) => ({
13
- ...current,
14
- name: extracted.policyName ?? current.name,
15
- description: current.description,
16
- vendorPolicy: buildAllowBlockField({
17
- allowList: extracted.allowedEntity.merchantNames,
18
- blockList: extracted.blockedEntity.merchantNames,
19
- currentAllowItems: current.vendorPolicy.allowItems,
20
- currentBlockItems: current.vendorPolicy.blockItems,
21
- universe: vendorSearchOptions,
22
- matchOnCode: false,
23
- }),
24
- categoryPolicy: buildAllowBlockField({
25
- allowList: extracted.allowedEntity.categoryCodes,
26
- blockList: extracted.blockedEntity.categoryCodes,
27
- currentAllowItems: current.categoryPolicy.allowItems,
28
- currentBlockItems: current.categoryPolicy.blockItems,
29
- universe: categorySearchOptions,
30
- matchOnCode: true,
31
- }),
32
- limits: buildLimitRows({
33
- current: current.limits,
34
- transactionLimit: extracted.transactionLimit,
35
- requiredReceiptThreshold: extracted.requiredReceiptThreshold,
36
- }),
37
- });
38
- const buildAllowBlockField = ({ allowList, blockList, currentAllowItems, currentBlockItems, universe, matchOnCode, }) => {
39
- const allowItems = buildSideItems({
40
- selectedValues: allowList,
41
- currentItems: currentAllowItems,
42
- universe,
43
- matchOnCode,
44
- });
45
- const blockItems = buildSideItems({
46
- selectedValues: blockList,
47
- currentItems: currentBlockItems,
48
- universe,
49
- matchOnCode,
50
- });
51
- const mode = allowList.length === 0 && blockList.length > 0 ? 'block' : 'allow';
52
- return { mode, allowItems, blockItems };
53
- };
54
- const buildSideItems = ({ selectedValues, currentItems, universe, matchOnCode, }) => {
55
- const selectedKeys = new Set(selectedValues
56
- .map((value) => normalizeKey(value))
57
- .filter((key) => key != null));
58
- const items = [];
59
- const seenIds = new Set();
60
- for (const item of currentItems) {
61
- const key = normalizeKey(matchOnCode ? item.optionalCode : item.label);
62
- const isSelected = key != null && selectedKeys.has(key);
63
- items.push({ ...item, isSelected });
64
- seenIds.add(item.id);
65
- if (isSelected && key != null) {
66
- selectedKeys.delete(key);
67
- }
68
- }
69
- for (const option of universe) {
70
- if (selectedKeys.size === 0) {
71
- break;
72
- }
73
- const key = normalizeKey(matchOnCode ? option.optionalCode : option.label);
74
- if (key == null || !selectedKeys.has(key) || seenIds.has(option.id)) {
75
- continue;
76
- }
77
- items.push({ ...option, isSelected: true });
78
- seenIds.add(option.id);
79
- selectedKeys.delete(key);
80
- }
81
- for (const value of selectedValues) {
82
- const key = normalizeKey(value);
83
- if (key == null || !selectedKeys.has(key)) {
84
- continue;
85
- }
86
- const id = matchOnCode
87
- ? buildMccCategoryChipId(value)
88
- : buildVendorChipId(value);
89
- if (seenIds.has(id)) {
90
- continue;
91
- }
92
- items.push({
93
- id,
94
- label: value,
95
- isSelected: true,
96
- ...(matchOnCode ? { optionalCode: value } : {}),
97
- });
98
- seenIds.add(id);
99
- selectedKeys.delete(key);
100
- }
101
- return items;
102
- };
103
- const buildLimitRows = ({ current, transactionLimit, requiredReceiptThreshold, }) => current.map((row) => {
104
- if (row.id === CARD_POLICY_LIMIT_ROW_ID_TRANSACTION) {
105
- return amountToLimitRow(row, transactionLimit);
106
- }
107
- if (row.id === CARD_POLICY_LIMIT_ROW_ID_REQUIRE_RECEIPT) {
108
- return amountToLimitRow(row, requiredReceiptThreshold);
109
- }
110
- return row;
111
- });
112
- const amountToLimitRow = (row, amount) => {
113
- if (amount > 0) {
114
- return { ...row, isEnabled: true, amount };
115
- }
116
- return { ...row, isEnabled: false, amount: row.amount };
117
- };
118
- const normalizeKey = (value) => value == null || value.length === 0 ? undefined : value.toLowerCase().trim();
@@ -1,33 +0,0 @@
1
- import { buildUploadReviewRows } from './buildAiCardPolicyFormDraftSeed';
2
- /**
3
- * Patch a fresh `wizardPlan` (returned by
4
- * `policy-recommendation-from-upload`) onto the upload-mode AI
5
- * card-policy draft + context. Re-derives the Step 5 review rows from
6
- * the new draft policies while preserving the user's Step 2
7
- * `cardSelections` and Step 1 `limits` toggles.
8
- *
9
- * No-op when the draft isn't in `upload` mode (the bridge epic should
10
- * still gate on `policyKind === "upload"` upstream).
11
- */
12
- export const applyUploadPlanToAiCardPolicyDraft = ({ current, context, wizardPlan, source, }) => {
13
- if (current.policyKind !== 'upload') {
14
- return { draft: current, context };
15
- }
16
- const policiesReview = buildUploadReviewRows(wizardPlan.step5Review.draftPolicies, context.cardsMasterList, context.categorySearchOptions);
17
- return {
18
- draft: {
19
- ...current,
20
- policiesReview,
21
- },
22
- context: {
23
- ...context,
24
- wizardPlan,
25
- source: source ?? context.source,
26
- suggestedPoliciesSeed: wizardPlan.step5Review.draftPolicies.map((draft) => ({
27
- id: draft.id,
28
- name: draft.name,
29
- isChecked: true,
30
- })),
31
- },
32
- };
33
- };