@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.
- package/lib/entity/aiCfo/aiCfoPayload.d.ts +4 -1
- package/lib/entity/aiCfo/aiCfoReducer.js +17 -4
- package/lib/entity/aiCfo/aiCfoState.d.ts +19 -0
- package/lib/entity/aiCfo/aiCfoState.js +8 -1
- package/lib/entity/cardPolicy/cardPolicyReducer.js +99 -9
- package/lib/entity/cardPolicy/cardPolicySelector.js +4 -0
- package/lib/entity/cardPolicy/policyRecommendationFromUploadEpic.d.ts +2 -3
- package/lib/entity/cardPolicy/policyRecommendationFromUploadEpic.js +10 -4
- package/lib/epic.d.ts +12 -15
- package/lib/epic.js +12 -15
- package/lib/esm/entity/aiCfo/aiCfoReducer.js +18 -5
- package/lib/esm/entity/aiCfo/aiCfoState.js +7 -0
- package/lib/esm/entity/cardPolicy/cardPolicyReducer.js +99 -9
- package/lib/esm/entity/cardPolicy/cardPolicySelector.js +4 -0
- package/lib/esm/entity/cardPolicy/policyRecommendationFromUploadEpic.js +11 -5
- package/lib/esm/epic.js +12 -15
- package/lib/esm/index.js +6 -21
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.js +19 -138
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.js +0 -18
- package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardReducer.js +5 -38
- package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardSelector.js +0 -23
- package/lib/index.d.ts +7 -28
- package/lib/index.js +14 -68
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.d.ts +1 -40
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.js +20 -139
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.d.ts +0 -15
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.js +0 -20
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyState.d.ts +0 -28
- package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardReducer.d.ts +1 -7
- package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardReducer.js +6 -39
- package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardSelector.d.ts +0 -15
- package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardSelector.js +1 -26
- package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardState.d.ts +0 -16
- package/package.json +1 -1
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyAiCardPolicyFormDraftUpdate.js +0 -328
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToAiCardPolicyDraft.js +0 -120
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToDraftEpic.js +0 -60
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToManualCardPolicyDraft.js +0 -118
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyUploadPlanToAiCardPolicyDraft.js +0 -33
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildAiCardPolicyFormDraftSeed.js +0 -385
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildManualCardPolicyFormDraftSeed.js +0 -34
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyChipIds.js +0 -33
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyFormDraftTypes.js +0 -17
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyRequestParts.js +0 -144
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/deriveAiPolicyReviewRowsFromInputs.js +0 -148
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/seedAiCardPolicyFormDraftEpic.js +0 -112
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toBulkCardPolicyTemplateRequestsFromDraft.js +0 -101
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toCardPolicyTemplateRequestFromDraft.js +0 -71
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toManualCardPolicyTemplateRequestFromDraft.js +0 -34
- package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/aiCardCreationFormDraftTypes.js +0 -1
- package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/applyAiCardCreationFormDraftUpdate.js +0 -42
- package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/buildAiCardCreationFormDraftSeed.js +0 -69
- package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/deriveAiCardRowsFromTeams.js +0 -45
- package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/seedAiCardCreationFormDraftEpic.js +0 -65
- package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/toIssueChargeCardLocalDataFromDraft.js +0 -64
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyAiCardPolicyFormDraftUpdate.d.ts +0 -38
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyAiCardPolicyFormDraftUpdate.js +0 -333
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToAiCardPolicyDraft.d.ts +0 -25
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToAiCardPolicyDraft.js +0 -124
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToDraftEpic.d.ts +0 -28
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToDraftEpic.js +0 -64
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToManualCardPolicyDraft.d.ts +0 -17
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToManualCardPolicyDraft.js +0 -122
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyUploadPlanToAiCardPolicyDraft.d.ts +0 -23
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyUploadPlanToAiCardPolicyDraft.js +0 -37
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildAiCardPolicyFormDraftSeed.d.ts +0 -38
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildAiCardPolicyFormDraftSeed.js +0 -391
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildManualCardPolicyFormDraftSeed.d.ts +0 -11
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildManualCardPolicyFormDraftSeed.js +0 -38
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyChipIds.d.ts +0 -24
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyChipIds.js +0 -40
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyFormDraftTypes.d.ts +0 -253
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyFormDraftTypes.js +0 -21
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyRequestParts.d.ts +0 -66
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyRequestParts.js +0 -151
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/deriveAiPolicyReviewRowsFromInputs.d.ts +0 -30
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/deriveAiPolicyReviewRowsFromInputs.js +0 -152
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/seedAiCardPolicyFormDraftEpic.d.ts +0 -26
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/seedAiCardPolicyFormDraftEpic.js +0 -116
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toBulkCardPolicyTemplateRequestsFromDraft.d.ts +0 -35
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toBulkCardPolicyTemplateRequestsFromDraft.js +0 -105
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toCardPolicyTemplateRequestFromDraft.d.ts +0 -23
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toCardPolicyTemplateRequestFromDraft.js +0 -75
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toManualCardPolicyTemplateRequestFromDraft.d.ts +0 -15
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toManualCardPolicyTemplateRequestFromDraft.js +0 -38
- package/lib/view/spendManagement/chargeCards/issueChargeCard/aiCardCreationFormDraftTypes.d.ts +0 -76
- package/lib/view/spendManagement/chargeCards/issueChargeCard/aiCardCreationFormDraftTypes.js +0 -2
- package/lib/view/spendManagement/chargeCards/issueChargeCard/applyAiCardCreationFormDraftUpdate.d.ts +0 -7
- package/lib/view/spendManagement/chargeCards/issueChargeCard/applyAiCardCreationFormDraftUpdate.js +0 -46
- package/lib/view/spendManagement/chargeCards/issueChargeCard/buildAiCardCreationFormDraftSeed.d.ts +0 -28
- package/lib/view/spendManagement/chargeCards/issueChargeCard/buildAiCardCreationFormDraftSeed.js +0 -73
- package/lib/view/spendManagement/chargeCards/issueChargeCard/deriveAiCardRowsFromTeams.d.ts +0 -23
- package/lib/view/spendManagement/chargeCards/issueChargeCard/deriveAiCardRowsFromTeams.js +0 -49
- package/lib/view/spendManagement/chargeCards/issueChargeCard/seedAiCardCreationFormDraftEpic.d.ts +0 -11
- package/lib/view/spendManagement/chargeCards/issueChargeCard/seedAiCardCreationFormDraftEpic.js +0 -69
- package/lib/view/spendManagement/chargeCards/issueChargeCard/toIssueChargeCardLocalDataFromDraft.d.ts +0 -11
- 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
|
-
};
|