@zeniai/client-epic-state 5.0.83-betaRR3 → 5.0.83-betaRR5
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 +1 -4
- package/lib/entity/aiCfo/aiCfoReducer.js +3 -11
- package/lib/entity/aiCfo/aiCfoState.d.ts +0 -19
- package/lib/entity/aiCfo/aiCfoState.js +1 -8
- package/lib/entity/cardPolicy/policyRecommendationFromUploadEpic.d.ts +3 -2
- package/lib/entity/cardPolicy/policyRecommendationFromUploadEpic.js +3 -2
- package/lib/epic.d.ts +14 -12
- package/lib/epic.js +14 -12
- package/lib/esm/entity/aiCfo/aiCfoReducer.js +4 -12
- package/lib/esm/entity/aiCfo/aiCfoState.js +0 -7
- package/lib/esm/entity/cardPolicy/policyRecommendationFromUploadEpic.js +4 -3
- package/lib/esm/epic.js +14 -12
- package/lib/esm/index.js +14 -3
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyAiCardPolicyFormDraftUpdate.js +328 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToAiCardPolicyDraft.js +120 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToDraftEpic.js +60 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToManualCardPolicyDraft.js +118 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyUploadPlanToAiCardPolicyDraft.js +33 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildAiCardPolicyFormDraftSeed.js +385 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildManualCardPolicyFormDraftSeed.js +34 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyChipIds.js +33 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyFormDraftTypes.js +17 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyRequestParts.js +144 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.js +137 -1
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.js +18 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/deriveAiPolicyReviewRowsFromInputs.js +148 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/seedAiCardPolicyFormDraftEpic.js +112 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toBulkCardPolicyTemplateRequestsFromDraft.js +101 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toCardPolicyTemplateRequestFromDraft.js +71 -0
- package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toManualCardPolicyTemplateRequestFromDraft.js +34 -0
- package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/applyAiCardCreationFormDraftUpdate.js +0 -1
- package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/buildAiCardCreationFormDraftSeed.js +13 -6
- package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/deriveAiCardRowsFromTeams.js +1 -3
- package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/toIssueChargeCardLocalDataFromDraft.js +0 -5
- package/lib/index.d.ts +20 -4
- package/lib/index.js +45 -4
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyAiCardPolicyFormDraftUpdate.d.ts +38 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyAiCardPolicyFormDraftUpdate.js +333 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToAiCardPolicyDraft.d.ts +25 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToAiCardPolicyDraft.js +124 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToDraftEpic.d.ts +28 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToDraftEpic.js +64 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToManualCardPolicyDraft.d.ts +17 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToManualCardPolicyDraft.js +122 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyUploadPlanToAiCardPolicyDraft.d.ts +23 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyUploadPlanToAiCardPolicyDraft.js +37 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildAiCardPolicyFormDraftSeed.d.ts +38 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildAiCardPolicyFormDraftSeed.js +391 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildManualCardPolicyFormDraftSeed.d.ts +11 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildManualCardPolicyFormDraftSeed.js +38 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyChipIds.d.ts +24 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyChipIds.js +40 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyFormDraftTypes.d.ts +253 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyFormDraftTypes.js +21 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyRequestParts.d.ts +66 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyRequestParts.js +151 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.d.ts +40 -1
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.js +138 -2
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.d.ts +15 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.js +20 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyState.d.ts +28 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/deriveAiPolicyReviewRowsFromInputs.d.ts +30 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/deriveAiPolicyReviewRowsFromInputs.js +152 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/seedAiCardPolicyFormDraftEpic.d.ts +26 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/seedAiCardPolicyFormDraftEpic.js +116 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toBulkCardPolicyTemplateRequestsFromDraft.d.ts +35 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toBulkCardPolicyTemplateRequestsFromDraft.js +105 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toCardPolicyTemplateRequestFromDraft.d.ts +23 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toCardPolicyTemplateRequestFromDraft.js +75 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toManualCardPolicyTemplateRequestFromDraft.d.ts +15 -0
- package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toManualCardPolicyTemplateRequestFromDraft.js +38 -0
- package/lib/view/spendManagement/chargeCards/issueChargeCard/aiCardCreationFormDraftTypes.d.ts +3 -6
- package/lib/view/spendManagement/chargeCards/issueChargeCard/applyAiCardCreationFormDraftUpdate.js +0 -1
- package/lib/view/spendManagement/chargeCards/issueChargeCard/buildAiCardCreationFormDraftSeed.d.ts +6 -3
- package/lib/view/spendManagement/chargeCards/issueChargeCard/buildAiCardCreationFormDraftSeed.js +13 -6
- package/lib/view/spendManagement/chargeCards/issueChargeCard/deriveAiCardRowsFromTeams.d.ts +1 -4
- package/lib/view/spendManagement/chargeCards/issueChargeCard/deriveAiCardRowsFromTeams.js +1 -3
- package/lib/view/spendManagement/chargeCards/issueChargeCard/toIssueChargeCardLocalDataFromDraft.js +0 -5
- package/package.json +1 -1
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { createSlice } from '@reduxjs/toolkit';
|
|
2
|
+
import { applyAiCardPolicyFormDraftUpdate } from './applyAiCardPolicyFormDraftUpdate';
|
|
3
|
+
import { applyExtractedPolicyToAiCardPolicyDraft as applyExtractedToAiDraftHelper } from './applyExtractedPolicyToAiCardPolicyDraft';
|
|
4
|
+
import { applyExtractedPolicyToManualCardPolicyDraft as applyExtractedToManualDraftHelper } from './applyExtractedPolicyToManualCardPolicyDraft';
|
|
5
|
+
import { applyUploadPlanToAiCardPolicyDraft as applyUploadPlanToAiDraftHelper } from './applyUploadPlanToAiCardPolicyDraft';
|
|
6
|
+
import { buildAiCardPolicyFormDraftSeed } from './buildAiCardPolicyFormDraftSeed';
|
|
7
|
+
import { buildManualCardPolicyFormDraftSeed, } from './buildManualCardPolicyFormDraftSeed';
|
|
8
|
+
import { toMccCategoryLike, } from './cardPolicyFormDraftTypes';
|
|
2
9
|
export const initialState = {
|
|
3
10
|
createCardPolicyTemplateRequestState: {
|
|
4
11
|
fetchState: 'Not-Started',
|
|
@@ -36,10 +43,139 @@ const createCardPolicy = createSlice({
|
|
|
36
43
|
draft.lastCreateCardPolicyTemplateErrors = action.payload.perItemErrors;
|
|
37
44
|
}
|
|
38
45
|
},
|
|
46
|
+
/**
|
|
47
|
+
* Seed the AI CFO card-policy form draft + companion context from
|
|
48
|
+
* the AI's `interactive_form` payload. Dispatched by
|
|
49
|
+
* `seedAiCardPolicyFormDraftEpic` when the AI answer lands.
|
|
50
|
+
*/
|
|
51
|
+
seedAiCardPolicyFormDraft: {
|
|
52
|
+
reducer(draft, action) {
|
|
53
|
+
const { draft: nextDraft, context } = buildAiCardPolicyFormDraftSeed({
|
|
54
|
+
initialData: action.payload.initialData,
|
|
55
|
+
cardsMasterList: action.payload.cardsMasterList,
|
|
56
|
+
categorySearchOptions: action.payload.mccCategories.map(toMccCategoryLike),
|
|
57
|
+
suggestedAllowMerchants: action.payload.suggestedAllowMerchants,
|
|
58
|
+
suggestedBlockMerchants: action.payload.suggestedBlockMerchants,
|
|
59
|
+
suggestedAllowCategories: (action.payload.suggestedAllowCategories ?? []).map(toMccCategoryLike),
|
|
60
|
+
suggestedBlockCategories: (action.payload.suggestedBlockCategories ?? []).map(toMccCategoryLike),
|
|
61
|
+
});
|
|
62
|
+
draft.aiCardPolicyFormDraft = nextDraft;
|
|
63
|
+
draft.aiCardPolicyFormDraftContext = context;
|
|
64
|
+
},
|
|
65
|
+
prepare(payload) {
|
|
66
|
+
return { payload };
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
/**
|
|
70
|
+
* Replace the AI card-policy form draft from the component's
|
|
71
|
+
* `watchHandler`. Delegates to `applyAiCardPolicyFormDraftUpdate`
|
|
72
|
+
* which:
|
|
73
|
+
* - guided: mirrors review-chip diffs into source slices then
|
|
74
|
+
* re-derives `policiesReview` from those slices,
|
|
75
|
+
* - upload: patches "Applied to Cards" chip group from
|
|
76
|
+
* `cardSelections`,
|
|
77
|
+
* - auto: pass-through.
|
|
78
|
+
*/
|
|
79
|
+
updateAiCardPolicyFormDraft(draft, action) {
|
|
80
|
+
draft.aiCardPolicyFormDraft = applyAiCardPolicyFormDraftUpdate({
|
|
81
|
+
prev: draft.aiCardPolicyFormDraft,
|
|
82
|
+
next: action.payload,
|
|
83
|
+
context: draft.aiCardPolicyFormDraftContext,
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
/**
|
|
87
|
+
* Apply a fresh `wizardPlan` returned by the upload-mode
|
|
88
|
+
* recommendation API to the AI draft. No-op when the draft isn't
|
|
89
|
+
* in `upload` mode.
|
|
90
|
+
*/
|
|
91
|
+
updateAiCardPolicyFormDraftFromUploadPlan: {
|
|
92
|
+
reducer(draft, action) {
|
|
93
|
+
if (draft.aiCardPolicyFormDraft == null ||
|
|
94
|
+
draft.aiCardPolicyFormDraftContext == null) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const { draft: nextDraft, context } = applyUploadPlanToAiDraftHelper({
|
|
98
|
+
current: draft.aiCardPolicyFormDraft,
|
|
99
|
+
context: draft.aiCardPolicyFormDraftContext,
|
|
100
|
+
wizardPlan: action.payload.wizardPlan,
|
|
101
|
+
source: action.payload.source,
|
|
102
|
+
});
|
|
103
|
+
draft.aiCardPolicyFormDraft = nextDraft;
|
|
104
|
+
draft.aiCardPolicyFormDraftContext = context;
|
|
105
|
+
},
|
|
106
|
+
prepare(wizardPlan, source) {
|
|
107
|
+
return { payload: { wizardPlan, source } };
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
/**
|
|
111
|
+
* Apply an extracted policy document response to the AI draft.
|
|
112
|
+
* Dispatched by the extract-success bridge epic when the upload
|
|
113
|
+
* was kicked off from inside an AI CFO card-policy form.
|
|
114
|
+
*/
|
|
115
|
+
applyExtractedPolicyToAiCardPolicyDraft: {
|
|
116
|
+
reducer(draft, action) {
|
|
117
|
+
if (draft.aiCardPolicyFormDraft == null) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
draft.aiCardPolicyFormDraft = applyExtractedToAiDraftHelper({
|
|
121
|
+
current: draft.aiCardPolicyFormDraft,
|
|
122
|
+
extracted: action.payload.extracted,
|
|
123
|
+
vendorSearchOptions: action.payload.vendorSearchOptions,
|
|
124
|
+
categorySearchOptions: action.payload.categorySearchOptions,
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
prepare(payload) {
|
|
128
|
+
return { payload };
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
clearAiCardPolicyFormDraft(draft) {
|
|
132
|
+
draft.aiCardPolicyFormDraft = undefined;
|
|
133
|
+
draft.aiCardPolicyFormDraftContext = undefined;
|
|
134
|
+
},
|
|
135
|
+
/**
|
|
136
|
+
* Seed the manual `CardPolicyCreatePage` form draft from the BE
|
|
137
|
+
* suggested merchant/category seeds + the cards master list.
|
|
138
|
+
* Dispatched on the create-page mount.
|
|
139
|
+
*/
|
|
140
|
+
seedManualCardPolicyFormDraft: {
|
|
141
|
+
reducer(draft, action) {
|
|
142
|
+
draft.manualCardPolicyFormDraft = buildManualCardPolicyFormDraftSeed(action.payload);
|
|
143
|
+
},
|
|
144
|
+
prepare(payload) {
|
|
145
|
+
return { payload };
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
updateManualCardPolicyFormDraft(draft, action) {
|
|
149
|
+
draft.manualCardPolicyFormDraft = action.payload;
|
|
150
|
+
},
|
|
151
|
+
/**
|
|
152
|
+
* Apply an extracted policy document response to the manual draft.
|
|
153
|
+
* Dispatched by the extract-success bridge epic when the upload was
|
|
154
|
+
* kicked off from the manual `CardPolicyCreatePage`.
|
|
155
|
+
*/
|
|
156
|
+
applyExtractedPolicyToManualCardPolicyDraft: {
|
|
157
|
+
reducer(draft, action) {
|
|
158
|
+
if (draft.manualCardPolicyFormDraft == null) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
draft.manualCardPolicyFormDraft = applyExtractedToManualDraftHelper({
|
|
162
|
+
current: draft.manualCardPolicyFormDraft,
|
|
163
|
+
extracted: action.payload.extracted,
|
|
164
|
+
vendorSearchOptions: action.payload.vendorSearchOptions,
|
|
165
|
+
categorySearchOptions: action.payload.categorySearchOptions,
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
prepare(payload) {
|
|
169
|
+
return { payload };
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
clearManualCardPolicyFormDraft(draft) {
|
|
173
|
+
draft.manualCardPolicyFormDraft = undefined;
|
|
174
|
+
},
|
|
39
175
|
clearCreateCardPolicy(draft) {
|
|
40
176
|
Object.assign(draft, initialState);
|
|
41
177
|
},
|
|
42
178
|
},
|
|
43
179
|
});
|
|
44
|
-
export const { createCardPolicyTemplates, updateCreateCardPolicyTemplateRequestState, clearCreateCardPolicy, } = createCardPolicy.actions;
|
|
180
|
+
export const { createCardPolicyTemplates, updateCreateCardPolicyTemplateRequestState, seedAiCardPolicyFormDraft, updateAiCardPolicyFormDraft, updateAiCardPolicyFormDraftFromUploadPlan, applyExtractedPolicyToAiCardPolicyDraft, clearAiCardPolicyFormDraft, seedManualCardPolicyFormDraft, updateManualCardPolicyFormDraft, applyExtractedPolicyToManualCardPolicyDraft, clearManualCardPolicyFormDraft, clearCreateCardPolicy, } = createCardPolicy.actions;
|
|
45
181
|
export default createCardPolicy.reducer;
|
|
@@ -37,3 +37,21 @@ export function getLastCreateCardPolicyTemplateErrors(state) {
|
|
|
37
37
|
export function getLastCreateCardPolicySourceChatSessionId(state) {
|
|
38
38
|
return state.lastCreateCardPolicySourceChatSessionId;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Returns the AI CFO card-policy form draft, or `undefined` before
|
|
42
|
+
* `seedAiCardPolicyFormDraft` runs / after `clearAiCardPolicyFormDraft`.
|
|
43
|
+
* Connected screens use this to hydrate the
|
|
44
|
+
* `CardPolicyInteractiveForm`'s RHF.
|
|
45
|
+
*/
|
|
46
|
+
export function getAiCardPolicyFormDraft(state) {
|
|
47
|
+
return state.aiCardPolicyFormDraft;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Returns the manual `CardPolicyCreatePage` form draft, or `undefined`
|
|
51
|
+
* before `seedManualCardPolicyFormDraft` runs / after
|
|
52
|
+
* `clearManualCardPolicyFormDraft`. The connected screen uses this to
|
|
53
|
+
* hydrate `CardPolicyFormSections`.
|
|
54
|
+
*/
|
|
55
|
+
export function getManualCardPolicyFormDraft(state) {
|
|
56
|
+
return state.manualCardPolicyFormDraft;
|
|
57
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose the Step 5 review rows from the live Step 1–4 form state.
|
|
3
|
+
*
|
|
4
|
+
* - One row per `suggestedPoliciesSeed` entry, in the same order.
|
|
5
|
+
* - Per-row `isChecked` preserved from `prevPoliciesReview`; otherwise
|
|
6
|
+
* falls back to the seed value.
|
|
7
|
+
* - Chip groups rebuilt from the live vendor / category / card / limit
|
|
8
|
+
* slices, with per-chip `isSelected` merged from the previous build
|
|
9
|
+
* so user-removed (`+ name`) chips don't snap back when an upstream
|
|
10
|
+
* slice changes.
|
|
11
|
+
*/
|
|
12
|
+
export const deriveAiPolicyReviewRowsFromInputs = ({ vendorPolicy, categoryPolicy, cardSelections, limits, suggestedPoliciesSeed, prevPoliciesReview, }) => {
|
|
13
|
+
const prevById = new Map(prevPoliciesReview.map((row) => [row.id, row]));
|
|
14
|
+
const groups = deriveSharedChipGroups({
|
|
15
|
+
vendorPolicy,
|
|
16
|
+
categoryPolicy,
|
|
17
|
+
cardSelections,
|
|
18
|
+
limits,
|
|
19
|
+
});
|
|
20
|
+
return suggestedPoliciesSeed.map((seed) => {
|
|
21
|
+
const previous = prevById.get(seed.id);
|
|
22
|
+
return {
|
|
23
|
+
id: seed.id,
|
|
24
|
+
name: seed.name,
|
|
25
|
+
isChecked: previous?.isChecked ?? seed.isChecked,
|
|
26
|
+
chipGroups: previous != null
|
|
27
|
+
? mergeChipSelections(groups, previous.chipGroups)
|
|
28
|
+
: groups,
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const mergeChipSelections = (next, prev) => {
|
|
33
|
+
const prevByGroupId = new Map(prev.map((group) => [group.id, group]));
|
|
34
|
+
return next.map((group) => {
|
|
35
|
+
const prevGroup = prevByGroupId.get(group.id);
|
|
36
|
+
if (prevGroup == null) {
|
|
37
|
+
return group;
|
|
38
|
+
}
|
|
39
|
+
const prevSelectedById = new Map(prevGroup.chips.map((chip) => [chip.id, chip.isSelected]));
|
|
40
|
+
return {
|
|
41
|
+
...group,
|
|
42
|
+
chips: group.chips.map((chip) => {
|
|
43
|
+
const prevSelected = prevSelectedById.get(chip.id);
|
|
44
|
+
return prevSelected === undefined
|
|
45
|
+
? chip
|
|
46
|
+
: { ...chip, isSelected: prevSelected };
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
const deriveSharedChipGroups = ({ vendorPolicy, categoryPolicy, cardSelections, limits, }) => {
|
|
52
|
+
const groups = [];
|
|
53
|
+
const vendorChips = chipsFromAllowBlock(vendorPolicy);
|
|
54
|
+
if (vendorChips.length > 0) {
|
|
55
|
+
groups.push({
|
|
56
|
+
id: 'vendors',
|
|
57
|
+
label: vendorPolicy.mode === 'allow' ? 'Allowed vendors' : 'Blocked vendors',
|
|
58
|
+
chips: vendorChips,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const categoryChips = chipsFromAllowBlock(categoryPolicy);
|
|
62
|
+
if (categoryChips.length > 0) {
|
|
63
|
+
groups.push({
|
|
64
|
+
id: 'categories',
|
|
65
|
+
label: categoryPolicy.mode === 'allow'
|
|
66
|
+
? 'Allowed categories'
|
|
67
|
+
: 'Blocked categories',
|
|
68
|
+
chips: categoryChips,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const cardChips = cardSelections
|
|
72
|
+
.filter((row) => row.isChecked)
|
|
73
|
+
.map((row) => ({
|
|
74
|
+
id: row.id,
|
|
75
|
+
label: row.owner.displayName,
|
|
76
|
+
chipKind: 'avatar',
|
|
77
|
+
}));
|
|
78
|
+
if (cardChips.length > 0) {
|
|
79
|
+
groups.push({ id: 'cardholders', label: 'Cardholders', chips: cardChips });
|
|
80
|
+
}
|
|
81
|
+
const limitChips = limits.map((row) => ({
|
|
82
|
+
id: limitChipIdForRowId(row.id),
|
|
83
|
+
label: limitDisplayLabelForRowId(row.id),
|
|
84
|
+
helperText: limitHelperTextForRowId(row.id),
|
|
85
|
+
placeholder: '$0',
|
|
86
|
+
chipKind: 'positive',
|
|
87
|
+
isSelected: row.isEnabled,
|
|
88
|
+
optionalCode: row.isEnabled && row.amount != null ? String(row.amount) : undefined,
|
|
89
|
+
}));
|
|
90
|
+
if (limitChips.length > 0) {
|
|
91
|
+
groups.push({
|
|
92
|
+
id: 'limits',
|
|
93
|
+
label: 'Limits',
|
|
94
|
+
kind: 'limits',
|
|
95
|
+
chips: limitChips,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return groups;
|
|
99
|
+
};
|
|
100
|
+
const chipsFromAllowBlock = (field) => {
|
|
101
|
+
const items = field.mode === 'allow' ? field.allowItems : field.blockItems;
|
|
102
|
+
return items
|
|
103
|
+
.filter((item) => item.isSelected)
|
|
104
|
+
.map((item) => ({
|
|
105
|
+
id: item.id,
|
|
106
|
+
label: item.optionalCode != null
|
|
107
|
+
? `${item.label} (${item.optionalCode})`
|
|
108
|
+
: item.label,
|
|
109
|
+
chipKind: field.mode === 'allow' ? 'positive' : 'negative',
|
|
110
|
+
}));
|
|
111
|
+
};
|
|
112
|
+
const limitChipIdForRowId = (rowId) => {
|
|
113
|
+
if (rowId === 'transactionLimit') {
|
|
114
|
+
return 'limit-transaction';
|
|
115
|
+
}
|
|
116
|
+
if (rowId === 'requireReceipt') {
|
|
117
|
+
return 'limit-receipt';
|
|
118
|
+
}
|
|
119
|
+
return rowId;
|
|
120
|
+
};
|
|
121
|
+
const limitDisplayLabelForRowId = (rowId) => {
|
|
122
|
+
if (rowId === 'transactionLimit') {
|
|
123
|
+
return 'Transaction Limit';
|
|
124
|
+
}
|
|
125
|
+
if (rowId === 'requireReceipt') {
|
|
126
|
+
return 'Require Receipt';
|
|
127
|
+
}
|
|
128
|
+
return humanizeLimitId(rowId);
|
|
129
|
+
};
|
|
130
|
+
const limitHelperTextForRowId = (rowId) => {
|
|
131
|
+
if (rowId === 'transactionLimit') {
|
|
132
|
+
return 'Maximum amount per single transaction';
|
|
133
|
+
}
|
|
134
|
+
if (rowId === 'requireReceipt') {
|
|
135
|
+
return 'Require a receipt above this amount';
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
};
|
|
139
|
+
const humanizeLimitId = (id) => {
|
|
140
|
+
const spaced = id
|
|
141
|
+
.replace(/([A-Z])/g, ' $1')
|
|
142
|
+
.replace(/[_-]+/g, ' ')
|
|
143
|
+
.trim();
|
|
144
|
+
if (spaced.length === 0) {
|
|
145
|
+
return id;
|
|
146
|
+
}
|
|
147
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
|
|
148
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { EMPTY, concat, of } from 'rxjs';
|
|
2
|
+
import { distinctUntilChanged, filter, map, mergeMap, take, } from 'rxjs/operators';
|
|
3
|
+
import { upsertAnswerPayload, upsertOrAddQuestionAnswerPayload, } from '../../../../../entity/aiCfo/aiCfoReducer';
|
|
4
|
+
import { getQuestionAnswerByIdForChatSession } from '../../../../../entity/aiCfo/aiCfoSelector';
|
|
5
|
+
import { fetchCardPolicyMccCategories } from '../../../../../entity/cardPolicy/cardPolicyReducer';
|
|
6
|
+
import { getAllChargeCardsWithUser } from '../../chargeCardList/chargeCardListSelector';
|
|
7
|
+
import { clearAiCardPolicyFormDraft, seedAiCardPolicyFormDraft, updateCreateCardPolicyTemplateRequestState, } from './createCardPolicyReducer';
|
|
8
|
+
const isClosedChargeCardStatusCode = (code) => code === 'closed' || code === 'closed_by_customer';
|
|
9
|
+
const toCardPolicyCardSelectionRow = (card) => {
|
|
10
|
+
const user = card.cardHolderUser;
|
|
11
|
+
const displayName = `${user.firstName ?? ''} ${user.lastName ?? ''}`.trim() ||
|
|
12
|
+
user.email ||
|
|
13
|
+
user.userId;
|
|
14
|
+
return {
|
|
15
|
+
id: card.id,
|
|
16
|
+
isChecked: false,
|
|
17
|
+
cardName: card.cardName,
|
|
18
|
+
last4: card.accountLast4,
|
|
19
|
+
limitAmount: card.limit?.amount ?? 0,
|
|
20
|
+
limitCadence: card.creditLimitFrequency?.code,
|
|
21
|
+
owner: {
|
|
22
|
+
userId: user.userId,
|
|
23
|
+
displayName,
|
|
24
|
+
roleLabel: card.cardHolderRoles?.titleAtCompany,
|
|
25
|
+
},
|
|
26
|
+
utilizationAmount: (card.limit?.amount ?? 0) - (card.available?.amount ?? 0),
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const readCardsMasterList = (state) => {
|
|
30
|
+
const view = getAllChargeCardsWithUser(state);
|
|
31
|
+
return view.cards
|
|
32
|
+
.filter((card) => !isClosedChargeCardStatusCode(card.status?.code))
|
|
33
|
+
.map(toCardPolicyCardSelectionRow);
|
|
34
|
+
};
|
|
35
|
+
const readMccCategoriesAvailable = (state) => state.cardPolicyState.mccCategoriesFetchState === 'Completed';
|
|
36
|
+
const readCardPolicyPayload = (state, chatSessionId, questionAnswerId) => {
|
|
37
|
+
const qa = getQuestionAnswerByIdForChatSession(state.aiCfoState, chatSessionId, questionAnswerId);
|
|
38
|
+
const visualization = qa?.responses.answer?.content.visualization;
|
|
39
|
+
if (visualization?.type !== 'interactive_form') {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const data = visualization.data;
|
|
43
|
+
if (!('formType' in data) || data.formType !== 'card_policy') {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
return data.payload;
|
|
47
|
+
};
|
|
48
|
+
const resolveQuestionAnswerId = (state, action) => {
|
|
49
|
+
if (action.payload.questionAnswerId != null) {
|
|
50
|
+
return action.payload.questionAnswerId;
|
|
51
|
+
}
|
|
52
|
+
const session = state.aiCfoState.aiCfoByChatSessionId[action.payload.chatSessionId];
|
|
53
|
+
return session?.questionAnswers.slice(-1)[0]?.questionAnswerId;
|
|
54
|
+
};
|
|
55
|
+
const isSeedTrigger = (action) => upsertAnswerPayload.match(action) ||
|
|
56
|
+
upsertOrAddQuestionAnswerPayload.match(action);
|
|
57
|
+
const isCreateSuccess = (action) => updateCreateCardPolicyTemplateRequestState.match(action) &&
|
|
58
|
+
action.payload.fetchState === 'Completed';
|
|
59
|
+
const buildSeedAction = (state, payload) => {
|
|
60
|
+
const cardsMasterList = readCardsMasterList(state);
|
|
61
|
+
const mccCategories = state.cardPolicyState.mccCategories;
|
|
62
|
+
return seedAiCardPolicyFormDraft({
|
|
63
|
+
initialData: payload,
|
|
64
|
+
cardsMasterList,
|
|
65
|
+
mccCategories,
|
|
66
|
+
suggestedAllowMerchants: state.cardPolicyState.suggestedAllowMerchants,
|
|
67
|
+
suggestedBlockMerchants: state.cardPolicyState.suggestedBlockMerchants,
|
|
68
|
+
suggestedAllowCategories: state.cardPolicyState.suggestedAllowCategories,
|
|
69
|
+
suggestedBlockCategories: state.cardPolicyState.suggestedBlockCategories,
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Listens for AI CFO `interactive_form` answers with
|
|
74
|
+
* `formType === "card_policy"` and seeds the AI card-policy form
|
|
75
|
+
* draft + companion context onto `createCardPolicy` state. Mirrors
|
|
76
|
+
* `seedAiCardCreationFormDraftEpic` for the cards form.
|
|
77
|
+
*
|
|
78
|
+
* Also clears the draft on a successful bulk create so a stale draft
|
|
79
|
+
* doesn't leak into the next AI session.
|
|
80
|
+
*
|
|
81
|
+
* MCC categories drive category chip labels in the seed. When the
|
|
82
|
+
* categories slice isn't loaded yet, the epic kicks off
|
|
83
|
+
* `fetchCardPolicyMccCategories` and waits for `Completed` before
|
|
84
|
+
* dispatching the seed (mirrors `fetchUserListByType` in the cards
|
|
85
|
+
* variant).
|
|
86
|
+
*/
|
|
87
|
+
export const seedAiCardPolicyFormDraftEpic = (actions$, state$) => actions$.pipe(mergeMap((action) => {
|
|
88
|
+
if (isCreateSuccess(action)) {
|
|
89
|
+
return of(clearAiCardPolicyFormDraft());
|
|
90
|
+
}
|
|
91
|
+
if (!isSeedTrigger(action)) {
|
|
92
|
+
return EMPTY;
|
|
93
|
+
}
|
|
94
|
+
const state = state$.value;
|
|
95
|
+
const questionAnswerId = resolveQuestionAnswerId(state, action);
|
|
96
|
+
if (questionAnswerId == null) {
|
|
97
|
+
return EMPTY;
|
|
98
|
+
}
|
|
99
|
+
const payload = readCardPolicyPayload(state, action.payload.chatSessionId, questionAnswerId);
|
|
100
|
+
if (payload == null) {
|
|
101
|
+
return EMPTY;
|
|
102
|
+
}
|
|
103
|
+
if (readMccCategoriesAvailable(state)) {
|
|
104
|
+
return of(buildSeedAction(state, payload));
|
|
105
|
+
}
|
|
106
|
+
const seedWhenMccLoaded$ = state$.pipe(map((rs) => readMccCategoriesAvailable(rs)), distinctUntilChanged(), filter((available) => available), take(1), map(() => buildSeedAction(state$.value, payload)));
|
|
107
|
+
const mccFetchState = state.cardPolicyState.mccCategoriesFetchState;
|
|
108
|
+
if (mccFetchState === 'In-Progress') {
|
|
109
|
+
return seedWhenMccLoaded$;
|
|
110
|
+
}
|
|
111
|
+
return concat(of(fetchCardPolicyMccCategories()), seedWhenMccLoaded$);
|
|
112
|
+
}));
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { CARD_POLICY_LIMIT_ROW_ID_REQUIRE_RECEIPT, CARD_POLICY_LIMIT_ROW_ID_TRANSACTION, } from './cardPolicyFormDraftTypes';
|
|
2
|
+
import { REVIEW_GROUP_IDS, REVIEW_LIMIT_CHIP_IDS, amountFromLimitRow, collectCategoryCodes, collectMerchantNames, } from './cardPolicyRequestParts';
|
|
3
|
+
const isSelectedChip = (chip) => chip.isSelected !== false;
|
|
4
|
+
const chipsInGroup = (row, groupId) => {
|
|
5
|
+
const group = row.chipGroups.find((g) => g.id === groupId);
|
|
6
|
+
if (group == null) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
return group.chips.filter(isSelectedChip);
|
|
10
|
+
};
|
|
11
|
+
const collectVendorLabelsFromReviewRow = (row, groupId) => chipsInGroup(row, groupId).map((chip) => chip.label);
|
|
12
|
+
const collectCategoryCodesFromReviewRow = (row, groupId) => chipsInGroup(row, groupId).map((chip) => chip.optionalCode ?? chip.id);
|
|
13
|
+
const collectCardIdsFromReviewRow = (row) => chipsInGroup(row, REVIEW_GROUP_IDS.appliedToCards).flatMap((chip) => chip.cardIds ?? []);
|
|
14
|
+
const limitValueFromReviewRow = (row, chipId) => {
|
|
15
|
+
const group = row.chipGroups.find((g) => g.id === REVIEW_GROUP_IDS.limits);
|
|
16
|
+
if (group == null) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const chip = group.chips.find((c) => c.id === chipId && isSelectedChip(c));
|
|
20
|
+
if (chip == null) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
if (chip.optionalCode != null) {
|
|
24
|
+
const parsed = Number(chip.optionalCode);
|
|
25
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
26
|
+
}
|
|
27
|
+
const labelDigits = chip.label.match(/\d+(?:\.\d+)?/);
|
|
28
|
+
if (labelDigits == null) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const parsed = Number(labelDigits[0]);
|
|
32
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
33
|
+
};
|
|
34
|
+
const buildGuidedEntry = (row, draft, guidedMode, description) => ({
|
|
35
|
+
allowedEntity: {
|
|
36
|
+
categoryCodes: collectCategoryCodes(draft.categoryPolicy, 'allow'),
|
|
37
|
+
merchantNames: collectMerchantNames(draft.vendorPolicy, 'allow'),
|
|
38
|
+
},
|
|
39
|
+
applyToCards: draft.cardSelections
|
|
40
|
+
.filter((card) => card.isChecked)
|
|
41
|
+
.map((card) => card.id),
|
|
42
|
+
blockedEntity: {
|
|
43
|
+
categoryCodes: collectCategoryCodes(draft.categoryPolicy, 'block'),
|
|
44
|
+
merchantNames: collectMerchantNames(draft.vendorPolicy, 'block'),
|
|
45
|
+
},
|
|
46
|
+
description: description.trim(),
|
|
47
|
+
mode: guidedMode,
|
|
48
|
+
name: row.name.trim(),
|
|
49
|
+
requiredReceiptThreshold: amountFromLimitRow(draft.limits, CARD_POLICY_LIMIT_ROW_ID_REQUIRE_RECEIPT),
|
|
50
|
+
spendLimits: {
|
|
51
|
+
transaction: amountFromLimitRow(draft.limits, CARD_POLICY_LIMIT_ROW_ID_TRANSACTION),
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
const buildAutoEntry = (row, description) => ({
|
|
55
|
+
allowedEntity: {
|
|
56
|
+
categoryCodes: collectCategoryCodesFromReviewRow(row, REVIEW_GROUP_IDS.allowedCategories),
|
|
57
|
+
merchantNames: collectVendorLabelsFromReviewRow(row, REVIEW_GROUP_IDS.allowedVendors),
|
|
58
|
+
},
|
|
59
|
+
applyToCards: collectCardIdsFromReviewRow(row),
|
|
60
|
+
blockedEntity: {
|
|
61
|
+
categoryCodes: collectCategoryCodesFromReviewRow(row, REVIEW_GROUP_IDS.restrictedCategories),
|
|
62
|
+
merchantNames: collectVendorLabelsFromReviewRow(row, REVIEW_GROUP_IDS.blockedVendors),
|
|
63
|
+
},
|
|
64
|
+
description: description.trim(),
|
|
65
|
+
name: row.name.trim(),
|
|
66
|
+
});
|
|
67
|
+
const buildUploadEntry = (row, draft, description) => ({
|
|
68
|
+
allowedEntity: {
|
|
69
|
+
categoryCodes: collectCategoryCodesFromReviewRow(row, REVIEW_GROUP_IDS.allowedCategories),
|
|
70
|
+
merchantNames: collectVendorLabelsFromReviewRow(row, REVIEW_GROUP_IDS.allowedVendors),
|
|
71
|
+
},
|
|
72
|
+
applyToCards: draft.cardSelections
|
|
73
|
+
.filter((card) => card.isChecked)
|
|
74
|
+
.map((card) => card.id),
|
|
75
|
+
blockedEntity: {
|
|
76
|
+
categoryCodes: collectCategoryCodesFromReviewRow(row, REVIEW_GROUP_IDS.restrictedCategories),
|
|
77
|
+
merchantNames: collectVendorLabelsFromReviewRow(row, REVIEW_GROUP_IDS.blockedVendors),
|
|
78
|
+
},
|
|
79
|
+
description: description.trim(),
|
|
80
|
+
name: row.name.trim(),
|
|
81
|
+
requiredReceiptThreshold: limitValueFromReviewRow(row, REVIEW_LIMIT_CHIP_IDS.receipt),
|
|
82
|
+
spendLimits: {
|
|
83
|
+
transaction: limitValueFromReviewRow(row, REVIEW_LIMIT_CHIP_IDS.transaction),
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
/**
|
|
87
|
+
* Build the array of per-row entries for the bulk
|
|
88
|
+
* `createCardPolicyTemplates` action from the AI CFO form draft.
|
|
89
|
+
* Iterates checked review rows and emits one entry per row, with the
|
|
90
|
+
* per-mode key contract enforced internally.
|
|
91
|
+
*/
|
|
92
|
+
export const toBulkCardPolicyTemplateRequestsFromDraft = ({ draft, description = '', guidedMode = 'strict', }) => {
|
|
93
|
+
const checkedRows = draft.policiesReview.filter((row) => row.isChecked);
|
|
94
|
+
if (draft.policyKind === 'guided') {
|
|
95
|
+
return checkedRows.map((row) => buildGuidedEntry(row, draft, guidedMode, description));
|
|
96
|
+
}
|
|
97
|
+
if (draft.policyKind === 'upload') {
|
|
98
|
+
return checkedRows.map((row) => buildUploadEntry(row, draft, description));
|
|
99
|
+
}
|
|
100
|
+
return checkedRows.map((row) => buildAutoEntry(row, description));
|
|
101
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { CARD_POLICY_LIMIT_ROW_ID_REQUIRE_RECEIPT, CARD_POLICY_LIMIT_ROW_ID_TRANSACTION, } from './cardPolicyFormDraftTypes';
|
|
2
|
+
import { amountFromLimitRow, collectCategoryCodes, collectMerchantNames, projectAutoReviewRowToCardPolicyRequestParts, } from './cardPolicyRequestParts';
|
|
3
|
+
/**
|
|
4
|
+
* Map the AI CFO `CardPolicyFormDraft` to the wire-ready
|
|
5
|
+
* `CreateCardPolicyTemplateRequest`. Branches on `draft.policyKind`:
|
|
6
|
+
*
|
|
7
|
+
* - `auto` / `upload`: active review row's chip groups are canonical;
|
|
8
|
+
* project via `projectAutoReviewRowToCardPolicyRequestParts`. Active
|
|
9
|
+
* row = "first checked, fallback to first".
|
|
10
|
+
* - `guided`: source slices are canonical (the
|
|
11
|
+
* `applyAiCardPolicyFormDraftUpdate` reducer mirrors review-chip
|
|
12
|
+
* edits back into them); read directly from the source slices.
|
|
13
|
+
*
|
|
14
|
+
* Name picking: first checked row of `policiesReview`, fallback to the
|
|
15
|
+
* first row, fallback to empty.
|
|
16
|
+
*/
|
|
17
|
+
export const toCardPolicyTemplateRequestFromDraft = ({ draft, description = '', mode = 'strict', }) => {
|
|
18
|
+
const name = pickPolicyName(draft);
|
|
19
|
+
const base = {
|
|
20
|
+
description: description.trim(),
|
|
21
|
+
mode,
|
|
22
|
+
name: name.trim(),
|
|
23
|
+
};
|
|
24
|
+
if (draft.policyKind === 'auto' || draft.policyKind === 'upload') {
|
|
25
|
+
const activeRow = draft.policiesReview.find((row) => row.isChecked) ??
|
|
26
|
+
draft.policiesReview[0];
|
|
27
|
+
if (activeRow == null) {
|
|
28
|
+
return {
|
|
29
|
+
...base,
|
|
30
|
+
allowedEntity: { categoryCodes: [], merchantNames: [] },
|
|
31
|
+
applyToCards: [],
|
|
32
|
+
blockedEntity: { categoryCodes: [], merchantNames: [] },
|
|
33
|
+
requiredReceiptThreshold: null,
|
|
34
|
+
spendLimits: { transaction: null },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
...base,
|
|
39
|
+
...projectAutoReviewRowToCardPolicyRequestParts(activeRow),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// Guided
|
|
43
|
+
return {
|
|
44
|
+
...base,
|
|
45
|
+
allowedEntity: {
|
|
46
|
+
categoryCodes: collectCategoryCodes(draft.categoryPolicy, 'allow'),
|
|
47
|
+
merchantNames: collectMerchantNames(draft.vendorPolicy, 'allow'),
|
|
48
|
+
},
|
|
49
|
+
applyToCards: draft.cardSelections
|
|
50
|
+
.filter((row) => row.isChecked)
|
|
51
|
+
.map((row) => row.id),
|
|
52
|
+
blockedEntity: {
|
|
53
|
+
categoryCodes: collectCategoryCodes(draft.categoryPolicy, 'block'),
|
|
54
|
+
merchantNames: collectMerchantNames(draft.vendorPolicy, 'block'),
|
|
55
|
+
},
|
|
56
|
+
requiredReceiptThreshold: amountFromLimitRow(draft.limits, CARD_POLICY_LIMIT_ROW_ID_REQUIRE_RECEIPT),
|
|
57
|
+
spendLimits: {
|
|
58
|
+
transaction: amountFromLimitRow(draft.limits, CARD_POLICY_LIMIT_ROW_ID_TRANSACTION),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
const pickPolicyName = (draft) => {
|
|
63
|
+
const checked = draft.policiesReview.find((row) => row.isChecked);
|
|
64
|
+
if (checked != null) {
|
|
65
|
+
return checked.name;
|
|
66
|
+
}
|
|
67
|
+
if (draft.policiesReview.length > 0) {
|
|
68
|
+
return draft.policiesReview[0].name;
|
|
69
|
+
}
|
|
70
|
+
return '';
|
|
71
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { CARD_POLICY_LIMIT_ROW_ID_REQUIRE_RECEIPT, CARD_POLICY_LIMIT_ROW_ID_TRANSACTION, } from './cardPolicyFormDraftTypes';
|
|
2
|
+
import { amountFromLimitRow, collectCategoryCodes, collectMerchantNames, } from './cardPolicyRequestParts';
|
|
3
|
+
/**
|
|
4
|
+
* Map the manual `CardPolicyCreateFormDraft` onto the wire-ready
|
|
5
|
+
* `CreateCardPolicyTemplateRequest` payload consumed by the
|
|
6
|
+
* `createCardPolicyTemplates` epic.
|
|
7
|
+
*
|
|
8
|
+
* Mirrors the AI guided builder minus the `policiesReview` projection:
|
|
9
|
+
* - `vendorPolicy` / `categoryPolicy` are read directly; `mode` is
|
|
10
|
+
* forwarded from the form.
|
|
11
|
+
* - `applyToCards` is the id list of checked rows in `cardSelections`.
|
|
12
|
+
* - `spendLimits.transaction` / `requiredReceiptThreshold` are `null`
|
|
13
|
+
* when their toggle is off (BE: "not configured").
|
|
14
|
+
*/
|
|
15
|
+
export const toManualCardPolicyTemplateRequestFromDraft = (draft) => ({
|
|
16
|
+
allowedEntity: {
|
|
17
|
+
categoryCodes: collectCategoryCodes(draft.categoryPolicy, 'allow'),
|
|
18
|
+
merchantNames: collectMerchantNames(draft.vendorPolicy, 'allow'),
|
|
19
|
+
},
|
|
20
|
+
applyToCards: draft.cardSelections
|
|
21
|
+
.filter((row) => row.isChecked)
|
|
22
|
+
.map((row) => row.id),
|
|
23
|
+
blockedEntity: {
|
|
24
|
+
categoryCodes: collectCategoryCodes(draft.categoryPolicy, 'block'),
|
|
25
|
+
merchantNames: collectMerchantNames(draft.vendorPolicy, 'block'),
|
|
26
|
+
},
|
|
27
|
+
description: draft.description.trim(),
|
|
28
|
+
mode: draft.mode,
|
|
29
|
+
name: draft.name.trim(),
|
|
30
|
+
requiredReceiptThreshold: amountFromLimitRow(draft.limits, CARD_POLICY_LIMIT_ROW_ID_REQUIRE_RECEIPT),
|
|
31
|
+
spendLimits: {
|
|
32
|
+
transaction: amountFromLimitRow(draft.limits, CARD_POLICY_LIMIT_ROW_ID_TRANSACTION),
|
|
33
|
+
},
|
|
34
|
+
});
|
|
@@ -24,7 +24,6 @@ export const applyAiCardCreationFormDraftUpdate = ({ next, prev, context, }) =>
|
|
|
24
24
|
teamRows: next.teamRows,
|
|
25
25
|
prevCardRows: next.cardRows,
|
|
26
26
|
allUsers: context.allUsers,
|
|
27
|
-
defaultCardOptionId: context.defaultCardOptionId,
|
|
28
27
|
suggestedOwnerUserIdByTeamId: context.suggestedOwnerUserIdByTeamId,
|
|
29
28
|
}),
|
|
30
29
|
};
|