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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/lib/entity/aiCfo/aiCfoPayload.d.ts +4 -1
  2. package/lib/entity/aiCfo/aiCfoReducer.js +17 -4
  3. package/lib/entity/aiCfo/aiCfoState.d.ts +19 -0
  4. package/lib/entity/aiCfo/aiCfoState.js +8 -1
  5. package/lib/entity/cardPolicy/cardPolicyReducer.js +99 -9
  6. package/lib/entity/cardPolicy/cardPolicySelector.js +4 -0
  7. package/lib/entity/cardPolicy/policyRecommendationFromUploadEpic.d.ts +2 -3
  8. package/lib/entity/cardPolicy/policyRecommendationFromUploadEpic.js +10 -4
  9. package/lib/epic.d.ts +12 -15
  10. package/lib/epic.js +12 -15
  11. package/lib/esm/entity/aiCfo/aiCfoReducer.js +18 -5
  12. package/lib/esm/entity/aiCfo/aiCfoState.js +7 -0
  13. package/lib/esm/entity/cardPolicy/cardPolicyReducer.js +99 -9
  14. package/lib/esm/entity/cardPolicy/cardPolicySelector.js +4 -0
  15. package/lib/esm/entity/cardPolicy/policyRecommendationFromUploadEpic.js +11 -5
  16. package/lib/esm/epic.js +12 -15
  17. package/lib/esm/index.js +6 -21
  18. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.js +19 -138
  19. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.js +0 -18
  20. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardReducer.js +5 -38
  21. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardSelector.js +0 -23
  22. package/lib/index.d.ts +7 -28
  23. package/lib/index.js +14 -68
  24. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.d.ts +1 -40
  25. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyReducer.js +20 -139
  26. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.d.ts +0 -15
  27. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicySelector.js +0 -20
  28. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/createCardPolicyState.d.ts +0 -28
  29. package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardReducer.d.ts +1 -7
  30. package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardReducer.js +6 -39
  31. package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardSelector.d.ts +0 -15
  32. package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardSelector.js +1 -26
  33. package/lib/view/spendManagement/chargeCards/issueChargeCard/issueChargeCardState.d.ts +0 -16
  34. package/package.json +1 -1
  35. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyAiCardPolicyFormDraftUpdate.js +0 -328
  36. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToAiCardPolicyDraft.js +0 -120
  37. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToDraftEpic.js +0 -60
  38. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToManualCardPolicyDraft.js +0 -118
  39. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyUploadPlanToAiCardPolicyDraft.js +0 -33
  40. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildAiCardPolicyFormDraftSeed.js +0 -385
  41. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildManualCardPolicyFormDraftSeed.js +0 -34
  42. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyChipIds.js +0 -33
  43. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyFormDraftTypes.js +0 -17
  44. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyRequestParts.js +0 -144
  45. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/deriveAiPolicyReviewRowsFromInputs.js +0 -148
  46. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/seedAiCardPolicyFormDraftEpic.js +0 -112
  47. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toBulkCardPolicyTemplateRequestsFromDraft.js +0 -101
  48. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toCardPolicyTemplateRequestFromDraft.js +0 -71
  49. package/lib/esm/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toManualCardPolicyTemplateRequestFromDraft.js +0 -34
  50. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/aiCardCreationFormDraftTypes.js +0 -1
  51. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/applyAiCardCreationFormDraftUpdate.js +0 -42
  52. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/buildAiCardCreationFormDraftSeed.js +0 -69
  53. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/deriveAiCardRowsFromTeams.js +0 -45
  54. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/seedAiCardCreationFormDraftEpic.js +0 -65
  55. package/lib/esm/view/spendManagement/chargeCards/issueChargeCard/toIssueChargeCardLocalDataFromDraft.js +0 -64
  56. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyAiCardPolicyFormDraftUpdate.d.ts +0 -38
  57. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyAiCardPolicyFormDraftUpdate.js +0 -333
  58. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToAiCardPolicyDraft.d.ts +0 -25
  59. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToAiCardPolicyDraft.js +0 -124
  60. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToDraftEpic.d.ts +0 -28
  61. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToDraftEpic.js +0 -64
  62. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToManualCardPolicyDraft.d.ts +0 -17
  63. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyExtractedPolicyToManualCardPolicyDraft.js +0 -122
  64. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyUploadPlanToAiCardPolicyDraft.d.ts +0 -23
  65. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/applyUploadPlanToAiCardPolicyDraft.js +0 -37
  66. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildAiCardPolicyFormDraftSeed.d.ts +0 -38
  67. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildAiCardPolicyFormDraftSeed.js +0 -391
  68. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildManualCardPolicyFormDraftSeed.d.ts +0 -11
  69. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/buildManualCardPolicyFormDraftSeed.js +0 -38
  70. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyChipIds.d.ts +0 -24
  71. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyChipIds.js +0 -40
  72. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyFormDraftTypes.d.ts +0 -253
  73. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyFormDraftTypes.js +0 -21
  74. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyRequestParts.d.ts +0 -66
  75. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/cardPolicyRequestParts.js +0 -151
  76. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/deriveAiPolicyReviewRowsFromInputs.d.ts +0 -30
  77. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/deriveAiPolicyReviewRowsFromInputs.js +0 -152
  78. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/seedAiCardPolicyFormDraftEpic.d.ts +0 -26
  79. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/seedAiCardPolicyFormDraftEpic.js +0 -116
  80. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toBulkCardPolicyTemplateRequestsFromDraft.d.ts +0 -35
  81. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toBulkCardPolicyTemplateRequestsFromDraft.js +0 -105
  82. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toCardPolicyTemplateRequestFromDraft.d.ts +0 -23
  83. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toCardPolicyTemplateRequestFromDraft.js +0 -75
  84. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toManualCardPolicyTemplateRequestFromDraft.d.ts +0 -15
  85. package/lib/view/spendManagement/chargeCards/cardPolicy/createCardPolicy/toManualCardPolicyTemplateRequestFromDraft.js +0 -38
  86. package/lib/view/spendManagement/chargeCards/issueChargeCard/aiCardCreationFormDraftTypes.d.ts +0 -76
  87. package/lib/view/spendManagement/chargeCards/issueChargeCard/aiCardCreationFormDraftTypes.js +0 -2
  88. package/lib/view/spendManagement/chargeCards/issueChargeCard/applyAiCardCreationFormDraftUpdate.d.ts +0 -7
  89. package/lib/view/spendManagement/chargeCards/issueChargeCard/applyAiCardCreationFormDraftUpdate.js +0 -46
  90. package/lib/view/spendManagement/chargeCards/issueChargeCard/buildAiCardCreationFormDraftSeed.d.ts +0 -28
  91. package/lib/view/spendManagement/chargeCards/issueChargeCard/buildAiCardCreationFormDraftSeed.js +0 -73
  92. package/lib/view/spendManagement/chargeCards/issueChargeCard/deriveAiCardRowsFromTeams.d.ts +0 -23
  93. package/lib/view/spendManagement/chargeCards/issueChargeCard/deriveAiCardRowsFromTeams.js +0 -49
  94. package/lib/view/spendManagement/chargeCards/issueChargeCard/seedAiCardCreationFormDraftEpic.d.ts +0 -11
  95. package/lib/view/spendManagement/chargeCards/issueChargeCard/seedAiCardCreationFormDraftEpic.js +0 -69
  96. package/lib/view/spendManagement/chargeCards/issueChargeCard/toIssueChargeCardLocalDataFromDraft.d.ts +0 -11
  97. package/lib/view/spendManagement/chargeCards/issueChargeCard/toIssueChargeCardLocalDataFromDraft.js +0 -68
