Handbook
CRM portfolio read strategies (Dynamics 365 Sales Hub)
Read-only harvest for Dynamics 365 account list views (portfolio) and account record enrich (form + related subgrids). Cockpit orchestrates; LCDL provides governed tasks.
Updated
Entry
- Edge tab on
*.crm*.dynamics.comwithpagetype=entitylist&etn=account(list phase). - Operator view: Accounts → My Active Accounts (or any saved account list view).
- Record enrich: navigate to
pagetype=entityrecord&etn=account&id={account_id}(read-only). - Host:
softserveinc.crm4.dynamics.com(tenant-specific).
CDP policy
- Reuse the operator's existing Dynamics tab (
pick_crm_portfolio_cdp_page_targetfor list; same tab for record enrich). - Attach with selective CDP (
connect_owa_page_via_selective_cdp) — same bridge as OWA/Org Explorer; focus emulation enabled on connect when OS focus steal is off. - Read the main Dynamics frame via
Page.createIsolatedWorld+Runtime.evaluate. - Read-only: DOM evaluate,
.ag-body-viewportscroll, and navigation to entityrecord URLs only. - Background-tab grid harvest may stall without focus emulation — see EDGE-CDP-ATTACH.md.
Allowed
| Action | Phase |
|---|---|
| List grid scroll + evaluate | List |
Navigate to pagetype=entityrecord&etn=account&id=… |
Enrich |
| Read form fields (description, phone, industry, …) | Enrich |
| Subgrid scroll + evaluate (Contacts, Opportunities) | Enrich |
| Allowlisted click on Contacts / Opportunities related tab only (loads subgrid) | Enrich |
| Optional: read GET OData responses on record page (no token export) | Enrich |
Forbidden
| Action |
|---|
| New, Refresh, Share, Email a Link, Run Report (toolbar) |
| Save, Save & Close, Assign, Delete, Qualify, Close as Won/Lost |
| Row actions on contacts or opportunities (compose, send, assign) |
| Opening records via row double-click when harvest can use URL navigation instead |
Tasks
| Task | Purpose |
|---|---|
crm_probe_portfolio_health |
Account grid visible, portfolio URL |
crm_probe_list_view |
Assert current view_name matches expected (guided multi-view) |
crm_list_portfolio_accounts_viewport |
Visible virtualized rows only |
crm_list_portfolio_accounts_scroll |
Scroll harvest with dedupe on row-id |
crm_harvest_account_record |
Navigate entityrecord URL; form + Contacts/Opportunities subgrids |
Input: { "session_ref": "…" } where the session page is a CdpOwaPageShim on a Dynamics tab.
List scroll optional input: { "max_scroll_rounds": 80 }.
Record enrich input: { "session_ref": "…", "account_id": "…", "record_url_template": "…" }.
Wire shapes
List harvest
{
"ok": true,
"read_only": true,
"view_name": "My Active Accounts",
"page_url": "https://…/main.aspx?pagetype=entitylist&etn=account…",
"record_url_template": "https://…/main.aspx?pagetype=entityrecord&etn=account&id={account_id}",
"accounts_seen": 42,
"accounts": [
{
"account_id": "45efa899-441a-ec11-b6e6-6045bd8de0ad",
"name": "Reltio, Inc.",
"relationship_type": "Customer",
"business_unit": "NA-SH2-4",
"delivery_unit": "Delivery E1-3",
"owner": "Iurii Liashenko (Busy)",
"hq_territory": "OR",
"new_logo_model": null,
"tal_status": null
}
]
}
account_id comes from the grid row attribute row-id (Dynamics GUID).
Record enrich
{
"ok": true,
"read_only": true,
"account_id": "45efa899-441a-ec11-b6e6-6045bd8de0ad",
"name": "Reltio, Inc.",
"description": "…",
"website": "https://example.com",
"main_phone": "+1 …",
"industry": "Software",
"contacts": [
{
"contact_id": "…",
"full_name": "Jane Doe",
"email": "jane@example.com",
"job_title": "CTO"
}
],
"opportunities": [
{
"opportunity_id": "…",
"name": "FY25 Expansion",
"stage": "Qualify",
"amount": "$100,000"
}
]
}
Consumer
forge-cockpit-web — cockpit_server/crm/portfolio_harvest.py, enrich_harvest.py, guided_harvest.py, ingest.py. Operator flow: crm-portfolio-harvest.md. Dual architecture: CRM-PORTFOLIO-DUAL-ARCHITECTURE.md ↔ crm-portfolio-dual-architecture.md.