@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
|
@@ -0,0 +1,120 @@
|
|
|
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();
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
}));
|
|
@@ -0,0 +1,118 @@
|
|
|
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();
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
};
|