@@ -1,65 +0,0 @@
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 { fetchUserListByType } from '../../../userListView/userListViewReducer';
6
- import { getUserList } from '../../../userListView/userListViewSelector';
7
- import { clearAiCardCreationFormDraft, issueChargeCardSuccessOrFailure, seedAiCardCreationFormDraft, } from './issueChargeCardReducer';
8
- const readAllUsersAsUserArray = (state) => getUserList(state.userState, state.userRoleState, state.userListViewState, 'cardHolderCandidate')
9
- .users.map((person) => person.user)
10
- .filter((user) => user != null);
11
- const readCardsCreationPayload = (state, chatSessionId, questionAnswerId) => {
12
- const qa = getQuestionAnswerByIdForChatSession(state.aiCfoState, chatSessionId, questionAnswerId);
13
- const visualization = qa?.responses.answer?.content.visualization;
14
- if (visualization?.type !== 'interactive_form') {
15
- return undefined;
16
- }
17
- const data = visualization.data;
18
- if (!('formType' in data) || data.formType !== 'cards_creation') {
19
- return undefined;
20
- }
21
- return data.payload;
22
- };
23
- // Pusher (`upsertAnswerPayload`) targets either an explicit
24
- // `questionAnswerId` or "latest QA in session"; REST
25
- // (`upsertOrAddQuestionAnswerPayload`) always carries the id. Resolve to
26
- // the QA the reducer just wrote so we can read the freshly-decoded
27
- // visualization off state.
28
- const resolveQuestionAnswerId = (state, action) => {
29
- if (action.payload.questionAnswerId != null) {
30
- return action.payload.questionAnswerId;
31
- }
32
- const session = state.aiCfoState.aiCfoByChatSessionId[action.payload.chatSessionId];
33
- return session?.questionAnswers.slice(-1)[0]?.questionAnswerId;
34
- };
35
- const isSeedTrigger = (action) => upsertAnswerPayload.match(action) ||
36
- upsertOrAddQuestionAnswerPayload.match(action);
37
- export const seedAiCardCreationFormDraftEpic = (actions$, state$) => actions$.pipe(mergeMap((action) => {
38
- if (issueChargeCardSuccessOrFailure.match(action) &&
39
- action.payload.fetchState === 'Completed') {
40
- return of(clearAiCardCreationFormDraft());
41
- }
42
- if (!isSeedTrigger(action)) {
43
- return EMPTY;
44
- }
45
- const state = state$.value;
46
- const questionAnswerId = resolveQuestionAnswerId(state, action);
47
- if (questionAnswerId == null) {
48
- return EMPTY;
49
- }
50
- const payload = readCardsCreationPayload(state, action.payload.chatSessionId, questionAnswerId);
51
- if (payload == null) {
52
- return EMPTY;
53
- }
54
- const allUsersNow = readAllUsersAsUserArray(state);
55
- if (allUsersNow.length > 0) {
56
- return of(seedAiCardCreationFormDraft(allUsersNow, payload));
57
- }
58
- const seedWhenUsersLoaded$ = state$.pipe(map(readAllUsersAsUserArray), distinctUntilChanged((prev, next) => prev.length === next.length &&
59
- prev.every((user, idx) => user === next[idx])), filter((users) => users.length > 0), take(1), map((users) => seedAiCardCreationFormDraft(users, payload)));
60
- const userListFetchState = state.userListViewState.userListByType.cardHolderCandidate.fetchState;
61
- if (userListFetchState === 'In-Progress') {
62
- return seedWhenUsersLoaded$;
63
- }
64
- return concat(of(fetchUserListByType('cardHolderCandidate', false)), seedWhenUsersLoaded$);
65
- }));
@@ -1,64 +0,0 @@
1
- /**
2
- * Default credit limit frequency emitted on the wire when the AI CFO
3
- * card creation form is submitted. The interactive form has no
4
- * frequency field today (v1 product decision: AI CFO only ever issues
5
- * monthly virtual credit cards).
6
- */
7
- const DEFAULT_FREQUENCY = 'monthly';
8
- const DEFAULT_CURRENCY_CODE = 'USD';
9
- const DEFAULT_CURRENCY_SYMBOL = '$';
10
- /**
11
- * Map a single AI card-creation row to the `IssueChargeCardLocalData`
12
- * shape consumed by `issueChargeCardEpic`.
13
- *
14
- * AI CFO only issues `business_virtual_credit_card`s in v1, so:
15
- * - `chargeCardType` is hard-coded to `'credit_card'`
16
- * - `creditCard.cardType` is hard-coded to `'business_virtual_credit_card'`
17
- * - the physicalCard / debitCard.* slots are populated with empty
18
- * defaults that the epic ignores.
19
- */
20
- const mapCardRow = (row) => {
21
- const ownerUserId = row.owner.selectedUser?.userId;
22
- return {
23
- lineId: row.id,
24
- chargeCardType: 'credit_card',
25
- creditCard: {
26
- cardType: 'business_virtual_credit_card',
27
- virtualCard: {
28
- cardName: row.label,
29
- frequency: DEFAULT_FREQUENCY,
30
- userIds: ownerUserId != null ? [ownerUserId] : [],
31
- amount: {
32
- amount: row.recommendedLimit ?? 0,
33
- currencyCode: DEFAULT_CURRENCY_CODE,
34
- currencySymbol: DEFAULT_CURRENCY_SYMBOL,
35
- },
36
- },
37
- physicalCard: {
38
- cardName: '',
39
- frequency: DEFAULT_FREQUENCY,
40
- },
41
- },
42
- debitCard: {
43
- cardType: 'business_virtual_debit_card',
44
- virtualCard: {
45
- cardName: '',
46
- frequency: DEFAULT_FREQUENCY,
47
- userIds: [],
48
- },
49
- physicalCard: {
50
- cardName: '',
51
- frequency: DEFAULT_FREQUENCY,
52
- },
53
- },
54
- };
55
- };
56
- /**
57
- * Convert a `CardsFormDraft` into the `IssueChargeCardLocalData[]`
58
- * payload `issueChargeCardEpic` expects in
59
- * `state.issueChargeCardState.localData`.
60
- *
61
- * Returns an empty array when `cardRows` is empty so callers can
62
- * short-circuit without dispatching a no-op bulk POST.
63
- */
64
- export const toIssueChargeCardLocalDataFromDraft = (draft) => draft.cardRows.map(mapCardRow);
@@ -1,38 +0,0 @@
1
- import { AiCardPolicyFormDraftSeedContext, CardPolicyCardSelectionRow, CardPolicyFormDraft, CardPolicyReviewRow } from './cardPolicyFormDraftTypes';
2
- export interface ApplyAiCardPolicyFormDraftUpdateArgs {
3
- context: AiCardPolicyFormDraftSeedContext | undefined;
4
- next: CardPolicyFormDraft;
5
- prev: CardPolicyFormDraft | undefined;
6
- }
7
- /**
8
- * Single source of truth for "what the next AI card-policy form draft
9
- * should look like after a `watchHandler` flush".
10
- *
11
- * Branches on `next.policyKind`:
12
- *
13
- * - `auto`: pass-through. Chip groups on `policiesReview` are the
14
- * canonical source of truth; the source slices stay empty.
15
- *
16
- * - `guided`: mirrors review-step chip edits (diffed against
17
- * `prev.policiesReview`) back into the source slices
18
- * (`vendorPolicy` / `categoryPolicy` / `cardSelections` / `limits`),
19
- * then re-derives `policiesReview` from the bridged source slices
20
- * via `deriveAiPolicyReviewRowsFromInputs` so the review always
21
- * reflects the freshest live state. Replaces the
22
- * `useReviewChipEditBridge` hook that previously lived in
23
- * `CardPolicyInteractiveForm`.
24
- *
25
- * - `upload`: passes through the user's chip edits on
26
- * `policiesReview` (the wire reads from those chip groups). The
27
- * "Applied to Cards" chip group is patched in-place from
28
- * `cardSelections` (the user's Step 2 picks).
29
- */
30
- export declare const applyAiCardPolicyFormDraftUpdate: ({ next, prev, context, }: ApplyAiCardPolicyFormDraftUpdateArgs) => CardPolicyFormDraft;
31
- /**
32
- * Replace (or insert) the "Applied to Cards" chip group on every
33
- * upload review row with avatar chips derived from the current
34
- * `cardSelections`. The new group is inserted before the "Limits"
35
- * group so the visual order stays Vendors → Categories → Cards →
36
- * Limits.
37
- */
38
- export declare const patchUploadReviewWithAppliedCards: (reviewRows: CardPolicyReviewRow[], cardSelections: CardPolicyCardSelectionRow[]) => CardPolicyReviewRow[];
@@ -1,333 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.patchUploadReviewWithAppliedCards = exports.applyAiCardPolicyFormDraftUpdate = void 0;
4
- const deriveAiPolicyReviewRowsFromInputs_1 = require("./deriveAiPolicyReviewRowsFromInputs");
5
- /**
6
- * Single source of truth for "what the next AI card-policy form draft
7
- * should look like after a `watchHandler` flush".
8
- *
9
- * Branches on `next.policyKind`:
10
- *
11
- * - `auto`: pass-through. Chip groups on `policiesReview` are the
12
- * canonical source of truth; the source slices stay empty.
13
- *
14
- * - `guided`: mirrors review-step chip edits (diffed against
15
- * `prev.policiesReview`) back into the source slices
16
- * (`vendorPolicy` / `categoryPolicy` / `cardSelections` / `limits`),
17
- * then re-derives `policiesReview` from the bridged source slices
18
- * via `deriveAiPolicyReviewRowsFromInputs` so the review always
19
- * reflects the freshest live state. Replaces the
20
- * `useReviewChipEditBridge` hook that previously lived in
21
- * `CardPolicyInteractiveForm`.
22
- *
23
- * - `upload`: passes through the user's chip edits on
24
- * `policiesReview` (the wire reads from those chip groups). The
25
- * "Applied to Cards" chip group is patched in-place from
26
- * `cardSelections` (the user's Step 2 picks).
27
- */
28
- const applyAiCardPolicyFormDraftUpdate = ({ next, prev, context, }) => {
29
- if (next.policyKind === 'auto') {
30
- return next;
31
- }
32
- if (next.policyKind === 'upload') {
33
- return applyUploadUpdate({ next, prev });
34
- }
35
- return applyGuidedUpdate({ next, prev, context });
36
- };
37
- exports.applyAiCardPolicyFormDraftUpdate = applyAiCardPolicyFormDraftUpdate;
38
- const applyGuidedUpdate = ({ next, prev, context, }) => {
39
- const bridged = bridgeGuidedPoliciesReviewToSources({ next, prev });
40
- if (context == null) {
41
- return bridged;
42
- }
43
- return {
44
- ...bridged,
45
- policiesReview: (0, deriveAiPolicyReviewRowsFromInputs_1.deriveAiPolicyReviewRowsFromInputs)({
46
- vendorPolicy: bridged.vendorPolicy,
47
- categoryPolicy: bridged.categoryPolicy,
48
- cardSelections: bridged.cardSelections,
49
- limits: bridged.limits,
50
- suggestedPoliciesSeed: context.suggestedPoliciesSeed,
51
- prevPoliciesReview: bridged.policiesReview,
52
- }),
53
- };
54
- };
55
- const APPLIED_TO_CARDS_GROUP_ID = 'applied-to-cards';
56
- const LIMITS_GROUP_ID = 'limits';
57
- const applyUploadUpdate = ({ next, prev, }) => {
58
- // Mirror review-side avatar / limits edits into source slices so the
59
- // bulk-submit builder (which reads `cardSelections` for upload's
60
- // applyToCards) sees the user's latest intent.
61
- const bridged = bridgeUploadPoliciesReviewToSources({ next, prev });
62
- if (!haveCardSelectionsChanged(prev?.cardSelections, bridged.cardSelections)) {
63
- return bridged;
64
- }
65
- return {
66
- ...bridged,
67
- policiesReview: (0, exports.patchUploadReviewWithAppliedCards)(bridged.policiesReview, bridged.cardSelections),
68
- };
69
- };
70
- const haveCardSelectionsChanged = (prevSelections, nextSelections) => {
71
- const prevCheckedIds = (prevSelections ?? [])
72
- .filter((row) => row.isChecked)
73
- .map((row) => row.id);
74
- const nextCheckedIds = nextSelections
75
- .filter((row) => row.isChecked)
76
- .map((row) => row.id);
77
- if (prevCheckedIds.length !== nextCheckedIds.length) {
78
- return true;
79
- }
80
- return prevCheckedIds.some((id, idx) => id !== nextCheckedIds[idx]);
81
- };
82
- const buildAppliedOwnerSummaries = (cardSelections) => {
83
- const summaries = new Map();
84
- for (const card of cardSelections) {
85
- if (!card.isChecked) {
86
- continue;
87
- }
88
- const existing = summaries.get(card.owner.userId);
89
- if (existing != null) {
90
- existing.cardIds.push(card.id);
91
- continue;
92
- }
93
- summaries.set(card.owner.userId, {
94
- userId: card.owner.userId,
95
- displayName: card.owner.displayName,
96
- cardIds: [card.id],
97
- });
98
- }
99
- return Array.from(summaries.values());
100
- };
101
- /**
102
- * Replace (or insert) the "Applied to Cards" chip group on every
103
- * upload review row with avatar chips derived from the current
104
- * `cardSelections`. The new group is inserted before the "Limits"
105
- * group so the visual order stays Vendors → Categories → Cards →
106
- * Limits.
107
- */
108
- const patchUploadReviewWithAppliedCards = (reviewRows, cardSelections) => {
109
- const owners = buildAppliedOwnerSummaries(cardSelections);
110
- const cardChips = owners.map((owner) => ({
111
- id: owner.userId,
112
- label: owner.displayName,
113
- chipKind: 'avatar',
114
- cardIds: owner.cardIds,
115
- isSelected: true,
116
- }));
117
- const cardsGroup = cardChips.length > 0
118
- ? {
119
- id: APPLIED_TO_CARDS_GROUP_ID,
120
- label: 'Applied to Cards',
121
- chips: cardChips,
122
- }
123
- : null;
124
- return reviewRows.map((row) => {
125
- const withoutCards = row.chipGroups.filter((group) => group.id !== APPLIED_TO_CARDS_GROUP_ID);
126
- if (cardsGroup == null) {
127
- return { ...row, chipGroups: withoutCards };
128
- }
129
- const limitsIdx = withoutCards.findIndex((group) => group.id === LIMITS_GROUP_ID);
130
- if (limitsIdx === -1) {
131
- return { ...row, chipGroups: [...withoutCards, cardsGroup] };
132
- }
133
- const before = withoutCards.slice(0, limitsIdx);
134
- const after = withoutCards.slice(limitsIdx);
135
- return { ...row, chipGroups: [...before, cardsGroup, ...after] };
136
- });
137
- };
138
- exports.patchUploadReviewWithAppliedCards = patchUploadReviewWithAppliedCards;
139
- /**
140
- * Mirror diffs in `next.policiesReview` chips back into the source
141
- * slices for guided mode. Detects per-chip `isSelected` and
142
- * `optionalCode` changes vs `prev` and writes them to:
143
- * - `limits` for `kind: "limits"` group chips,
144
- * - `cardSelections` for avatar chips, and
145
- * - `vendorPolicy` / `categoryPolicy` for positive/negative chips.
146
- */
147
- const bridgeGuidedPoliciesReviewToSources = ({ next, prev, }) => {
148
- if (prev == null || prev.policiesReview.length === 0) {
149
- return next;
150
- }
151
- const reviewDiffs = collectReviewChipDiffs(prev.policiesReview, next.policiesReview);
152
- if (reviewDiffs.length === 0) {
153
- return next;
154
- }
155
- let limits = next.limits;
156
- let cardSelections = next.cardSelections;
157
- let vendorPolicy = next.vendorPolicy;
158
- let categoryPolicy = next.categoryPolicy;
159
- for (const diff of reviewDiffs) {
160
- if (diff.groupKind === 'limits' || diff.chip.id.startsWith('limit-')) {
161
- limits = applyLimitChipDiffToLimits(limits, diff);
162
- continue;
163
- }
164
- if (diff.chip.chipKind === 'avatar') {
165
- cardSelections = applyAvatarChipDiffToCardSelections(cardSelections, diff);
166
- continue;
167
- }
168
- if (diff.changedFields.has('isSelected')) {
169
- const writtenToVendor = tryWriteAllowBlock(vendorPolicy, diff);
170
- if (writtenToVendor != null) {
171
- vendorPolicy = writtenToVendor;
172
- continue;
173
- }
174
- const writtenToCategory = tryWriteAllowBlock(categoryPolicy, diff);
175
- if (writtenToCategory != null) {
176
- categoryPolicy = writtenToCategory;
177
- }
178
- }
179
- }
180
- return {
181
- ...next,
182
- limits,
183
- cardSelections,
184
- vendorPolicy,
185
- categoryPolicy,
186
- };
187
- };
188
- /**
189
- * Upload-mode bridge — narrower than guided. Vendor / category chips in
190
- * the review are the wire's source of truth (no source slice to write
191
- * back to); avatar chips mirror into `cardSelections` (the wire's
192
- * `applyToCards` projection for upload reads from there); limit chips
193
- * are read from the review chip group directly by the wire, so no
194
- * mirror is needed.
195
- */
196
- const bridgeUploadPoliciesReviewToSources = ({ next, prev, }) => {
197
- if (prev == null || prev.policiesReview.length === 0) {
198
- return next;
199
- }
200
- const reviewDiffs = collectReviewChipDiffs(prev.policiesReview, next.policiesReview);
201
- if (reviewDiffs.length === 0) {
202
- return next;
203
- }
204
- let cardSelections = next.cardSelections;
205
- for (const diff of reviewDiffs) {
206
- if (diff.chip.chipKind === 'avatar') {
207
- cardSelections = applyAvatarChipDiffToCardSelections(cardSelections, diff);
208
- }
209
- }
210
- if (cardSelections === next.cardSelections) {
211
- return next;
212
- }
213
- return { ...next, cardSelections };
214
- };
215
- const collectReviewChipDiffs = (prevRows, nextRows) => {
216
- const prevById = new Map(prevRows.map((row) => [row.id, row]));
217
- const diffs = [];
218
- for (const row of nextRows) {
219
- const prevRow = prevById.get(row.id);
220
- if (prevRow == null) {
221
- continue;
222
- }
223
- const prevGroupsById = new Map(prevRow.chipGroups.map((group) => [group.id, group]));
224
- for (const group of row.chipGroups) {
225
- const prevGroup = prevGroupsById.get(group.id);
226
- if (prevGroup == null) {
227
- continue;
228
- }
229
- const prevChipsById = new Map(prevGroup.chips.map((chip) => [chip.id, chip]));
230
- for (const chip of group.chips) {
231
- const prevChip = prevChipsById.get(chip.id);
232
- if (prevChip == null) {
233
- continue;
234
- }
235
- const changed = new Set();
236
- if (chip.isSelected !== prevChip.isSelected) {
237
- changed.add('isSelected');
238
- }
239
- if (chip.optionalCode !== prevChip.optionalCode) {
240
- changed.add('optionalCode');
241
- }
242
- if (changed.size > 0) {
243
- diffs.push({ chip, changedFields: changed, groupKind: group.kind });
244
- }
245
- }
246
- }
247
- }
248
- return diffs;
249
- };
250
- const REVIEW_LIMIT_CHIP_ID_TO_LIMIT_ROW_ID = {
251
- 'limit-transaction': 'transactionLimit',
252
- 'limit-receipt': 'requireReceipt',
253
- };
254
- const applyLimitChipDiffToLimits = (limits, diff) => {
255
- const mapped = REVIEW_LIMIT_CHIP_ID_TO_LIMIT_ROW_ID[diff.chip.id];
256
- const directMatch = limits.find((row) => row.id === diff.chip.id) != null;
257
- const rowId = mapped ?? (directMatch ? diff.chip.id : undefined);
258
- if (rowId == null) {
259
- return limits;
260
- }
261
- const idx = limits.findIndex((row) => row.id === rowId);
262
- if (idx === -1) {
263
- return limits;
264
- }
265
- let nextRow = limits[idx];
266
- if (diff.changedFields.has('isSelected')) {
267
- nextRow = { ...nextRow, isEnabled: diff.chip.isSelected === true };
268
- }
269
- if (diff.changedFields.has('optionalCode')) {
270
- const raw = diff.chip.optionalCode;
271
- const numericNext = raw != null && raw !== '' ? Number(raw) : undefined;
272
- nextRow = { ...nextRow, amount: numericNext };
273
- }
274
- if (nextRow === limits[idx]) {
275
- return limits;
276
- }
277
- const copy = limits.slice();
278
- copy[idx] = nextRow;
279
- return copy;
280
- };
281
- const applyAvatarChipDiffToCardSelections = (cardSelections, diff) => {
282
- if (!diff.changedFields.has('isSelected')) {
283
- return cardSelections;
284
- }
285
- const targetIds = diff.chip.cardIds != null && diff.chip.cardIds.length > 0
286
- ? diff.chip.cardIds
287
- : [diff.chip.id];
288
- const targetSet = new Set(targetIds);
289
- let mutated = false;
290
- const next = cardSelections.map((row) => {
291
- if (!targetSet.has(row.id)) {
292
- return row;
293
- }
294
- const desired = diff.chip.isSelected === true;
295
- if (row.isChecked === desired) {
296
- return row;
297
- }
298
- mutated = true;
299
- return { ...row, isChecked: desired };
300
- });
301
- return mutated ? next : cardSelections;
302
- };
303
- const tryWriteAllowBlock = (field, diff) => {
304
- const desired = diff.chip.isSelected === true;
305
- const written = tryFlipItemInSide(field.allowItems, diff, desired);
306
- if (written != null) {
307
- return { ...field, allowItems: written };
308
- }
309
- const writtenBlock = tryFlipItemInSide(field.blockItems, diff, desired);
310
- if (writtenBlock != null) {
311
- return { ...field, blockItems: writtenBlock };
312
- }
313
- return null;
314
- };
315
- const tryFlipItemInSide = (items, diff, desired) => {
316
- let idx = items.findIndex((item) => item.id === diff.chip.id);
317
- if (idx === -1 && diff.chip.optionalCode != null) {
318
- // Auto / upload mode category chips use the raw MCC code as
319
- // `chip.id` while the source items use `mcc-{name}`; fall back to
320
- // matching by code so the bridge round-trips both shapes.
321
- idx = items.findIndex((item) => item.optionalCode === diff.chip.optionalCode ||
322
- (item.optionalCodes ?? []).includes(diff.chip.optionalCode ?? ''));
323
- }
324
- if (idx === -1) {
325
- return null;
326
- }
327
- if (items[idx].isSelected === desired) {
328
- return items;
329
- }
330
- const copy = items.slice();
331
- copy[idx] = { ...items[idx], isSelected: desired };
332
- return copy;
333
- };
@@ -1,25 +0,0 @@
1
- import { CardPolicyChipFieldValue, CardPolicyFormDraft, ExtractedCardPolicyDataView } from './cardPolicyFormDraftTypes';
2
- export interface ApplyExtractedPolicyToAiCardPolicyDraftArgs {
3
- /** Category universe the chip picker can offer (search dropdown contents). */
4
- categorySearchOptions: CardPolicyChipFieldValue[];
5
- /** Current draft — preserved for slices the response does not cover. */
6
- current: CardPolicyFormDraft;
7
- /** The extracted rules returned by the cards micro-service. */
8
- extracted: ExtractedCardPolicyDataView;
9
- /** Vendor universe the chip picker can offer. */
10
- vendorSearchOptions: CardPolicyChipFieldValue[];
11
- }
12
- /**
13
- * Apply an extracted policy document response to the AI CFO card-policy
14
- * draft. Touches only the slices the extract API populates:
15
- * - `vendorPolicy` from allow/block `merchantNames` (with allow-wins
16
- * flip).
17
- * - `categoryPolicy` from allow/block `categoryCodes` (matched on
18
- * `optionalCode`).
19
- * - `limits` rows from `transactionLimit` / `requiredReceiptThreshold`.
20
- *
21
- * `cardSelections` and `policiesReview` are preserved verbatim — the
22
- * user still picks cards on Step 2 and the review step is seeded
23
- * separately.
24
- */
25
- export declare const applyExtractedPolicyToAiCardPolicyDraft: ({ extracted, current, vendorSearchOptions, categorySearchOptions, }: ApplyExtractedPolicyToAiCardPolicyDraftArgs) => CardPolicyFormDraft;
@@ -1,124 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.applyExtractedPolicyToAiCardPolicyDraft = void 0;
4
- const cardPolicyChipIds_1 = require("./cardPolicyChipIds");
5
- const cardPolicyFormDraftTypes_1 = require("./cardPolicyFormDraftTypes");
6
- /**
7
- * Apply an extracted policy document response to the AI CFO card-policy
8
- * draft. Touches only the slices the extract API populates:
9
- * - `vendorPolicy` from allow/block `merchantNames` (with allow-wins
10
- * flip).
11
- * - `categoryPolicy` from allow/block `categoryCodes` (matched on
12
- * `optionalCode`).
13
- * - `limits` rows from `transactionLimit` / `requiredReceiptThreshold`.
14
- *
15
- * `cardSelections` and `policiesReview` are preserved verbatim — the
16
- * user still picks cards on Step 2 and the review step is seeded
17
- * separately.
18
- */
19
- const applyExtractedPolicyToAiCardPolicyDraft = ({ extracted, current, vendorSearchOptions, categorySearchOptions, }) => ({
20
- ...current,
21
- vendorPolicy: buildAllowBlockField({
22
- allowList: extracted.allowedEntity.merchantNames,
23
- blockList: extracted.blockedEntity.merchantNames,
24
- currentAllowItems: current.vendorPolicy.allowItems,
25
- currentBlockItems: current.vendorPolicy.blockItems,
26
- universe: vendorSearchOptions,
27
- matchOnCode: false,
28
- }),
29
- categoryPolicy: buildAllowBlockField({
30
- allowList: extracted.allowedEntity.categoryCodes,
31
- blockList: extracted.blockedEntity.categoryCodes,
32
- currentAllowItems: current.categoryPolicy.allowItems,
33
- currentBlockItems: current.categoryPolicy.blockItems,
34
- universe: categorySearchOptions,
35
- matchOnCode: true,
36
- }),
37
- limits: buildLimitRows({
38
- current: current.limits,
39
- transactionLimit: extracted.transactionLimit,
40
- requiredReceiptThreshold: extracted.requiredReceiptThreshold,
41
- }),
42
- });
43
- exports.applyExtractedPolicyToAiCardPolicyDraft = applyExtractedPolicyToAiCardPolicyDraft;
44
- const buildAllowBlockField = ({ allowList, blockList, currentAllowItems, currentBlockItems, universe, matchOnCode, }) => {
45
- const allowItems = buildSideItems({
46
- selectedValues: allowList,
47
- currentItems: currentAllowItems,
48
- universe,
49
- matchOnCode,
50
- });
51
- const blockItems = buildSideItems({
52
- selectedValues: blockList,
53
- currentItems: currentBlockItems,
54
- universe,
55
- matchOnCode,
56
- });
57
- const mode = allowList.length === 0 && blockList.length > 0 ? 'block' : 'allow';
58
- return { mode, allowItems, blockItems };
59
- };
60
- const buildSideItems = ({ selectedValues, currentItems, universe, matchOnCode, }) => {
61
- const selectedKeys = new Set(selectedValues
62
- .map((value) => normalizeKey(value))
63
- .filter((key) => key != null));
64
- const items = [];
65
- const seenIds = new Set();
66
- for (const item of currentItems) {
67
- const key = normalizeKey(matchOnCode ? item.optionalCode : item.label);
68
- const isSelected = key != null && selectedKeys.has(key);
69
- items.push({ ...item, isSelected });
70
- seenIds.add(item.id);
71
- if (isSelected && key != null) {
72
- selectedKeys.delete(key);
73
- }
74
- }
75
- for (const option of universe) {
76
- if (selectedKeys.size === 0) {
77
- break;
78
- }
79
- const key = normalizeKey(matchOnCode ? option.optionalCode : option.label);
80
- if (key == null || !selectedKeys.has(key) || seenIds.has(option.id)) {
81
- continue;
82
- }
83
- items.push({ ...option, isSelected: true });
84
- seenIds.add(option.id);
85
- selectedKeys.delete(key);
86
- }
87
- for (const value of selectedValues) {
88
- const key = normalizeKey(value);
89
- if (key == null || !selectedKeys.has(key)) {
90
- continue;
91
- }
92
- const id = matchOnCode
93
- ? (0, cardPolicyChipIds_1.buildMccCategoryChipId)(value)
94
- : (0, cardPolicyChipIds_1.buildVendorChipId)(value);
95
- if (seenIds.has(id)) {
96
- continue;
97
- }
98
- items.push({
99
- id,
100
- label: value,
101
- isSelected: true,
102
- ...(matchOnCode ? { optionalCode: value } : {}),
103
- });
104
- seenIds.add(id);
105
- selectedKeys.delete(key);
106
- }
107
- return items;
108
- };
109
- const buildLimitRows = ({ current, transactionLimit, requiredReceiptThreshold, }) => current.map((row) => {
110
- if (row.id === cardPolicyFormDraftTypes_1.CARD_POLICY_LIMIT_ROW_ID_TRANSACTION) {
111
- return amountToLimitRow(row, transactionLimit);
112
- }
113
- if (row.id === cardPolicyFormDraftTypes_1.CARD_POLICY_LIMIT_ROW_ID_REQUIRE_RECEIPT) {
114
- return amountToLimitRow(row, requiredReceiptThreshold);
115
- }
116
- return row;
117
- });
118
- const amountToLimitRow = (row, amount) => {
119
- if (amount > 0) {
120
- return { ...row, isEnabled: true, amount };
121
- }
122
- return { ...row, isEnabled: false, amount: row.amount };
123
- };
124
- const normalizeKey = (value) => value == null || value.length === 0 ? undefined : value.toLowerCase().trim();
@@ -1,28 +0,0 @@
1
- import { ActionsObservable, StateObservable } from 'redux-observable';
2
- import { Observable } from 'rxjs';
3
- import { updatePolicyDocumentExtractionSuccess } from '../../../../../entity/cardPolicy/cardPolicyReducer';
4
- import { RootState } from '../../../../../reducer';
5
- import { applyExtractedPolicyToAiCardPolicyDraft, applyExtractedPolicyToManualCardPolicyDraft } from './createCardPolicyReducer';
6
- export type ActionType = ReturnType<typeof applyExtractedPolicyToAiCardPolicyDraft> | ReturnType<typeof applyExtractedPolicyToManualCardPolicyDraft>;
7
- type InboundAction = ReturnType<typeof updatePolicyDocumentExtractionSuccess>;
8
- /**
9
- * Bridge epic — fans `updatePolicyDocumentExtractionSuccess` out to the
10
- * right card-policy form draft based on which surface kicked the upload
11
- * off:
12
- *
13
- * - `aiCfoAnswerId != null` → the AI CFO upload-mode wizard owns it;
14
- * dispatch `applyExtractedPolicyToAiCardPolicyDraft` so the AI draft
15
- * picks up the extracted allow/block + limits. The wizard's `step_5`
16
- * review chips are separately patched by
17
- * `policyRecommendationFromUploadEpic` (the second hop in the chain).
18
- * - `aiCfoAnswerId == null` → the manual `CardPolicyCreatePage` owns
19
- * it; dispatch `applyExtractedPolicyToManualCardPolicyDraft` so the
20
- * manual draft picks up name + allow/block + limits.
21
- *
22
- * The bridge reads vendor + category universes off CES
23
- * (`cardPolicyState.vendorSearch.vendors`,
24
- * `cardPolicyState.mccCategories`) instead of taking them through the
25
- * payload, so any caller that drops the action wires up automatically.
26
- */
27
- export declare const applyExtractedPolicyToDraftEpic: (actions$: ActionsObservable<InboundAction>, state$: StateObservable<RootState>) => Observable<ActionType>;
28
- export {};