@vtex/faststore-plugin-buyer-portal 2.0.7 → 2.0.9
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/.github/workflows/release.yaml +4 -4
- package/AGENTS.md +6 -0
- package/CHANGELOG.md +30 -1
- package/docs/superpowers/plans/2026-06-26-contracts-listing-selection-search-pagination.md +1070 -0
- package/docs/superpowers/specs/2026-06-26-contracts-listing-selection-search-pagination-design.md +157 -0
- package/package.json +1 -1
- package/plugin.config.js +8 -0
- package/public/buyer-portal-icons.svg +37 -1
- package/specs/001-suma-phase-2/checklists/requirements.md +36 -0
- package/specs/001-suma-phase-2/contracts/contracts-api.md +187 -0
- package/specs/001-suma-phase-2/contracts/feature-flag.md +91 -0
- package/specs/001-suma-phase-2/data-model.md +119 -0
- package/specs/001-suma-phase-2/plan.md +123 -0
- package/specs/001-suma-phase-2/quickstart.md +156 -0
- package/specs/001-suma-phase-2/research.md +150 -0
- package/specs/001-suma-phase-2/spec.md +186 -0
- package/specs/001-suma-phase-2/tasks.md +253 -0
- package/specs/suma-add-contracts.md +224 -0
- package/specs/suma-contract-listing.md +295 -0
- package/specs/suma-contract-switching.md +201 -0
- package/specs/suma-remove-contracts.md +233 -0
- package/specs/suma-set-default-contract.md +208 -0
- package/src/features/budgets/layouts/BudgetsLayout/BudgetsLayout.tsx +113 -105
- package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/BasicBuyingPolicyDrawer.tsx +2 -2
- package/src/features/buying-policies/layouts/BuyingPoliciesLayout/BuyingPoliciesLayout.tsx +60 -46
- package/src/features/contracts/clients/ContractsClient.ts +45 -0
- package/src/features/contracts/components/AddContractsDrawer/AddContractsDrawer.tsx +323 -0
- package/src/features/contracts/components/AddContractsDrawer/add-contracts-drawer.scss +282 -0
- package/src/features/contracts/components/ConfirmRemoveDrawer/ConfirmRemoveDrawer.tsx +58 -0
- package/src/features/contracts/components/ConfirmRemoveDrawer/confirm-remove-drawer.scss +18 -0
- package/src/features/contracts/components/ContractListItem/ContractListItem.tsx +120 -0
- package/src/features/contracts/components/ContractSelectAllRow/ContractSelectAllRow.tsx +37 -0
- package/src/features/contracts/components/ContractSelectAllRow/index.ts +2 -0
- package/src/features/contracts/components/ContractSelectionBar/ContractSelectionBar.tsx +47 -0
- package/src/features/contracts/components/ContractSelectionBar/index.ts +2 -0
- package/src/features/contracts/components/ContractSelectionList/ContractSelectionList.tsx +55 -0
- package/src/features/contracts/components/ContractSwitchOverlay/ContractSwitchOverlay.tsx +36 -0
- package/src/features/contracts/components/ContractSwitchOverlay/contract-switch-overlay.scss +7 -0
- package/src/features/contracts/components/ContractsListHeader/ContractsListHeader.tsx +58 -0
- package/src/features/contracts/components/ContractsListHeader/index.ts +2 -0
- package/src/features/contracts/components/ErrorRemoveDrawer/ErrorRemoveDrawer.tsx +69 -0
- package/src/features/contracts/components/UnableToRemoveContractsDrawer/UnableToRemoveContractsDrawer.tsx +52 -0
- package/src/features/contracts/components/UnableToRemoveContractsDrawer/unable-to-remove-contracts-drawer.scss +29 -0
- package/src/features/contracts/components/index.ts +14 -0
- package/src/features/contracts/hooks/__tests__/contractListingHelpers.test.ts +71 -0
- package/src/features/contracts/hooks/__tests__/contractSwitchChannelHelpers.test.ts +32 -0
- package/src/features/contracts/hooks/__tests__/useAddContracts.test.ts +63 -0
- package/src/features/contracts/hooks/__tests__/useContractSelection.test.ts +72 -0
- package/src/features/contracts/hooks/__tests__/useRemoveContracts.test.ts +130 -0
- package/src/features/contracts/hooks/__tests__/useSetDefaultContract.test.ts +82 -0
- package/src/features/contracts/hooks/contractListingHelpers.ts +45 -0
- package/src/features/contracts/hooks/contractSelectionHelpers.ts +28 -0
- package/src/features/contracts/hooks/contractSwitchChannelHelpers.ts +9 -0
- package/src/features/contracts/hooks/index.ts +19 -0
- package/src/features/contracts/hooks/useAddContracts.ts +27 -0
- package/src/features/contracts/hooks/useContractListing.ts +71 -0
- package/src/features/contracts/hooks/useContractSelection.ts +53 -0
- package/src/features/contracts/hooks/useContractSwitchChannel.ts +49 -0
- package/src/features/contracts/hooks/useListAvailableContracts.ts +27 -0
- package/src/features/contracts/hooks/useRemoveContracts.ts +21 -0
- package/src/features/contracts/hooks/useSetDefaultContract.ts +21 -0
- package/src/features/contracts/hooks/useSwitchContract.ts +76 -0
- package/src/features/contracts/layouts/ContractInformationLayout/ContractInformationLayout.tsx +316 -0
- package/src/features/contracts/layouts/ContractListingLayout/ContractListingLayout.tsx +415 -0
- package/src/features/contracts/layouts/ContractListingLayout/ContractSettingsNav.tsx +83 -0
- package/src/features/contracts/layouts/ContractListingLayout/contract-listing-layout.scss +468 -0
- package/src/features/contracts/layouts/ContractListingLayout/utils.ts +4 -0
- package/src/features/contracts/layouts/index.ts +8 -0
- package/src/features/contracts/services/__tests__/contracts-client.test.ts +148 -0
- package/src/features/contracts/services/__tests__/list-attached-contracts.service.test.ts +95 -0
- package/src/features/contracts/services/add-contracts.service.ts +27 -0
- package/src/features/contracts/services/index.ts +24 -0
- package/src/features/contracts/services/list-attached-contracts.service.ts +40 -0
- package/src/features/contracts/services/list-available-contracts.service.ts +32 -0
- package/src/features/contracts/services/remove-contract.service.ts +27 -0
- package/src/features/contracts/services/remove-contracts.service.ts +36 -0
- package/src/features/contracts/services/set-default-contract.service.ts +27 -0
- package/src/features/contracts/types/ContractData.ts +1 -0
- package/src/features/contracts/utils/__tests__/changeContractToken.test.ts +141 -0
- package/src/features/contracts/utils/changeContractToken.ts +122 -0
- package/src/features/contracts/utils/clearPersistedSessionState.ts +74 -0
- package/src/features/org-units/components/OrgUnitDetailsNavbar/OrgUnitDetailsNavbar.tsx +45 -3
- package/src/features/org-units/components/OrgUnitDetailsNavbar/org-unit-details-navbar.scss +35 -0
- package/src/features/org-units/layouts/OrgUnitsLayout/OrgUnitsLayout.tsx +54 -48
- package/src/features/roles/layout/RolesLayout/RolesLayout.tsx +32 -23
- package/src/features/shared/components/BuyerPortalProvider/BuyerPortalProvider.tsx +1 -0
- package/src/features/shared/components/HeaderInside/HeaderInside.tsx +8 -1
- package/src/features/shared/components/HeaderInside/header-inside.scss +9 -0
- package/src/features/shared/layouts/BaseTabsLayout/SidebarMenu.tsx +6 -97
- package/src/features/shared/layouts/ContractTabsLayout/ContractTabsLayout.tsx +42 -5
- package/src/features/shared/layouts/LoadingTabsLayout/LoadingTabsLayout.tsx +1 -1
- package/src/features/shared/layouts/SumaPageLayout/FullSidebarNav.tsx +124 -0
- package/src/features/shared/layouts/SumaPageLayout/SumaPageLayout.tsx +56 -0
- package/src/features/shared/layouts/SumaPageLayout/SumaSidebar.tsx +80 -0
- package/src/features/shared/layouts/SumaPageLayout/SumaSidebarDrawer.tsx +48 -0
- package/src/features/shared/layouts/SumaPageLayout/index.ts +6 -0
- package/src/features/shared/layouts/SumaPageLayout/suma-sidebar-drawer.scss +37 -0
- package/src/features/shared/layouts/index.ts +6 -0
- package/src/features/shared/services/feature-flags/__tests__/get-feature-flags.service.test.ts +71 -0
- package/src/features/shared/services/feature-flags/get-feature-flags.service.ts +1 -0
- package/src/features/shared/utils/buyerPortalRoutes.ts +9 -0
- package/src/features/shared/utils/constants.ts +1 -1
- package/src/features/shared/utils/getSumaListingSidebarLinks.ts +18 -0
- package/src/features/shared/utils/index.ts +1 -0
- package/src/features/users/layouts/UsersLayout/UsersLayout.tsx +97 -103
- package/src/pages/contract-information.tsx +128 -0
- package/src/pages/contracts.tsx +104 -0
- package/src/pages/home.tsx +10 -6
- package/src/pages/org-unit-details.tsx +17 -13
- package/src/themes/layouts.scss +2 -0
- package/.agents/skills/create-page/SKILL.md +0 -154
- package/.agents/skills/create-page/metadata.json +0 -14
- package/.specify/memory/constitution.md +0 -83
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Implementation Plan: SUMA Phase 2 — Contract Listing, Switching & Management
|
|
2
|
+
|
|
3
|
+
**Branch**: `feat/suma` | **Date**: 2026-06-15 | **Spec**: [spec.md](./spec.md)
|
|
4
|
+
|
|
5
|
+
**Input**: Feature specification from `specs/001-suma-phase-2/spec.md`
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
Add a Contract Listing page as the new account homepage (behind a feature flag) that lets buyers view all their Organizational Unit's contracts, switch the active contract via BFF integration, and supports multi-select with keyboard shortcuts. Admin users additionally get add/remove/set-default management flows with optional child-unit propagation. Cross-tab state synchronization is handled via BroadcastChannel. All new code lives in `src/features/contracts/` following the existing domain structure; two new pages are registered in `plugin.config.js`.
|
|
10
|
+
|
|
11
|
+
## Technical Context
|
|
12
|
+
|
|
13
|
+
**Language/Version**: TypeScript (strict mode) / Next.js (FastStore plugin — no standalone dev server)
|
|
14
|
+
|
|
15
|
+
**Primary Dependencies**: `@faststore/ui` (Toggle, Dropdown, Drawer patterns), `next/router` for navigation, React hooks for state and side effects
|
|
16
|
+
|
|
17
|
+
**Storage**: N/A — plugin is stateless; BFF owns persistence; active contract session is managed externally by the Identity/Auth team
|
|
18
|
+
|
|
19
|
+
**Testing**: Vitest (unit tests for hooks, services, pure functions), Cypress (E2E for contract switch happy path)
|
|
20
|
+
|
|
21
|
+
**Target Platform**: Browser — plugin consumed via `yarn link` in a FastStore host store (`b2bfaststoredev.store`)
|
|
22
|
+
|
|
23
|
+
**Project Type**: FastStore plugin (not a standalone web application)
|
|
24
|
+
|
|
25
|
+
**Performance Goals**: Contract switch end-to-end < 10 seconds; loading indicator visible < 500ms after click; admin actions confirmed < 5 seconds; cross-tab sync < 2 seconds
|
|
26
|
+
|
|
27
|
+
**Constraints**: Must not break host store build; TypeScript strict mode (`tsc --noEmit` zero errors); no cross-domain imports (only via `shared/`); zero regressions when feature flag is inactive; all new pages registered in `plugin.config.js` and `buyerPortalRoutes.ts`
|
|
28
|
+
|
|
29
|
+
**Scale/Scope**: All buyer portal users across any org unit; typical org unit has tens of contracts; no pagination required for MVP (BFF returns all contracts)
|
|
30
|
+
|
|
31
|
+
## Constitution Check
|
|
32
|
+
|
|
33
|
+
*GATE: Must pass before Phase 0 research. Re-checked post-design.*
|
|
34
|
+
|
|
35
|
+
| Principle | Status | Notes |
|
|
36
|
+
|-----------|--------|-------|
|
|
37
|
+
| I. Plugin Encapsulation | ✅ PASS | New pages registered in `plugin.config.js`; routes in `buyerPortalRoutes.ts`; no host-store coupling |
|
|
38
|
+
| II. Feature-per-Domain | ✅ PASS | All new code in `src/features/contracts/`; cross-domain (Org Unit selector) goes through `shared/` |
|
|
39
|
+
| III. Type Safety | ✅ PASS | No `any` usage; strict TypeScript throughout; `ContractData` type extended if needed |
|
|
40
|
+
| IV. Test Coverage | ✅ PASS | Unit tests for selection hook, activation hook, service wrappers; E2E for P1 contract switch flow |
|
|
41
|
+
| V. Spec-Driven | ✅ PASS | This plan follows the approved spec |
|
|
42
|
+
|
|
43
|
+
No violations → Complexity Tracking section not needed.
|
|
44
|
+
|
|
45
|
+
## Project Structure
|
|
46
|
+
|
|
47
|
+
### Documentation (this feature)
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
specs/001-suma-phase-2/
|
|
51
|
+
├── plan.md # This file
|
|
52
|
+
├── research.md # Phase 0 output
|
|
53
|
+
├── data-model.md # Phase 1 output
|
|
54
|
+
├── quickstart.md # Phase 1 output
|
|
55
|
+
├── contracts/ # Phase 1 output (BFF API contracts)
|
|
56
|
+
│ ├── contracts-api.md
|
|
57
|
+
│ └── feature-flag.md
|
|
58
|
+
├── checklists/
|
|
59
|
+
│ └── requirements.md
|
|
60
|
+
└── tasks.md # Phase 2 output (/speckit-tasks command)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Source Code (repository root)
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
src/
|
|
67
|
+
├── pages/
|
|
68
|
+
│ ├── home.tsx # MODIFIED: conditional redirect (flag on → contracts)
|
|
69
|
+
│ ├── contracts.tsx # NEW: Contract Listing page
|
|
70
|
+
│ └── contract-information.tsx # NEW: Contract Information page (active contract home)
|
|
71
|
+
│
|
|
72
|
+
└── features/
|
|
73
|
+
└── contracts/
|
|
74
|
+
├── clients/
|
|
75
|
+
│ └── ContractsClient.ts # MODIFIED: add BFF methods (add, remove, set-default, activate)
|
|
76
|
+
│
|
|
77
|
+
├── components/
|
|
78
|
+
│ ├── ContractsCard/ # EXISTING (no change)
|
|
79
|
+
│ ├── ContractListItem/ # NEW: single row in listing (name, default badge, selection state)
|
|
80
|
+
│ ├── ContractSelectionList/ # NEW: full list with multi-select + keyboard shortcuts
|
|
81
|
+
│ ├── AddContractsDrawer/ # NEW: modal/drawer to add one/many/all contracts
|
|
82
|
+
│ ├── RemoveContractsDrawer/ # NEW: confirmation + block for last contract
|
|
83
|
+
│ └── SetDefaultContractAction/ # NEW: trigger to mark contract as default
|
|
84
|
+
│
|
|
85
|
+
├── hooks/
|
|
86
|
+
│ ├── useUpdateContractStatus.ts # EXISTING (no change)
|
|
87
|
+
│ ├── useActivateContract.ts # NEW: activate contract via BFF + navigate
|
|
88
|
+
│ ├── useContractSelection.ts # NEW: multi-select state + keyboard shortcuts
|
|
89
|
+
│ ├── useAddContracts.ts # NEW: add contracts mutation
|
|
90
|
+
│ ├── useRemoveContracts.ts # NEW: remove contracts mutation
|
|
91
|
+
│ ├── useSetDefaultContract.ts # NEW: set default mutation
|
|
92
|
+
│ └── useCrossTabContractSync.ts # NEW: BroadcastChannel listener + router.refresh
|
|
93
|
+
│
|
|
94
|
+
├── layouts/
|
|
95
|
+
│ ├── ContractsLayout/ # EXISTING (repurposed — becomes base for old toggle view)
|
|
96
|
+
│ ├── ContractListingLayout/ # NEW: Contract Listing page layout (new homepage)
|
|
97
|
+
│ └── ContractInformationLayout/ # NEW: Contract Information page layout
|
|
98
|
+
│
|
|
99
|
+
├── services/
|
|
100
|
+
│ ├── get-contracts-org-by-unit-id.service.ts # EXISTING (no change)
|
|
101
|
+
│ ├── get-contract-details.service.ts # EXISTING (no change)
|
|
102
|
+
│ ├── update-contract-status.service.ts # EXISTING (no change)
|
|
103
|
+
│ ├── activate-contract.service.ts # NEW: BFF activate (distinct from toggle)
|
|
104
|
+
│ ├── add-contracts.service.ts # NEW
|
|
105
|
+
│ ├── remove-contracts.service.ts # NEW
|
|
106
|
+
│ └── set-default-contract.service.ts # NEW
|
|
107
|
+
│
|
|
108
|
+
└── types/
|
|
109
|
+
├── ContractData.ts # MODIFIED: add `isDefault` boolean field
|
|
110
|
+
└── index.ts
|
|
111
|
+
|
|
112
|
+
└── shared/
|
|
113
|
+
├── components/
|
|
114
|
+
│ └── BuyerPortalProvider/
|
|
115
|
+
│ └── BuyerPortalProvider.tsx # MODIFIED: add `suma` to FeatureFlags type
|
|
116
|
+
└── utils/
|
|
117
|
+
├── buyerPortalRoutes.ts # MODIFIED: add `contracts` and `contractInformation` routes
|
|
118
|
+
└── constants.ts # No change
|
|
119
|
+
|
|
120
|
+
plugin.config.js # MODIFIED: register `contracts` and `contract-information` pages
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Structure Decision**: Single feature domain (`contracts/`) following the established FastStore plugin pattern. No new top-level directories needed. Cross-domain navigation (Org Unit selector) reuses the existing `OrgUnitBreadcrumb` component from `shared/`.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Quickstart Validation Guide: SUMA Phase 2
|
|
2
|
+
|
|
3
|
+
**Feature**: Contract Listing, Switching & Management
|
|
4
|
+
**Date**: 2026-06-15
|
|
5
|
+
|
|
6
|
+
This guide documents how to validate each user story end-to-end once the implementation is in place. It is not a setup guide for the plugin itself — see [docs/README.md](../../docs/README.md) for host store linking instructions.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
1. Plugin linked to host store via `yarn link @vtex/faststore-plugin-buyer-portal`
|
|
13
|
+
2. Host store running locally or pointing to `b2bfaststoredev.store`
|
|
14
|
+
3. Test account: a buyer user belonging to an org unit with **at least 2 contracts**
|
|
15
|
+
4. `enable_suma_phase_2` feature flag **enabled** in FeatureHub for the test environment
|
|
16
|
+
5. Admin test account: same org unit, role with contract management permissions
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## US-1: View & Switch Active Contract (P1 — Core Flow)
|
|
21
|
+
|
|
22
|
+
**Scenario**: Feature flag ON, buyer with 2+ contracts
|
|
23
|
+
|
|
24
|
+
1. Log in as the buyer user
|
|
25
|
+
2. Navigate to `/pvt/organization-account`
|
|
26
|
+
3. **Expected**: Redirect lands on Contract Listing page (`/contracts/{orgUnitId}`), not `org-unit-details`
|
|
27
|
+
4. Verify all contracts for the org unit are listed
|
|
28
|
+
5. Verify the default contract is visually distinguished (badge, highlight, etc.)
|
|
29
|
+
6. Click on a non-active contract
|
|
30
|
+
7. **Expected**: Loading indicator appears within 500ms
|
|
31
|
+
8. **Expected**: On success, browser navigates to the Profile page (`/profile/{orgUnitId}/{contractId}`) — Contract Information
|
|
32
|
+
9. Verify the newly activated contract is reflected as active in the UI
|
|
33
|
+
|
|
34
|
+
**Error path**:
|
|
35
|
+
Simulate a BFF failure (disable the activate endpoint or use a network throttle to force timeout).
|
|
36
|
+
**Expected**: User stays on Contract Listing; an error toast/message appears.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## US-2: Multi-Select with Keyboard Shortcuts
|
|
41
|
+
|
|
42
|
+
**Scenario**: Buyer on Contract Listing with 4+ contracts
|
|
43
|
+
|
|
44
|
+
1. Navigate to Contract Listing
|
|
45
|
+
2. Press **Cmd+A** (Mac) or **Ctrl+A** (Windows/Linux)
|
|
46
|
+
3. **Expected**: All contracts are visually selected (checkboxes or highlight)
|
|
47
|
+
4. Click on a contract — **Expected**: loading overlay, then navigation to Profile (Contract Information); selection cleared on single click when applicable
|
|
48
|
+
5. **Cmd+Click** (Mac) / **Ctrl+Click** (Win) on contract #1 — **Expected**: selected without navigating
|
|
49
|
+
6. **Cmd+Click** on contract #3 — **Expected**: both #1 and #3 selected
|
|
50
|
+
7. **Shift+Click** on contract #5 — **Expected**: range from #1 (or last anchor) to #5 selected
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## US-3: Admin — Add Contracts
|
|
55
|
+
|
|
56
|
+
**Scenario**: Admin user, org unit has at least 1 available contract not yet added
|
|
57
|
+
|
|
58
|
+
1. Log in as admin, navigate to Contract Listing
|
|
59
|
+
2. Click the "Add contracts" button/trigger
|
|
60
|
+
3. **Expected**: Modal/drawer opens showing available contracts not yet in the unit
|
|
61
|
+
4. Select one contract, click Confirm
|
|
62
|
+
5. **Expected**: Contract appears in the listing
|
|
63
|
+
6. Repeat, selecting multiple contracts at once — **Expected**: all added
|
|
64
|
+
7. Use "Add all available" option — **Expected**: all remaining contracts added
|
|
65
|
+
|
|
66
|
+
**Propagation test**:
|
|
67
|
+
When the org unit has child units, toggle "Apply to child units" before confirming.
|
|
68
|
+
**Expected**: Child units also show the added contracts.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## US-4: Admin — Remove Contracts
|
|
73
|
+
|
|
74
|
+
**Scenario**: Admin user, org unit has 3 contracts
|
|
75
|
+
|
|
76
|
+
1. Navigate to Contract Listing
|
|
77
|
+
2. **Expected**: Remove option/button is visible (3 contracts > 1)
|
|
78
|
+
3. Select 1 contract via Cmd+Click, click Remove
|
|
79
|
+
4. Confirm in the confirmation UI
|
|
80
|
+
5. **Expected**: Contract removed from listing (now 2 remain)
|
|
81
|
+
|
|
82
|
+
**Single-contract guard**:
|
|
83
|
+
Set up an org unit with exactly 1 contract.
|
|
84
|
+
**Expected**: Remove option is not visible at all.
|
|
85
|
+
|
|
86
|
+
**Last-contract guard**:
|
|
87
|
+
With 2 contracts, select both and attempt to remove.
|
|
88
|
+
**Expected**: UI blocks the action (or unselects the last one) with an informative message.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## US-5: Admin — Set Default Contract
|
|
93
|
+
|
|
94
|
+
**Scenario**: Admin user, org unit has 2+ contracts; Contract A is the default
|
|
95
|
+
|
|
96
|
+
1. Navigate to Contract Listing — Contract A has default visual indicator
|
|
97
|
+
2. Trigger "Set as default" on Contract B (via dropdown or action button)
|
|
98
|
+
3. **Expected**: Contract B now shows the default indicator; Contract A does not
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## US-6: Navigation — Org Unit Hierarchy
|
|
103
|
+
|
|
104
|
+
**Scenario**: User in a child org unit with a parent unit
|
|
105
|
+
|
|
106
|
+
1. Navigate to Contract Listing for the child unit
|
|
107
|
+
2. Use the Org Unit selector (breadcrumb/dropdown) to navigate to the parent unit
|
|
108
|
+
3. **Expected**: Contract Listing reloads showing the parent unit's contracts and orgUnitId updates in the URL
|
|
109
|
+
4. From an Org Units page, click on a child unit
|
|
110
|
+
5. **Expected**: Navigates to that child unit's Contract Listing
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## US-7: Cross-Tab State Synchronization
|
|
115
|
+
|
|
116
|
+
**Scenario**: Same user session open in two browser tabs
|
|
117
|
+
|
|
118
|
+
1. Open Contract Listing in Tab A and Tab B (same session)
|
|
119
|
+
2. In Tab A, activate a different contract
|
|
120
|
+
3. **Expected**: Within 2 seconds, Tab B reflects the new active contract without manual refresh
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Feature Flag Regression Test
|
|
125
|
+
|
|
126
|
+
**Scenario**: `enable_suma_phase_2` flag is **disabled**
|
|
127
|
+
|
|
128
|
+
1. Log in as any buyer
|
|
129
|
+
2. Navigate to `/pvt/organization-account`
|
|
130
|
+
3. **Expected**: Redirect goes to `org-unit-details` (existing behavior) — NOT Contract Listing
|
|
131
|
+
4. Verify all existing navigation and pages work without any change
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Run Checklist
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
[ ] US-1: Contract switch success path
|
|
139
|
+
[ ] US-1: Contract switch error path (BFF failure)
|
|
140
|
+
[ ] US-1: Feature flag OFF regression
|
|
141
|
+
[ ] US-2: Cmd/Ctrl+A select all
|
|
142
|
+
[ ] US-2: Cmd/Ctrl+Click individual toggle
|
|
143
|
+
[ ] US-2: Shift+Click range selection
|
|
144
|
+
[ ] US-3: Add single contract
|
|
145
|
+
[ ] US-3: Add multiple contracts
|
|
146
|
+
[ ] US-3: Add all available
|
|
147
|
+
[ ] US-3: Propagate to children
|
|
148
|
+
[ ] US-4: Remove contract (2+ contracts)
|
|
149
|
+
[ ] US-4: Remove option hidden (1 contract)
|
|
150
|
+
[ ] US-4: Block removal of last contract
|
|
151
|
+
[ ] US-4: Propagate removal to children
|
|
152
|
+
[ ] US-5: Set default contract
|
|
153
|
+
[ ] US-6: Navigate to parent org unit
|
|
154
|
+
[ ] US-6: Navigate to child org unit
|
|
155
|
+
[ ] US-7: Cross-tab sync (2-second window)
|
|
156
|
+
```
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Research: SUMA Phase 2
|
|
2
|
+
|
|
3
|
+
**Feature**: Contract Listing, Switching & Management
|
|
4
|
+
**Date**: 2026-06-15
|
|
5
|
+
**Branch**: `feat/suma`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. BFF Endpoint Signatures
|
|
10
|
+
|
|
11
|
+
### Decision (updated 2026-06-17 — aligned with BFF v1.0.2 changelog)
|
|
12
|
+
|
|
13
|
+
Use the existing REST pattern (`units/{orgUnitId}/contracts/*`). The confirmed v1.0.2 endpoints:
|
|
14
|
+
|
|
15
|
+
| Operation | Method | Endpoint | Status |
|
|
16
|
+
|-----------|--------|----------|--------|
|
|
17
|
+
| List attached contracts | `GET` | `units/{orgUnitId}/contracts/attached` | ✅ v1.0.2 |
|
|
18
|
+
| List available contracts | `GET` | `units/{orgUnitId}/contracts/available` | ✅ v1.0.2 |
|
|
19
|
+
| Add contracts to unit | `POST` | `units/{orgUnitId}/contracts/attached` (body: `{ ids: string[] }`) | ✅ v1.0.2 |
|
|
20
|
+
| Remove one contract | `DELETE` | `units/{orgUnitId}/contracts/attached/{contractId}` | ✅ v1.0.2 |
|
|
21
|
+
| Activate contract (switch session) | `POST` | TBD | ⏳ Not in v1.0.2 |
|
|
22
|
+
| Set default contract | `POST` | TBD | ⏳ Not in v1.0.2 |
|
|
23
|
+
|
|
24
|
+
Management endpoints use a different base (`/_v/contracts-management/unit-contracts/{unitId}`) — see `contracts-api.md` for full details.
|
|
25
|
+
|
|
26
|
+
**Note on body shape**: Add uses `{ ids: string[] }` (not `{ contractIds }` as originally assumed). DELETE is per-resource, not bulk — call in sequence for multiple removals. Max 20 attached contracts per unit.
|
|
27
|
+
|
|
28
|
+
**Alternatives considered**: GraphQL mutation — rejected; this project uses REST throughout.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 2. Contract Activation vs. Status Toggle
|
|
33
|
+
|
|
34
|
+
### Decision
|
|
35
|
+
The existing `updateContractStatus` hook toggles `isActive` on a contract (enabled/disabled in the org unit). The new `useActivateContract` hook performs a different operation: it designates one contract as the session's active contract (the one under which the buyer operates). These are two independent BFF calls and must not be conflated.
|
|
36
|
+
|
|
37
|
+
**Rationale**: Phase 1 (already shipped) made the first contract always active by default. Phase 2 lets the user choose. The `isActive` flag controls whether a contract is available in the unit; the activation endpoint controls which one the session is scoped to.
|
|
38
|
+
|
|
39
|
+
**How to apply**: Do not reuse `useUpdateContractStatus` for the contract switch flow. Create `useActivateContract` separately.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 3. Feature Flag Name
|
|
44
|
+
|
|
45
|
+
### Decision
|
|
46
|
+
Feature flag key: `suma`
|
|
47
|
+
TypeScript property: `suma` (no conversion needed — key matches property name directly)
|
|
48
|
+
|
|
49
|
+
**Rationale**: The BFF team confirmed the flag key is `suma`. Unlike `enableAlternativeLogin` which requires snake_case → camelCase normalization, `suma` maps 1:1. Normalization is added in `getBuyerPortalFeatureFlags()` in `get-feature-flags.service.ts`.
|
|
50
|
+
|
|
51
|
+
**Where to add**:
|
|
52
|
+
1. BFF `flags.constants.ts`: add `'suma'` to `BUYER_PORTAL_FEATURE_FLAGS`
|
|
53
|
+
2. Frontend `BuyerPortalProvider.tsx`: add `suma?: boolean` to `FeatureFlags` type
|
|
54
|
+
3. `get-feature-flags.service.ts`: add `suma: normalized["suma"] ?? false` to `getBuyerPortalFeatureFlags()` return value
|
|
55
|
+
|
|
56
|
+
**Alternatives considered**: `enable_suma_phase_2` — rejected; BFF team confirmed key is `suma`.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 4. Contract Information Page — New vs. Reuse
|
|
61
|
+
|
|
62
|
+
### Decision
|
|
63
|
+
The **Contract Information** page is a **new page** (`src/pages/contract-information.tsx`) registered at `/pvt/organization-account/contract-information/[orgUnitId]/[contractId]`. The existing `org-unit-details` page remains unchanged as the homepage when the feature flag is inactive.
|
|
64
|
+
|
|
65
|
+
**Rationale**: The spec lists "Contract Information" as "Novo" (New). In SUMA Phase 2, after the user activates a contract from the Contract Listing, they land on a page scoped to both `orgUnitId` and `contractId`, showing the active contract context (the contract's home). This is architecturally different from `org-unit-details` which currently shows all contracts via a toggle. Separating them keeps backward compatibility with the flag-off flow.
|
|
66
|
+
|
|
67
|
+
**How to apply**: The Contract Listing page (`contracts.tsx`) navigates to `contractInformation({ orgUnitId, contractId })` on successful activation. The existing tabs/layouts that use `orgUnitId+contractId` (budgets, buying policies, etc.) are accessible from the Contract Information page's navigation.
|
|
68
|
+
|
|
69
|
+
**Alternatives considered**: Repurpose `org-unit-details` — rejected; would require feature-flag branching inside the layout, increasing complexity and regression risk.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 5. Multi-Select with Keyboard Shortcuts
|
|
74
|
+
|
|
75
|
+
### Decision
|
|
76
|
+
Implement `useContractSelection` as a pure React state hook managing a `Set<string>` of selected contract IDs, with event handler factories for:
|
|
77
|
+
|
|
78
|
+
- **Cmd/Ctrl+A**: `handleSelectAll()` — fills the set with all visible contract IDs
|
|
79
|
+
- **Cmd/Ctrl+Click**: `handleModifierClick(id)` — toggles the contract in the set
|
|
80
|
+
- **Shift+Click**: `handleShiftClick(id, allIds)` — selects the range from the last-clicked index to the target
|
|
81
|
+
|
|
82
|
+
The hook exposes `selectedIds`, `isSelected(id)`, `clearSelection()`, and the three handlers.
|
|
83
|
+
|
|
84
|
+
**Rationale**: No external library needed — the combination of a `Set` and standard keyboard event APIs (`event.metaKey`, `event.ctrlKey`, `event.shiftKey`) covers all requirements. This pattern is standard for file-manager-style lists.
|
|
85
|
+
|
|
86
|
+
**Platform detection**: Use `event.metaKey || event.ctrlKey` for Cmd/Ctrl to support both Mac and Windows/Linux without user-agent sniffing.
|
|
87
|
+
|
|
88
|
+
**Alternatives considered**: External drag-select library — rejected; overkill for keyboard-only selection; adds bundle weight to a plugin.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 6. Cross-Tab Contract State Synchronization
|
|
93
|
+
|
|
94
|
+
### Decision
|
|
95
|
+
Use the browser's `BroadcastChannel` API with channel name `'buyer-portal-active-contract'`. After a successful contract activation, post a message `{ type: 'CONTRACT_ACTIVATED', contractId, orgUnitId }`. Other tabs listen on this channel and call `router.refresh()` (Next.js App Router) or `router.replace(router.asPath)` (Pages Router) to reload data.
|
|
96
|
+
|
|
97
|
+
**Rationale**: `BroadcastChannel` is supported in all modern browsers (Chrome, Firefox, Safari 15.4+, Edge). No backend changes needed. The host store's Next.js version uses the Pages Router (evident from `useRouter` from `next/router` throughout the codebase), so `router.replace(router.asPath)` is the correct refresh mechanism.
|
|
98
|
+
|
|
99
|
+
**Implementation**: Encapsulated in `useCrossTabContractSync` hook — both sends (on activation success) and listens (on mount). Cleanup removes the channel on unmount.
|
|
100
|
+
|
|
101
|
+
**Alternatives considered**: `localStorage` + `storage` event — works but less clean than BroadcastChannel; same-tab events can fire unexpectedly. SharedWorker — too complex for this use case. Server-Sent Events — requires backend changes, explicitly excluded by assumptions.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 7. Org Unit Selector (Parent Navigation)
|
|
106
|
+
|
|
107
|
+
### Decision
|
|
108
|
+
Reuse the existing `OrgUnitBreadcrumb` / `OrgUnitBreadcrumbPath` component from `src/features/org-units/components/OrgUnitBreadcrumb/`. This component is in `shared/components` exports and handles hierarchy navigation. Adapt its integration point in the new `ContractListingLayout` to display the current org unit path and allow navigation to parent nodes.
|
|
109
|
+
|
|
110
|
+
**Rationale**: The component already exists and solves the same problem (navigating the org unit hierarchy). Importing from `shared/` is compliant with Constitution Principle II.
|
|
111
|
+
|
|
112
|
+
**Alternatives considered**: New custom selector — rejected; duplicates existing functionality.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 8. ContractData Type Extension
|
|
117
|
+
|
|
118
|
+
### Decision
|
|
119
|
+
Extend `ContractData` with an `isDefault?: boolean` field. The BFF's list endpoint is expected to return this field (per spec: "the BFF already implements this").
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
export type ContractData = {
|
|
123
|
+
name: string | null;
|
|
124
|
+
id: string;
|
|
125
|
+
email: string;
|
|
126
|
+
isActive: boolean;
|
|
127
|
+
isDefault?: boolean; // NEW: added for SUMA Phase 2
|
|
128
|
+
creationDate: string;
|
|
129
|
+
salesRepresentative?: string;
|
|
130
|
+
imageUrl?: string;
|
|
131
|
+
assortment?: unknown[];
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Rationale**: The visual distinction of the default contract requires this field. Optional (`?`) for backward compatibility with existing code that uses `ContractData`.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Summary Table
|
|
140
|
+
|
|
141
|
+
| Topic | Decision | Status |
|
|
142
|
+
|-------|----------|--------|
|
|
143
|
+
| BFF endpoints | REST pattern; 4 new methods in ContractsClient | Resolved |
|
|
144
|
+
| Activate vs. status toggle | Separate hook and endpoint | Resolved |
|
|
145
|
+
| Feature flag name | `enable_suma_phase_2` / `enableSumaPhase2` | Resolved |
|
|
146
|
+
| Contract Information page | New page at `contract-information/[orgUnitId]/[contractId]` | Resolved |
|
|
147
|
+
| Multi-select | `useContractSelection` hook with Set + keyboard events | Resolved |
|
|
148
|
+
| Cross-tab sync | BroadcastChannel + `router.replace` | Resolved |
|
|
149
|
+
| Org Unit selector | Reuse `OrgUnitBreadcrumb` from shared | Resolved |
|
|
150
|
+
| ContractData type | Add `isDefault?: boolean` | Resolved |
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Feature Specification: SUMA Phase 2 — Contract Listing, Switching & Management
|
|
2
|
+
|
|
3
|
+
**Feature Branch**: `feat/suma`
|
|
4
|
+
|
|
5
|
+
**Created**: 2026-06-15
|
|
6
|
+
|
|
7
|
+
**Status**: Draft
|
|
8
|
+
|
|
9
|
+
**Input**: User description: "SUMA Fase 2 — Permite que uma Unidade Organizacional visualize, gerencie e troque o contrato ativo entre múltiplos contratos comerciais disponíveis."
|
|
10
|
+
|
|
11
|
+
> **Terminology**: **Contract Information** refers to the existing **Profile** page (`/pvt/organization-account/profile/[orgUnitId]/[contractId]`). After switching contracts, buyers land on Profile to operate under the selected contract.
|
|
12
|
+
|
|
13
|
+
## User Scenarios & Testing *(mandatory)*
|
|
14
|
+
|
|
15
|
+
### User Story 1 - View and Switch Active Contract (Priority: P1)
|
|
16
|
+
|
|
17
|
+
A buyer opens the Contract Listing page (new account homepage, active when feature flag is on), sees all contracts for their Organizational Unit with the default contract visually highlighted, clicks a contract, and is navigated to the Profile page (Contract Information) where they now operate under the selected contract.
|
|
18
|
+
|
|
19
|
+
**Why this priority**: This is the core value of SUMA Phase 2 — enabling users to choose which commercial agreement they operate under. Without this flow, the feature delivers no value.
|
|
20
|
+
|
|
21
|
+
**Independent Test**: Can be fully tested by visiting the Contract Listing page, clicking a non-active contract, and verifying the user lands on Contract Information operating under the new contract.
|
|
22
|
+
|
|
23
|
+
**Acceptance Scenarios**:
|
|
24
|
+
|
|
25
|
+
1. **Given** a buyer is on the Contract Listing page with multiple contracts available, **When** they click on a non-default contract, **Then** a loading indicator appears, the BFF is called to switch the contract session, and the user is navigated to the Profile page upon success.
|
|
26
|
+
2. **Given** the BFF returns an error during contract activation, **When** the request fails, **Then** the user remains on the Contract Listing page and an error message is displayed.
|
|
27
|
+
3. **Given** the feature flag is inactive, **When** the user accesses their account homepage, **Then** the original navigation and homepage are displayed without any regression.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
### User Story 2 - Multi-Select Contracts with Keyboard Shortcuts (Priority: P2)
|
|
32
|
+
|
|
33
|
+
A buyer on the Contract Listing page uses keyboard shortcuts to efficiently select multiple contracts for bulk management actions (add, remove, set default).
|
|
34
|
+
|
|
35
|
+
**Why this priority**: Keyboard shortcuts significantly improve efficiency for power users managing large numbers of contracts. Required for the bulk add/remove flows.
|
|
36
|
+
|
|
37
|
+
**Independent Test**: Can be fully tested by visiting the Contract Listing page and verifying that Cmd+A selects all, Cmd+Click toggles individual selection, and Shift+Click selects a range — without triggering navigation to the contract detail.
|
|
38
|
+
|
|
39
|
+
**Acceptance Scenarios**:
|
|
40
|
+
|
|
41
|
+
1. **Given** a buyer is on the Contract Listing page, **When** they press Cmd+A (Mac) or Ctrl+A (Windows/Linux), **Then** all contracts are selected.
|
|
42
|
+
2. **Given** a buyer is on the Contract Listing page, **When** they Cmd+Click or Ctrl+Click a contract, **Then** that contract is toggled in the selection without navigating to its detail.
|
|
43
|
+
3. **Given** a buyer has selected one contract and then Shift+Clicks another, **Then** all contracts between the two are selected as a range.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### User Story 3 - Admin: Add Contracts to Organizational Unit (Priority: P2)
|
|
48
|
+
|
|
49
|
+
An admin opens the Contract Listing page, opens the add-contracts modal/drawer, selects one, multiple, or all available contracts, optionally propagates to child Organizational Units, and confirms the addition.
|
|
50
|
+
|
|
51
|
+
**Why this priority**: Admins need to be able to expand their unit's available contracts. This directly enables the P1 switching flow for contracts that are currently unavailable.
|
|
52
|
+
|
|
53
|
+
**Independent Test**: Can be fully tested by adding a contract as an admin and verifying it appears in the Contract Listing for that Organizational Unit.
|
|
54
|
+
|
|
55
|
+
**Acceptance Scenarios**:
|
|
56
|
+
|
|
57
|
+
1. **Given** an admin is on the Contract Listing page, **When** they open the add-contracts interface and select one or more contracts and confirm, **Then** the selected contracts are added to the Organizational Unit and appear in the listing.
|
|
58
|
+
2. **Given** an admin adds contracts, **When** they choose to propagate to child Organizational Units, **Then** the contracts are also added to all direct and indirect child units.
|
|
59
|
+
3. **Given** an admin uses "add all available", **When** confirmed, **Then** every contract available to the unit is added at once.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
### User Story 4 - Admin: Remove Contracts from Organizational Unit (Priority: P2)
|
|
64
|
+
|
|
65
|
+
An admin selects one or more contracts and removes them from the Organizational Unit. The system blocks removal of the last contract, and hides the remove option entirely when only one contract is present.
|
|
66
|
+
|
|
67
|
+
**Why this priority**: Lifecycle management of contracts is essential for admins to keep their unit's contract list accurate.
|
|
68
|
+
|
|
69
|
+
**Independent Test**: Can be fully tested by removing a contract as an admin (with at least 2 in the list) and verifying it no longer appears in the Contract Listing.
|
|
70
|
+
|
|
71
|
+
**Acceptance Scenarios**:
|
|
72
|
+
|
|
73
|
+
1. **Given** an admin has an Organizational Unit with 2+ contracts, **When** they select one or more and confirm removal, **Then** the contracts are removed from the listing.
|
|
74
|
+
2. **Given** an Organizational Unit has exactly 1 contract, **When** the admin views the listing, **Then** the remove option is not displayed.
|
|
75
|
+
3. **Given** an admin selects the last remaining contract for removal, **When** they attempt to remove it, **Then** the UI blocks the action and displays an informative message.
|
|
76
|
+
4. **Given** an admin removes contracts, **When** they choose to propagate to child Organizational Units, **Then** the contracts are also removed from all child units.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### User Story 5 - Admin: Set Default Contract (Priority: P2)
|
|
81
|
+
|
|
82
|
+
An admin designates a specific contract as the default for their Organizational Unit. The default contract is visually highlighted in the listing.
|
|
83
|
+
|
|
84
|
+
**Why this priority**: The default contract determines which contract new users or unspecified flows start under — a key configuration for org admins.
|
|
85
|
+
|
|
86
|
+
**Independent Test**: Can be fully tested by setting a non-default contract as the default and verifying the visual highlight updates accordingly.
|
|
87
|
+
|
|
88
|
+
**Acceptance Scenarios**:
|
|
89
|
+
|
|
90
|
+
1. **Given** an admin is on the Contract Listing page, **When** they mark a contract as the default, **Then** the listing updates to visually distinguish that contract as the default.
|
|
91
|
+
2. **Given** a default contract is already set, **When** the admin sets a different contract as default, **Then** the previous default loses its default status and the new one gains it.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### User Story 6 - Navigation: Parent and Child Org Unit Hierarchy (Priority: P3)
|
|
96
|
+
|
|
97
|
+
A buyer or admin navigates the Organizational Unit hierarchy — going up to parent units via the Org Unit selector, and down to child units via the Org Units page.
|
|
98
|
+
|
|
99
|
+
**Why this priority**: Hierarchy navigation is a supporting capability; the core contract features (P1/P2) are usable without it, but it is needed for complete org management workflows.
|
|
100
|
+
|
|
101
|
+
**Independent Test**: Can be fully tested by using the Org Unit selector to navigate to a parent unit and verifying the Contract Listing reflects the parent unit's contracts.
|
|
102
|
+
|
|
103
|
+
**Acceptance Scenarios**:
|
|
104
|
+
|
|
105
|
+
1. **Given** a user is viewing a child Organizational Unit's Contract Listing, **When** they use the Org Unit selector, **Then** they can navigate to a parent unit in the hierarchy.
|
|
106
|
+
2. **Given** a user is on the Org Units page, **When** they select a child unit, **Then** they navigate to that child unit's context.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### User Story 7 - Cross-Tab Contract State Synchronization (Priority: P3)
|
|
111
|
+
|
|
112
|
+
When a user switches their active contract in one browser tab, all other open tabs for the same session automatically reflect the updated contract.
|
|
113
|
+
|
|
114
|
+
**Why this priority**: Prevents confusing inconsistent state across tabs; important for reliability but not blocking core functionality.
|
|
115
|
+
|
|
116
|
+
**Independent Test**: Can be fully tested by switching the active contract in one tab and verifying that another open tab updates its displayed active contract within 2 seconds.
|
|
117
|
+
|
|
118
|
+
**Acceptance Scenarios**:
|
|
119
|
+
|
|
120
|
+
1. **Given** a user has the buyer portal open in two tabs, **When** they switch the active contract in Tab A, **Then** Tab B updates to reflect the new active contract within 2 seconds without requiring a manual refresh.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### Edge Cases
|
|
125
|
+
|
|
126
|
+
- What happens when the Organizational Unit has exactly 1 contract? The remove option is hidden entirely; the user can only add more contracts.
|
|
127
|
+
- What happens when a user attempts to remove all contracts? The UI blocks the action at the last contract — a minimum of 1 contract is always required per Organizational Unit.
|
|
128
|
+
- What happens when the BFF call to activate a contract fails? The user stays on the Contract Listing page and an error toast/message is displayed with guidance.
|
|
129
|
+
- What happens when the feature flag is turned off? The current homepage and navigation are preserved with no regressions.
|
|
130
|
+
- What happens with multi-tab state sync after a contract switch? The active contract state is synchronized across tabs within 2 seconds.
|
|
131
|
+
|
|
132
|
+
## Requirements *(mandatory)*
|
|
133
|
+
|
|
134
|
+
### Functional Requirements
|
|
135
|
+
|
|
136
|
+
- **FR-001**: System MUST display all contracts associated with the user's Organizational Unit on the Contract Listing page.
|
|
137
|
+
- **FR-002**: System MUST visually distinguish the default contract from non-default contracts in the listing.
|
|
138
|
+
- **FR-003**: System MUST display a loading state when a contract activation request is in progress.
|
|
139
|
+
- **FR-004**: System MUST call the BFF endpoint to activate a contract when the user clicks on a contract in the listing.
|
|
140
|
+
- **FR-005**: System MUST navigate the user to the Profile page (Contract Information) upon successful contract switch.
|
|
141
|
+
- **FR-006**: System MUST return the user to the Contract Listing page and display an error message when contract activation fails.
|
|
142
|
+
- **FR-007**: System MUST support multi-selection of contracts via Cmd+A / Ctrl+A (select all), Cmd+Click / Ctrl+Click (toggle individual selection without navigating), and Shift+Click (range selection).
|
|
143
|
+
- **FR-008**: Admin users MUST be able to add one or more contracts — including all available at once — to their Organizational Unit.
|
|
144
|
+
- **FR-009**: Admin users MUST be able to remove individual or multiple contracts in batch from their Organizational Unit.
|
|
145
|
+
- **FR-010**: Admin users MUST be able to designate a contract as the default for their Organizational Unit from the listing.
|
|
146
|
+
- **FR-011**: System MUST block the removal of the last contract in an Organizational Unit — a minimum of 1 contract is always required.
|
|
147
|
+
- **FR-012**: System MUST hide the remove option when an Organizational Unit has exactly 1 contract.
|
|
148
|
+
- **FR-013**: Admin users MUST have the option to propagate contract additions and removals to child Organizational Units.
|
|
149
|
+
- **FR-014**: System MUST replace the current account homepage with the Contract Listing page when the feature flag is active.
|
|
150
|
+
- **FR-015**: System MUST preserve the existing homepage and navigation when the feature flag is inactive, with no regressions.
|
|
151
|
+
- **FR-016**: System MUST synchronize active contract state across multiple browser tabs within the same session.
|
|
152
|
+
- **FR-017**: System MUST include the Organization Manager Agent button as specified in the approved UX prototype.
|
|
153
|
+
- **FR-018**: Users MUST be able to navigate to parent Organizational Units via the Org Unit selector.
|
|
154
|
+
- **FR-019**: Users MUST be able to navigate to child Organizational Units from the Org Units page.
|
|
155
|
+
|
|
156
|
+
### Key Entities
|
|
157
|
+
|
|
158
|
+
- **Contract**: A commercial agreement between the VTEX merchant and the buyer's organization. Attributes: identifier, name, default flag. Can be active or inactive for a given session.
|
|
159
|
+
- **Active Contract**: The contract currently selected by the user, under which all operations (pricing, catalog, payment) are evaluated. Stored in session; must be reflected consistently across browser tabs.
|
|
160
|
+
- **Organizational Unit**: A logical group within the buyer organization hierarchy. Holds a set of contracts. Has parent/child relationships with other units.
|
|
161
|
+
- **Admin Profile**: A role within an Organizational Unit that grants contract management capabilities (add, remove, set default, propagate to children).
|
|
162
|
+
|
|
163
|
+
## Success Criteria *(mandatory)*
|
|
164
|
+
|
|
165
|
+
### Measurable Outcomes
|
|
166
|
+
|
|
167
|
+
- **SC-001**: A buyer can switch their active contract and reach the Profile page (Contract Information) in under 10 seconds from the moment of clicking.
|
|
168
|
+
- **SC-002**: 100% of the Organizational Unit's contracts are displayed in the listing without missing entries.
|
|
169
|
+
- **SC-003**: A loading indicator appears within 500 milliseconds of initiating a contract switch.
|
|
170
|
+
- **SC-004**: Users are returned to the Contract Listing with a clear error message within 5 seconds of a failed contract activation, with no session data lost.
|
|
171
|
+
- **SC-005**: Multi-selection keyboard shortcuts (Cmd+A, Ctrl+A, Cmd+Click, Ctrl+Click, Shift+Click) work correctly on both Mac and Windows/Linux platforms.
|
|
172
|
+
- **SC-006**: Enabling or disabling the feature flag produces zero regressions in the existing homepage and navigation behavior.
|
|
173
|
+
- **SC-007**: Active contract state synchronizes across all open browser tabs within 2 seconds of a change.
|
|
174
|
+
- **SC-008**: Admin contract management actions (add, remove, set default) complete with confirmation feedback within 5 seconds.
|
|
175
|
+
- **SC-009**: The remove option is correctly hidden for any Organizational Unit with exactly 1 contract, verified across all admin views.
|
|
176
|
+
|
|
177
|
+
## Assumptions
|
|
178
|
+
|
|
179
|
+
- BFF endpoints for listing contracts, activating a contract, setting the default contract, adding contracts, and removing contracts are already implemented and production-ready; this feature covers only frontend integration.
|
|
180
|
+
- The feature flag mechanism is already in place; toggling it per account or environment is the responsibility of the feature flag service, not this feature's implementation.
|
|
181
|
+
- Session state that reflects the active contract is managed by the Identity/Auth team; this feature will consume the session API but does not own its implementation (integration to be confirmed with the Identity/Auth team before coding begins).
|
|
182
|
+
- The Organization Manager Agent is a pre-existing UI component that requires wiring into the Contract Listing page per the approved UX prototype.
|
|
183
|
+
- Propagating changes to child Organizational Units is an opt-in action at the admin's discretion; the default behavior affects only the current unit.
|
|
184
|
+
- The approved UX prototype is the authoritative source for visual design, component layout, and interaction details.
|
|
185
|
+
- Multi-tab synchronization will be implemented using browser-native mechanisms (e.g., BroadcastChannel + router refresh); no backend changes are needed for this behavior.
|
|
186
|
+
- "Notifications of new contracts added by the merchant" are explicitly out of scope and deferred to a future release.
|