From 696196bbf2885b0027b49bd375a61e53439354cb Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 20:25:17 +0000 Subject: [PATCH 1/2] =?UTF-8?q?docs(guides):=20import-and-export=20?= =?UTF-8?q?=E6=8C=89=E5=AE=9E=E6=B5=8B=E6=94=B9=E5=86=99=E2=80=94=E2=80=94?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=90=91=E5=AF=BC=E5=9C=A8=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E8=A7=86=E5=9B=BE=E8=80=8C=E9=9D=9E=20Setup=20=E2=86=92=20Data?= =?UTF-8?q?=EF=BC=8CSalesforce=20=E8=BF=81=E7=A7=BB=E6=A0=87=E6=B3=A8?= =?UTF-8?q?=E6=9C=AA=E8=90=BD=E5=9C=B0=20(#763)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Setup → Data` 分组根本不存在(Setup 应用只有 Overview/Apps/People & Organization/Access Control/Approvals/Configuration/Diagnostics/Integrations/ Advanced 九组),因此本页几乎每一步指向的都是不存在的界面。 但 issue「Import Wizard 不存在」这一条不成立:Console 的 object grid 确实 随附导入向导,入口在对象列表视图工具栏的 Import,并通过 `listImportMappings` → `meta.getItems('mapping')` 按 targetObject 列出本应用 自己的三个具名映射——与 importing-your-data.mdx 的 API 路线是同一条服务端 通路的两端。已按此改写,两页互链。 未落地的部分(Salesforce 迁移向导、OAuth、定时导出、数据主体请求)按 #755/#756 先例标注「尚未落地」并保留设计意图;能落地的部分对着 exportOptions、allowExport 授权与真实向导步骤重写。三语同改。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa --- .changeset/import-export-guide-measured.md | 63 +++++ content/docs/guides/import-and-export.mdx | 219 +++++++++-------- .../docs/guides/import-and-export.zh-Hans.mdx | 219 +++++++++-------- .../docs/guides/import-and-export.zh-Hant.mdx | 221 ++++++++++-------- 4 files changed, 418 insertions(+), 304 deletions(-) create mode 100644 .changeset/import-export-guide-measured.md diff --git a/.changeset/import-export-guide-measured.md b/.changeset/import-export-guide-measured.md new file mode 100644 index 000000000..86f5fc393 --- /dev/null +++ b/.changeset/import-export-guide-measured.md @@ -0,0 +1,63 @@ +--- +'hotcrm': patch +--- + +Docs: restate the Import & Export guide against what the app actually does, and +make it agree with `importing-your-data.mdx` instead of contradicting it. + +The page routed almost every step through a `Setup → Data → …` menu path. The +Setup app's groups are Overview, Apps, People & Organization, Access Control, +Approvals, Configuration, Diagnostics, Integrations and Advanced — there is no +**Data** group and no **Privacy** group, so `Setup → Data → Import Wizard`, +`→ Migrate from Salesforce`, `→ Migrate from HubSpot`, `→ Scheduled Exports` and +`Setup → Privacy → Data Subject Requests` all sent the reader looking for +screens that do not exist. + +Measured, and restated: + +- **The import wizard is real — it just lives somewhere else.** It is on the + object's own list view (**Import** in the toolbar): Upload → Mapping → + Preview, with drag-and-drop CSV / Excel or paste, a generated column template, + auto-matched columns with a confidence reading, write mode (*Always create + new* / *Update existing (skip if no match)* / *Update if matched, else + create*) plus **Match on**, a **Validate data** pass that writes nothing, a + background job with progress for large files, and **History** with **Undo + import**. It reads HotCRM's own saved mappings — the wizard lists the mappings + whose `targetObject` is the object you are on, so `crm_account_import`, + `crm_contact_import` and `crm_lead_import` are the same three the API path in + [Import your own data](/docs/guides/importing-your-data) names. The two pages + now describe one feature from two ends and link to each other. +- **No completion email.** Progress is on screen and the run lands in History; + the platform email service is outbound-only and delivers nothing until a + deployment configures a transport. +- **External IDs replaced by matching keys.** No HotCRM object carries an + `external_id` field, and a named mapping is a strict projection that drops + every column it does not declare, so the advice to add one was inert. The + idempotency it promised is real and comes from each mapping's business key + (Account Name, Email, Email). Also corrected: *Update only* skips rather than + fails, matching is on fields you choose rather than "Email / External ID", + and the 50 MB upload cap does not exist (the real ceiling is 50,000 rows per + job). +- **Salesforce migration marked *(not shipped yet)***, design intent and object + map kept. There is no migration wizard, no connector, and no action that + reads Salesforce — every mention of Salesforce in `src/` is a comment citing + it as a design reference. There is no outbound OAuth either: Setup's **OAuth + Applications** registers clients calling *into* ObjectStack, the opposite + direction. Noted honestly that Salesforce migration is not on the roadmap, + while **HubSpot import is** a named roadmap item — the two are not in the + same state. +- **Export restated on measurement**: the account, contact, lead and opportunity + list views declare `exportOptions: ['csv', 'xlsx']`, the export carries the + view's filter / sort / columns, and the same route is callable directly. + `allowExport` is the opt-in gate (unset denies; `viewAllRecords` / + `modifyAllRecords` do not substitute), with the per-role grants tabulated. + Cases hold an export grant with no list-view surface behind it, which is + called out rather than papered over. Scheduled warehouse export marked + *(not shipped yet)*. +- **GDPR section marked *(not shipped yet)*** and replaced with what can be done + today — delete the contact (attendee rows go, meetings stay), delete the + account (contacts cascade), clear identifying fields by hand. No Privacy + group, no data-subject-request object, no anonymise action, no audit + certificate (the audit capability is not among the ones this app enables). + +zh-Hans and zh-Hant pages updated with the same content. diff --git a/content/docs/guides/import-and-export.mdx b/content/docs/guides/import-and-export.mdx index 59c1fb931..f2846710e 100644 --- a/content/docs/guides/import-and-export.mdx +++ b/content/docs/guides/import-and-export.mdx @@ -1,143 +1,159 @@ --- title: Import & Export -description: Bulk import from CSV/Excel, migrate from Salesforce or HubSpot, export data, and handle GDPR data requests. +description: What actually ships for getting data in and out — the list-view import wizard and HotCRM's three ready-made mappings, CSV / XLSX export from four list views, the data API — and which migration wizards are still design intent. --- # Import & Export -How to get data **into** HotCRM (bulk imports, migrations) and **out** (exports, GDPR, backups). +How to get data **into** HotCRM and **out** of it. Everything below is stated against the app as it ships. Sections marked *(not shipped yet)* are kept as design intent — read them as a requirements document, not as instructions. + +> **There is no Setup → Data menu.** The Setup app's groups are *Overview*, *Apps*, *People & Organization*, *Access Control*, *Approvals*, *Configuration*, *Diagnostics*, *Integrations* and *Advanced*. There is no **Data** group and no **Privacy** group. Earlier versions of this page routed almost every step through `Setup → Data → …`, which sent readers looking for screens that do not exist. Import and export both live on the **object's own list view**; everything else is the data API. ## Importing from CSV / Excel -The simplest route. Works for: leads, accounts, contacts, products, opportunities, cases, contracts. +Two routes, both real, both landing in the same place: + +- **In the app** — open the object's list view and choose **Import** in the toolbar. Best for a one-off file you want to eyeball before it writes. +- **Over the API** — the import endpoint, with a dry run, a background job and an undo. Step by step in [Import your own data](/docs/guides/importing-your-data). + +They share the same server-side import path, so the same mappings, the same validation and the same per-row error codes apply either way. + +### The in-app import wizard + +1. **Open the object's list view** and choose **Import**. The target object comes from where you started — the wizard never asks you to pick one. +2. **Upload** — drag and drop a CSV or Excel file, or paste from a spreadsheet. **Download template** hands you a CSV carrying that object's columns, with the required ones marked `*`. +3. **Mapping** — columns are auto-matched to fields by header name, each match carrying a confidence reading; you correct or skip the rest by hand. If HotCRM ships a **saved mapping** for the object, pick that instead: the renames, the value transforms and the type coercion then run on the server and the column mapping goes read-only. Three ship today — `crm_account_import`, `crm_contact_import` and `crm_lead_import` — and they are the same three the API path names. +4. **Import options** — *When a row matches an existing record* offers **Always create new**, **Update existing (skip if no match)** and **Update if matched, else create**, with **Match on** deciding which field or fields the matching runs against. A saved mapping brings its own default write mode and key, so on the three objects above you rarely touch this. +5. **Validate data** — checks every row against the server without writing anything. Do it before every import. +6. **Preview → Import** — a large file is handed to a background job and reports `Importing n of m rows`. **History** lists recent import jobs for the object and offers **Undo import**: records the job created are deleted, records it updated are restored to their previous values. -### Step-by-step +> **No email arrives when the import finishes.** Progress shows on screen while the job runs, and the run is listed under **History** afterwards — that is the whole notification story. HotCRM sends no completion mail, and the platform's email service is outbound-only and delivers nothing at all until a deployment configures a transport. -1. Go to **Setup → Data → Import Wizard**. -2. Pick the **object** to import into. -3. Upload the **CSV** or **Excel** file (max 50 MB per upload; split larger files). -4. **Map columns** to fields — the wizard auto-suggests matches by header name. -5. Choose **conflict behaviour**: - - **Insert only** — skip rows that match an existing record. - - **Insert and update** — update existing records (matched by Email / External ID). - - **Update only** — fail if not found. -6. Run a **dry-run** — the wizard reports how many would succeed / fail without writing. -7. **Commit** — execute the import. You'll see a progress bar; large imports run in the background and you get an email when complete. +A job takes up to **50,000 rows**; split a bigger file. Undo has a tighter ceiling and a few other conditions — see [Import your own data](/docs/guides/importing-your-data). ### Field mapping tips -- **Owners** — provide the user's email; the importer resolves to the user ID. -- **Lookups** — provide the referenced record's *Name* or *External ID*; the importer resolves. -- **Picklists** — values must match the picklist's defined values exactly (case-insensitive). -- **Dates** — ISO format (`2025-03-15`) works everywhere; otherwise specify the format. -- **Multi-select** — separate values with semicolons. +- **Owners** — give the user's email address; it resolves against the user record. An address matching no user **fails that row** with `reference_not_found`; nothing is guessed. Leave the cell blank and the record is owned by whoever ran the import. +- **Lookups** — give the referenced record's id, or the text its object is identified by (its display field, then `name` / `title` / `label` / `full_name` / `email` / `username`). A parent named later in the same file still resolves. +- **Picklists** — matched against the field's option labels *and* values, case-insensitively, so *Technology* and *technology* both land. The shipped mappings additionally translate the vocabulary other systems export — *SaaS*, *Trade Show*, *Client*. Anything still unrecognised fails its row with `invalid_option` rather than being dropped quietly. +- **Dates** — `YYYY-MM-DD` is safest. +- **Numbers** — plain digits (`1500000`, not `1.5M`). +- **Addresses** — contacts have real mailing-address columns; accounts and leads store an address as one structured field that cannot be assembled out of separate spreadsheet columns. The known limits are listed in [Import your own data](/docs/guides/importing-your-data). -### External IDs (recommended) +### Matching keys — not external IDs -Add an `external_id` column (e.g., `legacy_account_id = ACME-001`) and mark it as the matching key. This lets you: +Earlier versions of this page told you to add an `external_id` column and mark it as the matching key. **No HotCRM object carries an `external_id` field**, and a named mapping is a strict projection — every column it does not declare is dropped before the write — so that column would simply be ignored. -- Re-run the import idempotently without creating duplicates. -- Cross-reference between objects (e.g., import leads with their original account's external ID, and the importer links them). +The idempotency it promised is real. It comes from a business key instead: -## Migrating from Salesforce +| Mapping | Matches existing records on | +| --- | --- | +| `crm_account_import` | Account Name | +| `crm_contact_import` | Email | +| `crm_lead_import` | Email | + +Re-run the same file and matching rows are updated rather than duplicated. In the wizard, **Match on** is the same idea chosen by hand. Cross-object references work the same way: a contact names its account by *Account Name*, not by a legacy id. + +Account names are matched **verbatim**, though — *Acme Corp* and *ACME Corp* are two different accounts. -A purpose-built migration wizard ships in the box. +## Migrating from Salesforce (not shipped yet) -1. **Setup → Data → Migrate from Salesforce**. -2. Authorise via OAuth — the wizard reads from your Salesforce sandbox or production. -3. The wizard auto-maps Salesforce objects to HotCRM equivalents: +The intended behaviour: a purpose-built migration wizard in the box. You authorise against your Salesforce sandbox or production over OAuth, the wizard auto-maps Salesforce objects onto their HotCRM equivalents, you review the field, picklist and owner mappings, choose the slice (all-time, or a date range for a staged cutover), dry-run, and commit — with the Salesforce record ids kept for traceability. + +| Salesforce | HotCRM equivalent (intended) | +| --- | --- | +| Account | `crm_account` | +| Contact | `crm_contact` | +| Lead | `crm_lead` | +| Opportunity | `crm_opportunity` | +| OpportunityLineItem | `crm_opportunity_line_item` | +| Quote / QuoteLineItem | `crm_quote` / `crm_quote_line_item` | +| Contract | `crm_contract` | +| Case | `crm_case` | +| Campaign / CampaignMember | `crm_campaign` / `crm_campaign_member` | +| Product2 | `crm_product` | +| Task / Event | `crm_task` / `crm_event` | +| User | user *(matched by email, or invited)* | - | Salesforce | HotCRM | - | --- | --- | - | Account | account | - | Contact | contact | - | Lead | lead | - | Opportunity | opportunity | - | OpportunityLineItem | opportunity_line_item | - | Quote / QuoteLineItem | quote / quote_line_item | - | Contract | contract | - | Case | case | - | Campaign / CampaignMember | campaign / campaign_member | - | Product2 | product | - | Task / Event | task / event | - | User | user *(matched by email or invited)* | +**None of this ships.** There is no migration wizard, no Salesforce connector and no action that reads from Salesforce anywhere in the app. Salesforce is mentioned a dozen or so times in the source, and every one of those is a comment citing it as a design reference — the forecast-category ladder, the first-response-time definition, the Lightning-style record page layouts, and the note in the import mappings about the vocabulary a Salesforce export writes. -4. **Field mapping** — defaults are sensible; review and adjust custom fields. -5. **Picklist mapping** — map Salesforce values to HotCRM values (especially stages, statuses, priorities). -6. **Owner mapping** — map Salesforce users to HotCRM users by email. -7. **Pick the slice** — all-time, or a date range (for staged cutover). -8. **Dry-run**, review report, **commit**. +There is no outbound OAuth either. Setup's **OAuth Applications** screen registers clients that call *into* ObjectStack; nothing in the product authorises HotCRM *against* somebody else's org. -The wizard preserves Salesforce IDs as `external_id` for traceability. +It is not on the roadmap as such: the connector line there reads *"More connectors — Slack, Microsoft Teams, Google Workspace, HubSpot import"*. -## Migrating from HubSpot +**What to do today.** Export from Salesforce to CSV and load it with the import above. The shipped mappings already speak part of that dialect — *SaaS* and *Financial Services* become *Software / SaaS* and *Finance*, *Trade Show* becomes *Event / Trade Show*, *Client* becomes a customer account — so the accounts, contacts and leads slice usually needs little more than the right header row. Objects with no shipped mapping go through the wizard's manual column mapping, or the data API. -Similar wizard at **Setup → Data → Migrate from HubSpot**. Map Hub object types to HotCRM: +## Migrating from HubSpot (not shipped yet) -| HubSpot | HotCRM | +The same story, with one difference: **HubSpot import is a named roadmap item**, so this one is scheduled rather than merely wished for. + +| HubSpot | HotCRM equivalent (intended) | | --- | --- | -| Company | account | -| Contact | contact | -| Deal | opportunity | -| Ticket | case | -| Line item | opportunity_line_item | -| Product | product | -| Quote | quote | +| Company | `crm_account` | +| Contact | `crm_contact` | +| Deal | `crm_opportunity` | +| Ticket | `crm_case` | +| Line item | `crm_opportunity_line_item` | +| Product | `crm_product` | +| Quote | `crm_quote` | -Deal pipelines map to opportunity stages — review the picklist mapping carefully. +Deal pipelines would map onto opportunity stages, which is the mapping worth reviewing by hand. Until it ships, the CSV route above is the migration path. ## Migration playbook -1. **Map roles & users first** — without owners, everything is orphaned. -2. **Dry-run in sandbox** — never run a first migration against production. -3. **Migrate in this order** to keep references intact: - 1. Users - 2. Products - 3. Accounts (+ External ID) - 4. Contacts (+ link to accounts) - 5. Leads - 6. Opportunities (+ link to accounts + contacts) - 7. Opportunity line items - 8. Quotes / Quote line items - 9. Contracts - 10. Cases - 11. Campaigns - 12. Campaign members - 13. Tasks / Events / Notes / Files -4. **Reconcile counts** — totals from the source system vs. HotCRM. Investigate any gap >0.1%. -5. **Spot-check 10 records** end-to-end manually. -6. **Sign-off** with sales / service leadership before go-live. +Still the right advice — it is now advice about the CSV route. + +1. **Invite the users first.** The importer never creates users, and an owner-email cell matching nobody fails its row. Load users into HotCRM (or clear the owner column and reassign afterwards) before anything else. +2. **Load in dependency order** — accounts, then contacts, then leads. Every contact names an account that must already exist. Past those three, keep the same principle: a record that points at another one goes second. +3. **Validate before you write**, every time. *Validate data* in the wizard, `dryRun` on the API. `row` in the report is the row number in your file, so you fix the sheet and re-run. +4. **Reconcile counts** — totals in the source system against HotCRM, and investigate any gap. +5. **Spot-check ten records end to end** by hand. Counts agreeing does not mean fields landed. +6. **Know your undo window before you start.** Undo is per job and does not cover a large one, so import a big data set in chunks if you want the rollback. ## Exporting data -### Ad-hoc export +### From a list view + +Four list views declare `exportOptions: ['csv', 'xlsx']` — **Accounts**, **Contacts**, **Leads** and **Opportunities**. Open one, use **Export** in the toolbar, pick CSV or Excel. What you get is the view you are looking at: its filter, its sort and its columns travel with the export. + +### Who may export + +Export is its own permission, not a side effect of being able to read. `allowExport` is opt-in and an unset bit **denies**; neither `viewAllRecords` nor `modifyAllRecords` substitutes for it. HotCRM authors the grants per role: + +| Role | Objects it may export | +| --- | --- | +| Sales Rep | Leads, Accounts, Contacts, Opportunities, Cases | +| Sales Manager | Leads, Accounts, Contacts, Opportunities, Cases | +| Service Agent | Leads, Accounts, Contacts, Cases | +| Marketing User | Leads, Accounts, Contacts, Opportunities | +| System Admin | Leads, Accounts, Contacts, Opportunities, Cases | +| Guest Portal | nothing, deliberately | + +The grant opens the door; it does not widen the rows. Export is read-derived, so record scope and sharing still apply on top — a rep exporting Opportunities gets their own book, not the org's. + +Cases carry an export grant but no Cases list view declares `exportOptions`: that grant is aimed at report export, which rides on the platform's `reports` capability. This app's `requires` does not list it, so on a default boot the four list views above and the data API are the export surfaces you have. -From any list view or report → **Export** → CSV or Excel. Subject to user permissions and FLS. +### Over the data API -### Scheduled export +The list view's **Export** button calls the object's export route, and you can call it yourself: format `csv`, `xlsx` or `json`, with `fields`, `orderby`, `filter`, `search` and `limit`. Or query the object with ObjectQL and page through the results. See [API reference](/docs/customization/api-reference) for the route shape your runtime version serves, and for authentication. -For data warehouse loads (Snowflake, BigQuery, Redshift): +### Scheduled export to a warehouse (not shipped yet) -- **Setup → Data → Scheduled Exports** → pick object, fields, filters, schedule, destination. -- Destinations: S3 / Azure Blob / GCS / SFTP. -- Format: CSV, Parquet, or JSON Lines. -- Incremental — only changed since the last run. +The intended behaviour: pick object, fields, filter, schedule and destination; land the file on S3 / Azure Blob / GCS / SFTP as CSV, Parquet or JSON Lines; incremental after the first run. -### Bulk API export +None of it exists — no scheduled-export screen, no warehouse destination, no Parquet writer, and no `Setup → Data` menu to hold one. Today, either export by hand from a list view, or put the export route on a schedule you own — a cron job, a Lambda, a GitHub Action. See [Integrations](/docs/guides/integrations). -For programmatic export — use the [API reference](/docs/customization/api-reference). Query any object with ObjectQL; results paginate. +## Data deletion requests (not shipped yet) -## GDPR / data deletion requests +The intended behaviour: `Setup → Privacy → Data Subject Requests`, identify the person by email or contact id, see everything linked to them, choose **Delete** or **Anonymise** (name and email replaced with `[redacted]`, records kept for analytics), and hand the customer an audit certificate. -When a customer requests *"delete all my personal data"*: +None of it exists. There is no Privacy group in Setup, no data-subject-request object, no anonymisation action, and no audit certificate — the platform's audit capability is not among the ones this app enables. -1. **Setup → Privacy → Data Subject Requests → New**. -2. Identify the person — by email or contact ID. -3. The wizard shows everything linked to them: contact record, leads, cases, emails logged, campaign memberships, files. -4. Choose **Delete** or **Anonymise** (replace name/email with `[redacted]` while keeping records for analytics). -5. Confirm — the system processes and produces an **audit certificate** you can hand the customer. +What you can do today is delete or edit the records directly: -Anonymisation is usually preferred — it preserves the integrity of historical reports without keeping PII. +- **Delete the contact.** The event-attendee rows naming that person go with it; the meetings themselves stay, so the team's history does not develop holes. +- **Delete the account** and its contacts cascade with it. Check what else hangs off it first — that cascade is worth planning rather than discovering. +- **Anonymise by hand** by clearing the identifying fields on the record you keep. There is no one-click equivalent, and no certificate comes out the other end. ## Backups & disaster recovery @@ -150,8 +166,9 @@ For a tested **disaster recovery** runbook, see [Reference › Security & Compli ## Tips -- ✅ **Always dry-run** large imports. -- ✅ **Always add an External ID** column — saves you from duplicates forever. -- ✅ **Migrate in sandbox first**, then run a final delta migration on cutover weekend. -- ✅ For exports, use **scheduled exports** rather than ad-hoc — repeatable, monitored, retried on failure. -- ✅ Use **anonymisation** rather than deletion for GDPR — keeps reports honest. +- ✅ **Validate before every import** — *Validate data* in the wizard, `dryRun` on the API. It costs seconds, and it is the only thing standing between a wrong column and 50,000 rows. +- ✅ **Load in dependency order** — accounts, then contacts, then leads. +- ✅ **Let the business key do the de-duplicating.** Do not invent an `external_id` column; re-running the same file already updates instead of duplicating. +- ✅ **Check the undo window before a big import**, and chunk the file if you want the rollback. +- ✅ **Grant `allowExport` deliberately** — it is opt-in because it is bulk egress, and the rows leave with the person. +- ✅ **Read every *(not shipped yet)* section as a requirements document, not a configuration guide** — and watch the [roadmap](/docs/whats-new#roadmap). diff --git a/content/docs/guides/import-and-export.zh-Hans.mdx b/content/docs/guides/import-and-export.zh-Hans.mdx index 53293ef70..f9715d2c2 100644 --- a/content/docs/guides/import-and-export.zh-Hans.mdx +++ b/content/docs/guides/import-and-export.zh-Hans.mdx @@ -1,143 +1,159 @@ --- title: 导入与导出 -description: 从 CSV/Excel 批量导入,从 Salesforce 或 HubSpot 迁移,导出数据,并处理 GDPR 数据请求。 +description: 数据进出 HotCRM 真正落地的那部分——列表视图上的导入向导与随附的三个映射、四个列表视图的 CSV / XLSX 导出、数据 API——以及哪些迁移向导仍是设计意图。 --- # 导入与导出 -如何将数据**导入** HotCRM(批量导入、迁移)以及**导出**(导出、GDPR、备份)。 +如何把数据**导入** HotCRM,以及如何**导出**。下文全部按应用当前实际的样子书写;标注 *(尚未落地)* 的小节保留的是设计意图——请把它们当作需求文档来读,而不是操作步骤。 + +> **不存在「设置 → 数据」菜单。** Setup 应用的分组是 *概览*、*应用*、*人员与组织*、*访问控制*、*审批*、*配置*、*诊断*、*集成* 和 *高级*。既没有 **数据** 分组,也没有 **隐私** 分组。本页较早的版本几乎把每一个步骤都指向 `设置 → 数据 → …`,而照着走的人去找的是并不存在的界面。导入与导出都在**对象自己的列表视图**上;其余的部分是数据 API。 ## 从 CSV / Excel 导入 -最简单的途径。适用于:潜在客户、客户、联系人、产品、商机、工单、合同。 +两条路,都是真的,而且落到同一个地方: + +- **在应用里**——打开对象的列表视图,点工具栏的**导入**。适合那种你想先亲眼过一遍再写入的一次性文件。 +- **通过 API**——导入端点,带试运行、后台作业与撤销。分步说明见[导入你自己的数据](/zh-Hans/docs/guides/importing-your-data)。 + +两条路共用服务端同一条导入通路,因此映射、校验与逐行错误码都是同一套。 + +### 应用内的导入向导 + +1. **打开该对象的列表视图**,点**导入**。目标对象由你从哪里进来决定——向导不会让你再选一次。 +2. **上传**——把 CSV 或 Excel 文件拖进去,或者直接从电子表格里粘贴。**下载模板** 会给你一个带该对象各列的 CSV,必填列标着 `*`。 +3. **映射**——各列按表头名自动匹配到字段,每条匹配都带一个置信度读数;其余的由你手工纠正或跳过。如果 HotCRM 为该对象随附了**已保存的映射**,就直接选它:重命名、值转换与类型强制转换随后都在服务端执行,列映射变为只读。今天随附三个——`crm_account_import`、`crm_contact_import` 与 `crm_lead_import`——和 API 那条路点名的是同样三个。 +4. **导入选项**——*当某行匹配到已有记录时* 提供 **总是新建**、**更新已有(无匹配则跳过)** 与 **匹配则更新,否则新建**,并由 **匹配依据** 决定按哪个(哪些)字段去匹配。已保存的映射会自带默认写入模式与匹配键,所以在上面那三个对象上你几乎不需要动它。 +5. **校验数据**——把每一行拿到服务端核对一遍,不写入任何东西。每次导入前都做。 +6. **预览 → 导入**——大文件会交给后台作业,并显示 `正在导入 n / m 行`。**历史** 列出该对象最近的导入作业,并提供**撤销导入**:作业创建的记录会被删除,它更新过的记录会恢复到之前的值。 -### 分步操作 +> **导入完成时不会有邮件送达。** 作业运行时进度显示在屏幕上,跑完后这次运行列在**历史**里——通知就到此为止。HotCRM 不发送任何完成邮件,而平台的邮件服务只有出站方向,且在部署配置传输方式之前根本什么都不投递。 -1. 前往 **设置 → 数据 → 导入向导**。 -2. 选择要导入到的**对象**。 -3. 上传 **CSV** 或 **Excel** 文件(每次上传最大 50 MB;更大的文件请拆分)。 -4. **将列映射**到字段 —— 向导会按表头名称自动建议匹配。 -5. 选择**冲突行为**: - - **仅插入** —— 跳过与现有记录匹配的行。 - - **插入并更新** —— 更新现有记录(按邮件 / 外部 ID 匹配)。 - - **仅更新** —— 如果未找到则失败。 -6. 运行**试运行** —— 向导会报告有多少会成功 / 失败,而不进行写入。 -7. **提交** —— 执行导入。你会看到一个进度条;大型导入在后台运行,完成后你会收到一封邮件。 +单个作业最多 **50,000 行**;更大的文件请拆分。撤销的上限更紧,另外还有几个前提条件——见[导入你自己的数据](/zh-Hans/docs/guides/importing-your-data)。 ### 字段映射提示 -- **所有者** —— 提供用户的邮件地址;导入器会解析为用户 ID。 -- **查找字段** —— 提供被引用记录的 *Name* 或 *External ID*;导入器会进行解析。 -- **选项列表** —— 值必须与选项列表定义的值完全匹配(不区分大小写)。 -- **日期** —— ISO 格式(`2025-03-15`)在任何地方都适用;否则请指定格式。 -- **多选** —— 用分号分隔各值。 +- **所有者**——填用户的邮件地址,它会去解析到用户记录。匹配不到任何用户的地址会让**该行失败**,错误码 `reference_not_found`;系统不做任何猜测。把单元格留空,则记录归运行导入的人所有。 +- **查找字段**——填被引用记录的 id,或者那个对象用来标识自己的文本(其显示字段,其次是 `name` / `title` / `label` / `full_name` / `email` / `username`)。父记录即使排在同一个文件的后面,也仍然能解析到。 +- **选项列表**——按字段选项的标签*和*值做匹配,不区分大小写,所以 *Technology* 和 *technology* 都能落地。随附的映射还额外翻译其他系统导出时使用的词汇——*SaaS*、*Trade Show*、*Client*。仍然认不出来的值会让该行以 `invalid_option` 失败,而不是被悄悄丢掉。 +- **日期**——`YYYY-MM-DD` 最稳妥。 +- **数字**——纯数字(`1500000`,不是 `1.5M`)。 +- **地址**——联系人有真实的通讯地址列;客户和潜在客户把地址存为一个结构化字段,无法由分散的表格列拼装出来。已知限制列在[导入你自己的数据](/zh-Hans/docs/guides/importing-your-data)。 -### 外部 ID(推荐) +### 匹配键——而不是外部 ID -添加一个 `external_id` 列(例如 `legacy_account_id = ACME-001`)并将其标记为匹配键。这让你能够: +本页较早的版本让你加一个 `external_id` 列并把它标记为匹配键。**HotCRM 的对象没有任何一个带 `external_id` 字段**,而具名映射是严格投影——它没有声明的每一列都会在写入前被丢弃——所以那一列只会被忽略。 -- 幂等地重新运行导入而不创建重复记录。 -- 在对象之间交叉引用(例如,导入潜在客户及其原始客户的外部 ID,导入器会将它们关联起来)。 +它承诺的幂等性是真的,只不过来自业务键: -## 从 Salesforce 迁移 +| 映射 | 与已有记录的匹配依据 | +| --- | --- | +| `crm_account_import` | 客户名称 | +| `crm_contact_import` | 邮件地址 | +| `crm_lead_import` | 邮件地址 | + +重新运行同一个文件,匹配上的行会被更新而不是重复创建。在向导里,**匹配依据** 就是同一个想法的手工版。跨对象引用也是同样的机制:联系人用*客户名称*指向它的客户,而不是某个遗留 id。 + +不过客户名称是**逐字**匹配的——*Acme Corp* 和 *ACME Corp* 是两个不同的客户。 -随附了一个专门构建的迁移向导。 +## 从 Salesforce 迁移(尚未落地) -1. **设置 → 数据 → 从 Salesforce 迁移**。 -2. 通过 OAuth 授权 —— 向导会从你的 Salesforce 沙盒或生产环境中读取。 -3. 向导会自动将 Salesforce 对象映射到 HotCRM 对应项: +设计意图是这样的:随箱提供一个专门构建的迁移向导。你通过 OAuth 授权到自己的 Salesforce 沙盒或生产环境,向导自动把 Salesforce 对象映射到 HotCRM 的对应项,你审阅字段、选项列表与所有者映射,选择数据切片(全部历史,或用于分阶段切换的一个日期范围),试运行,然后提交——并保留 Salesforce 的记录 id 以便追溯。 + +| Salesforce | HotCRM 对应项(设计意图) | +| --- | --- | +| Account | `crm_account` | +| Contact | `crm_contact` | +| Lead | `crm_lead` | +| Opportunity | `crm_opportunity` | +| OpportunityLineItem | `crm_opportunity_line_item` | +| Quote / QuoteLineItem | `crm_quote` / `crm_quote_line_item` | +| Contract | `crm_contract` | +| Case | `crm_case` | +| Campaign / CampaignMember | `crm_campaign` / `crm_campaign_member` | +| Product2 | `crm_product` | +| Task / Event | `crm_task` / `crm_event` | +| User | 用户 *(按邮件匹配,或发出邀请)* | - | Salesforce | HotCRM | - | --- | --- | - | Account | account | - | Contact | contact | - | Lead | lead | - | Opportunity | opportunity | - | OpportunityLineItem | opportunity_line_item | - | Quote / QuoteLineItem | quote / quote_line_item | - | Contract | contract | - | Case | case | - | Campaign / CampaignMember | campaign / campaign_member | - | Product2 | product | - | Task / Event | task / event | - | User | user *(按邮件匹配或邀请)* | +**这些一样都没有落地。** 应用里没有迁移向导,没有 Salesforce 连接器,也没有任何一个会去读 Salesforce 的操作。源码里提到 Salesforce 有十来处,每一处都是把它当作业界设计参照来引用的注释——预测分类阶梯、首次响应时间的定义、Lightning 风格的记录页版面,以及导入映射里那条关于「Salesforce 导出会写哪些词汇」的说明。 -4. **字段映射** —— 默认值合理;审阅并调整自定义字段。 -5. **选项列表映射** —— 将 Salesforce 的值映射到 HotCRM 的值(尤其是阶段、状态、优先级)。 -6. **所有者映射** —— 按邮件将 Salesforce 用户映射到 HotCRM 用户。 -7. **选择数据切片** —— 全部历史数据,或一个日期范围(用于分阶段切换)。 -8. **试运行**,审阅报告,**提交**。 +也没有对外的 OAuth。Setup 的 **OAuth 应用** 界面登记的是调用*进入* ObjectStack 的客户端;产品里没有任何东西负责让 HotCRM 去*向别人的组织*取得授权。 -向导会将 Salesforce ID 保留为 `external_id` 以便追溯。 +它甚至也不在路线图上:路线图那一行写的是 *「更多连接器 —— Slack、Microsoft Teams、Google Workspace、HubSpot 导入」*。 -## 从 HubSpot 迁移 +**今天该怎么做。** 从 Salesforce 导出 CSV,用上文的导入把它装进来。随附的映射已经会说这门方言的一部分——*SaaS* 与 *Financial Services* 会变成 *Software / SaaS* 与 *Finance*,*Trade Show* 变成 *Event / Trade Show*,*Client* 变成客户类型的客户——所以客户、联系人与潜在客户这一段,通常只需要正确的表头行。没有随附映射的对象,走向导里的手工列映射,或者走数据 API。 -在 **设置 → 数据 → 从 HubSpot 迁移** 中有一个类似的向导。将 Hub 对象类型映射到 HotCRM: +## 从 HubSpot 迁移(尚未落地) -| HubSpot | HotCRM | +同样的情况,但有一点不同:**HubSpot 导入是路线图上点名的一项**,所以这一条是排上了日程,而不只是一个愿望。 + +| HubSpot | HotCRM 对应项(设计意图) | | --- | --- | -| Company | account | -| Contact | contact | -| Deal | opportunity | -| Ticket | case | -| Line item | opportunity_line_item | -| Product | product | -| Quote | quote | +| Company | `crm_account` | +| Contact | `crm_contact` | +| Deal | `crm_opportunity` | +| Ticket | `crm_case` | +| Line item | `crm_opportunity_line_item` | +| Product | `crm_product` | +| Quote | `crm_quote` | -交易管道映射到商机阶段 —— 请仔细审阅选项列表映射。 +交易管道会映射到商机阶段,那是最值得手工审阅的一处映射。在它落地之前,上文的 CSV 路线就是迁移路径。 ## 迁移操作手册 -1. **首先映射角色和用户** —— 没有所有者,一切都会成为孤立数据。 -2. **在沙盒中试运行** —— 切勿对生产环境运行首次迁移。 -3. **按此顺序迁移**以保持引用关系完整: - 1. 用户 - 2. 产品 - 3. 客户(+ 外部 ID) - 4. 联系人(+ 关联到客户) - 5. 潜在客户 - 6. 商机(+ 关联到客户 + 联系人) - 7. 商机行项目 - 8. 报价 / 报价行项目 - 9. 合同 - 10. 工单 - 11. 营销活动 - 12. 营销活动成员 - 13. 任务 / 事件 / 备注 / 文件 -4. **核对数量** —— 源系统的总数 vs. HotCRM。调查任何 >0.1% 的差距。 -5. **端到端手动抽查 10 条记录**。 -6. 在上线前**获得**销售 / 服务负责人的**签字批准**。 +依然是对的建议——只是现在它是关于 CSV 这条路的建议。 + +1. **先把用户建好。** 导入器从不创建用户,而一个匹配不到任何人的所有者邮件单元格会让该行失败。先把用户装进 HotCRM(或者清空所有者列、导入后再分配),然后再做别的。 +2. **按依赖顺序装载**——先客户,再联系人,最后潜在客户。每个联系人都要指名一个必须已经存在的客户。除这三者之外也守同一条原则:指向别人的记录排在后面。 +3. **写入前先校验**,每次都做。向导里的*校验数据*,API 上的 `dryRun`。报告里的 `row` 就是你文件里的行号,改好表格再跑一遍即可。 +4. **核对数量**——源系统的总数与 HotCRM 的总数对一对,有差距就去查。 +5. **手工端到端抽查十条记录。** 数量对上,不等于字段都落对了。 +6. **动手前先弄清你的撤销窗口。** 撤销是按作业来的,而且覆盖不了大作业,所以如果你要这层保险,就把大数据集分块导入。 ## 导出数据 -### 临时导出 +### 从列表视图导出 + +有四个列表视图声明了 `exportOptions: ['csv', 'xlsx']`——**客户**、**联系人**、**潜在客户** 与 **商机**。打开其中之一,用工具栏的**导出**,选 CSV 或 Excel。你拿到的就是你正在看的那个视图:它的筛选、排序与列都会随导出一起走。 + +### 谁可以导出 + +导出是一项独立权限,不是「能读」的副产品。`allowExport` 是选择性开启的,未设置即**拒绝**;`viewAllRecords` 与 `modifyAllRecords` 都不能替代它。HotCRM 按角色逐一编写了这些授权: + +| 角色 | 可导出的对象 | +| --- | --- | +| 销售代表 | 潜在客户、客户、联系人、商机、工单 | +| 销售经理 | 潜在客户、客户、联系人、商机、工单 | +| 客服专员 | 潜在客户、客户、联系人、工单 | +| 营销用户 | 潜在客户、客户、联系人、商机 | +| 系统管理员 | 潜在客户、客户、联系人、商机、工单 | +| 访客门户 | 没有,这是刻意的 | + +授权只是打开那扇门,它不会放宽行范围。导出是从读派生的,因此记录范围与共享规则仍然叠加在上面——一个销售代表导出商机,拿到的是自己那本账,而不是全公司的。 + +工单虽然带有导出授权,却没有任何工单列表视图声明 `exportOptions`:那条授权面向的是报表导出,而报表导出依托平台的 `reports` 能力。本应用的 `requires` 没有列出它,所以在默认启动下,上面那四个列表视图与数据 API 就是你拥有的导出面。 -从任何列表视图或报表 → **导出** → CSV 或 Excel。受用户权限和 FLS 约束。 +### 通过数据 API 导出 -### 计划导出 +列表视图上的**导出**按钮调用的就是该对象的导出路由,你也可以自己调用它:格式为 `csv`、`xlsx` 或 `json`,并可带 `fields`、`orderby`、`filter`、`search` 与 `limit`。或者用 ObjectQL 查询该对象并分页取回。路由形态请按你所运行的版本、以及身份验证方式,参见 [API 参考](/zh-Hans/docs/customization/api-reference)。 -用于数据仓库加载(Snowflake、BigQuery、Redshift): +### 定时导出到数据仓库(尚未落地) -- **设置 → 数据 → 计划导出** → 选择对象、字段、筛选条件、计划、目标位置。 -- 目标位置:S3 / Azure Blob / GCS / SFTP。 -- 格式:CSV、Parquet 或 JSON Lines。 -- 增量 —— 仅自上次运行以来发生变更的数据。 +设计意图是这样的:选择对象、字段、筛选条件、计划与目标位置;把文件以 CSV、Parquet 或 JSON Lines 落到 S3 / Azure Blob / GCS / SFTP;首次之后转为增量。 -### 批量 API 导出 +这些一概不存在——没有定时导出界面,没有数据仓库目标位置,没有 Parquet 写出器,也没有 `设置 → 数据` 菜单来承载它。今天要么从列表视图手工导出,要么把导出路由挂到你自己掌控的调度上——一个 cron 作业、一个 Lambda、一个 GitHub Action。参见[集成](/zh-Hans/docs/guides/integrations)。 -用于编程式导出 —— 使用 [API 参考](/zh-Hans/docs/customization/api-reference)。用 ObjectQL 查询任何对象;结果分页返回。 +## 数据删除请求(尚未落地) -## GDPR / 数据删除请求 +设计意图是这样的:`设置 → 隐私 → 数据主体请求`,按邮件或联系人 id 识别该人,看到与他关联的全部内容,选择**删除**或**匿名化**(姓名与邮件替换为 `[redacted]`,记录保留以供分析),并交给客户一份审计证书。 -当客户请求 *"删除我的所有个人数据"* 时: +这些一概不存在。Setup 里没有隐私分组,没有数据主体请求对象,没有匿名化操作,也没有审计证书——平台的审计能力并不在本应用启用的那批能力里。 -1. **设置 → 隐私 → 数据主体请求 → 新建**。 -2. 识别该人 —— 按邮件或联系人 ID。 -3. 向导会显示与他们关联的所有内容:联系人记录、潜在客户、工单、已记录的邮件、营销活动成员资格、文件。 -4. 选择 **删除** 或 **匿名化**(用 `[redacted]` 替换姓名/邮件,同时保留记录以供分析)。 -5. 确认 —— 系统会进行处理并生成一份你可以交给客户的**审计证书**。 +今天你能做的,是直接删除或编辑记录: -通常更推荐匿名化 —— 它在不保留 PII 的情况下保持了历史报表的完整性。 +- **删除该联系人。** 指名这个人的参会人行会随之删除;会议本身保留,因此团队的历史不会因此出现空洞。 +- **删除该客户**,它的联系人会级联删除。先看清还有什么挂在它下面——这条级联值得事先规划,而不是事后发现。 +- **手工匿名化**:在你要保留的那条记录上清空可识别字段。没有一键等价物,末尾也不会产出任何证书。 ## 备份与灾难恢复 @@ -150,8 +166,9 @@ description: 从 CSV/Excel 批量导入,从 Salesforce 或 HubSpot 迁移, ## 提示 -- ✅ **始终试运行**大型导入。 -- ✅ **始终添加外部 ID** 列 —— 让你永远摆脱重复记录。 -- ✅ **先在沙盒中迁移**,然后在切换的周末运行最终的增量迁移。 -- ✅ 对于导出,使用**计划导出**而非临时导出 —— 可重复、受监控、失败时会重试。 -- ✅ 对于 GDPR,使用**匿名化**而非删除 —— 让报表保持真实。 +- ✅ **每次导入前都先校验**——向导里的*校验数据*,API 上的 `dryRun`。它只花几秒钟,却是挡在「一列写错」和「五万行」之间的唯一一道东西。 +- ✅ **按依赖顺序装载**——先客户,再联系人,最后潜在客户。 +- ✅ **让业务键去做去重。** 不要自己发明 `external_id` 列;重跑同一个文件本来就是更新而不是重复创建。 +- ✅ **大批量导入前先确认撤销窗口**,如果你要这层回滚保险,就把文件分块。 +- ✅ **谨慎授予 `allowExport`**——它之所以是选择性开启的,正因为它是批量外流,数据会跟着人一起离开。 +- ✅ **把每一个 *(尚未落地)* 小节当作需求文档来读,而不是配置指南**——并关注[路线图](/zh-Hans/docs/whats-new)。 diff --git a/content/docs/guides/import-and-export.zh-Hant.mdx b/content/docs/guides/import-and-export.zh-Hant.mdx index 8271d9541..7e209567b 100644 --- a/content/docs/guides/import-and-export.zh-Hant.mdx +++ b/content/docs/guides/import-and-export.zh-Hant.mdx @@ -1,143 +1,159 @@ --- title: 匯入與匯出 -description: 從 CSV/Excel 批量匯入,從 Salesforce 或 HubSpot 遷移,匯出資料,並處理 GDPR 資料請求。 +description: 資料進出 HotCRM 真正落地的那部分——清單檢視上的匯入精靈與隨附的三個對應、四個清單檢視的 CSV / XLSX 匯出、資料 API——以及哪些遷移精靈仍是設計意圖。 --- # 匯入與匯出 -如何將資料**匯入** HotCRM(批量匯入、遷移)以及**匯出**(匯出、GDPR、備份)。 +如何把資料**匯入** HotCRM,以及如何**匯出**。下文全部按應用當前實際的樣子書寫;標註 *(尚未落地)* 的小節保留的是設計意圖——請把它們當作需求文件來讀,而不是操作步驟。 + +> **不存在「設定 → 資料」選單。** Setup 應用的分組是 *概覽*、*應用*、*人員與組織*、*存取控制*、*簽核*、*設定*、*診斷*、*整合* 和 *進階*。既沒有 **資料** 分組,也沒有 **隱私** 分組。本頁較早的版本幾乎把每一個步驟都指向 `設定 → 資料 → …`,而照著走的人去找的是並不存在的畫面。匯入與匯出都在**物件自己的清單檢視**上;其餘的部分是資料 API。 ## 從 CSV / Excel 匯入 -最簡單的途徑。適用於:潛在客戶、客戶、聯絡人、產品、商機、工單、合約。 +兩條路,都是真的,而且落到同一個地方: + +- **在應用裡**——打開物件的清單檢視,點工具列的**匯入**。適合那種你想先親眼過一遍再寫入的一次性檔案。 +- **透過 API**——匯入端點,帶試算、背景作業與復原。分步說明見[匯入你自己的資料](/zh-Hant/docs/guides/importing-your-data)。 -### 分步操作 +兩條路共用伺服器端同一條匯入通路,因此對應、驗證與逐列錯誤碼都是同一套。 -1. 前往 **設定 → 資料 → 匯入精靈**。 -2. 選擇要匯入到的**物件**。 -3. 上傳 **CSV** 或 **Excel** 檔案(每次上傳最大 50 MB;更大的檔案請拆分)。 -4. **將欄映射**到欄位 —— 精靈會按表頭名稱自動建議比對。 -5. 選擇**衝突行為**: - - **僅插入** —— 跳過與現有記錄比對的列。 - - **插入並更新** —— 更新現有記錄(按郵件 / 外部 ID 比對)。 - - **僅更新** —— 如果未找到則失敗。 -6. 執行**試運行** —— 精靈會報告有多少會成功 / 失敗,而不進行寫入。 -7. **提交** —— 執行匯入。你會看到一個進度條;大型匯入在背景執行,完成後你會收到一封郵件。 +### 應用內的匯入精靈 -### 欄位映射提示 +1. **打開該物件的清單檢視**,點**匯入**。目標物件由你從哪裡進來決定——精靈不會讓你再選一次。 +2. **上傳**——把 CSV 或 Excel 檔案拖進去,或者直接從試算表裡貼上。**下載範本** 會給你一個帶該物件各欄的 CSV,必填欄標著 `*`。 +3. **對應**——各欄按表頭名自動比對到欄位,每條比對都帶一個信心度讀數;其餘的由你手動修正或略過。如果 HotCRM 為該物件隨附了**已儲存的對應**,就直接選它:重新命名、值轉換與型別強制轉換隨後都在伺服器端執行,欄位對應變為唯讀。今天隨附三個——`crm_account_import`、`crm_contact_import` 與 `crm_lead_import`——和 API 那條路點名的是同樣三個。 +4. **匯入選項**——*當某列比對到既有記錄時* 提供 **總是新建**、**更新既有(無比對則略過)** 與 **比對則更新,否則新建**,並由 **比對依據** 決定按哪個(哪些)欄位去比對。已儲存的對應會自帶預設寫入模式與比對鍵,所以在上面那三個物件上你幾乎不需要動它。 +5. **驗證資料**——把每一列拿到伺服器端核對一遍,不寫入任何東西。每次匯入前都做。 +6. **預覽 → 匯入**——大檔案會交給背景作業,並顯示 `正在匯入 n / m 列`。**歷史** 列出該物件最近的匯入作業,並提供**復原匯入**:作業建立的記錄會被刪除,它更新過的記錄會還原到之前的值。 -- **擁有者** —— 提供使用者的郵件地址;匯入器會解析為使用者 ID。 -- **查閱欄位** —— 提供被引用記錄的 *Name* 或 *External ID*;匯入器會進行解析。 -- **選項清單** —— 值必須與選項清單定義的值完全比對(不區分大小寫)。 -- **日期** —— ISO 格式(`2025-03-15`)在任何地方都適用;否則請指定格式。 -- **多選** —— 用分號分隔各值。 +> **匯入完成時不會有郵件送達。** 作業執行時進度顯示在畫面上,跑完後這次執行列在**歷史**裡——通知就到此為止。HotCRM 不寄送任何完成郵件,而平台的郵件服務只有出站方向,且在部署設定傳輸方式之前根本什麼都不投遞。 -### 外部 ID(推薦) +單一作業最多 **50,000 列**;更大的檔案請拆分。復原的上限更緊,另外還有幾個前提條件——見[匯入你自己的資料](/zh-Hant/docs/guides/importing-your-data)。 -新增一個 `external_id` 欄(例如 `legacy_account_id = ACME-001`)並將其標記為比對鍵。這讓你能夠: +### 欄位對應提示 -- 冪等地重新執行匯入而不建立重複記錄。 -- 在物件之間交叉引用(例如,匯入潛在客戶及其原始客戶的外部 ID,匯入器會將它們關聯起來)。 +- **擁有者**——填使用者的郵件地址,它會去解析到使用者記錄。比對不到任何使用者的地址會讓**該列失敗**,錯誤碼 `reference_not_found`;系統不做任何猜測。把儲存格留空,則記錄歸執行匯入的人所有。 +- **查閱欄位**——填被引用記錄的 id,或者那個物件用來標識自己的文字(其顯示欄位,其次是 `name` / `title` / `label` / `full_name` / `email` / `username`)。父記錄即使排在同一個檔案的後面,也仍然能解析到。 +- **選項清單**——按欄位選項的標籤*和*值做比對,不區分大小寫,所以 *Technology* 和 *technology* 都能落地。隨附的對應還額外翻譯其他系統匯出時使用的詞彙——*SaaS*、*Trade Show*、*Client*。仍然認不出來的值會讓該列以 `invalid_option` 失敗,而不是被悄悄丟掉。 +- **日期**——`YYYY-MM-DD` 最穩妥。 +- **數字**——純數字(`1500000`,不是 `1.5M`)。 +- **地址**——聯絡人有真實的通訊地址欄;客戶和潛在客戶把地址存為一個結構化欄位,無法由分散的表格欄拼裝出來。已知限制列在[匯入你自己的資料](/zh-Hant/docs/guides/importing-your-data)。 -## 從 Salesforce 遷移 +### 比對鍵——而不是外部 ID -隨附了一個專門建構的遷移精靈。 +本頁較早的版本讓你加一個 `external_id` 欄並把它標記為比對鍵。**HotCRM 的物件沒有任何一個帶 `external_id` 欄位**,而具名對應是嚴格投影——它沒有宣告的每一欄都會在寫入前被丟棄——所以那一欄只會被忽略。 -1. **設定 → 資料 → 從 Salesforce 遷移**。 -2. 透過 OAuth 授權 —— 精靈會從你的 Salesforce 沙箱或生產環境中讀取。 -3. 精靈會自動將 Salesforce 物件映射到 HotCRM 對應項: +它承諾的冪等性是真的,只不過來自業務鍵: - | Salesforce | HotCRM | - | --- | --- | - | Account | account | - | Contact | contact | - | Lead | lead | - | Opportunity | opportunity | - | OpportunityLineItem | opportunity_line_item | - | Quote / QuoteLineItem | quote / quote_line_item | - | Contract | contract | - | Case | case | - | Campaign / CampaignMember | campaign / campaign_member | - | Product2 | product | - | Task / Event | task / event | - | User | user *(按郵件比對或邀請)* | +| 對應 | 與既有記錄的比對依據 | +| --- | --- | +| `crm_account_import` | 客戶名稱 | +| `crm_contact_import` | 郵件地址 | +| `crm_lead_import` | 郵件地址 | -4. **欄位映射** —— 預設值合理;審閱並調整自訂欄位。 -5. **選項清單映射** —— 將 Salesforce 的值映射到 HotCRM 的值(尤其是階段、狀態、優先級)。 -6. **擁有者映射** —— 按郵件將 Salesforce 使用者映射到 HotCRM 使用者。 -7. **選擇資料切片** —— 全部歷史資料,或一個日期範圍(用於分階段切換)。 -8. **試運行**,審閱報告,**提交**。 +重新執行同一個檔案,比對上的列會被更新而不是重複建立。在精靈裡,**比對依據** 就是同一個想法的手動版。跨物件引用也是同樣的機制:聯絡人用*客戶名稱*指向它的客戶,而不是某個遺留 id。 -精靈會將 Salesforce ID 保留為 `external_id` 以便追溯。 +不過客戶名稱是**逐字**比對的——*Acme Corp* 和 *ACME Corp* 是兩個不同的客戶。 -## 從 HubSpot 遷移 +## 從 Salesforce 遷移(尚未落地) -在 **設定 → 資料 → 從 HubSpot 遷移** 中有一個類似的精靈。將 Hub 物件類型映射到 HotCRM: +設計意圖是這樣的:隨箱提供一個專門建構的遷移精靈。你透過 OAuth 授權到自己的 Salesforce 沙箱或生產環境,精靈自動把 Salesforce 物件對應到 HotCRM 的對應項,你審閱欄位、選項清單與擁有者對應,選擇資料切片(全部歷史,或用於分階段切換的一個日期範圍),試算,然後提交——並保留 Salesforce 的記錄 id 以便追溯。 -| HubSpot | HotCRM | +| Salesforce | HotCRM 對應項(設計意圖) | | --- | --- | -| Company | account | -| Contact | contact | -| Deal | opportunity | -| Ticket | case | -| Line item | opportunity_line_item | -| Product | product | -| Quote | quote | +| Account | `crm_account` | +| Contact | `crm_contact` | +| Lead | `crm_lead` | +| Opportunity | `crm_opportunity` | +| OpportunityLineItem | `crm_opportunity_line_item` | +| Quote / QuoteLineItem | `crm_quote` / `crm_quote_line_item` | +| Contract | `crm_contract` | +| Case | `crm_case` | +| Campaign / CampaignMember | `crm_campaign` / `crm_campaign_member` | +| Product2 | `crm_product` | +| Task / Event | `crm_task` / `crm_event` | +| User | 使用者 *(按郵件比對,或發出邀請)* | + +**這些一樣都沒有落地。** 應用裡沒有遷移精靈,沒有 Salesforce 連接器,也沒有任何一個會去讀 Salesforce 的操作。原始碼裡提到 Salesforce 有十來處,每一處都是把它當作業界設計參照來引用的註解——預測分類階梯、首次回應時間的定義、Lightning 風格的記錄頁版面,以及匯入對應裡那條關於「Salesforce 匯出會寫哪些詞彙」的說明。 + +也沒有對外的 OAuth。Setup 的 **OAuth 應用** 畫面登記的是呼叫*進入* ObjectStack 的用戶端;產品裡沒有任何東西負責讓 HotCRM 去*向別人的組織*取得授權。 -交易管道映射到商機階段 —— 請仔細審閱選項清單映射。 +它甚至也不在路線圖上:路線圖那一行寫的是 *「更多連接器 —— Slack、Microsoft Teams、Google Workspace、HubSpot 匯入」*。 + +**今天該怎麼做。** 從 Salesforce 匯出 CSV,用上文的匯入把它裝進來。隨附的對應已經會說這門方言的一部分——*SaaS* 與 *Financial Services* 會變成 *Software / SaaS* 與 *Finance*,*Trade Show* 變成 *Event / Trade Show*,*Client* 變成客戶類型的客戶——所以客戶、聯絡人與潛在客戶這一段,通常只需要正確的表頭列。沒有隨附對應的物件,走精靈裡的手動欄位對應,或者走資料 API。 + +## 從 HubSpot 遷移(尚未落地) + +同樣的情況,但有一點不同:**HubSpot 匯入是路線圖上點名的一項**,所以這一條是排上了日程,而不只是一個願望。 + +| HubSpot | HotCRM 對應項(設計意圖) | +| --- | --- | +| Company | `crm_account` | +| Contact | `crm_contact` | +| Deal | `crm_opportunity` | +| Ticket | `crm_case` | +| Line item | `crm_opportunity_line_item` | +| Product | `crm_product` | +| Quote | `crm_quote` | + +交易管道會對應到商機階段,那是最值得手動審閱的一處對應。在它落地之前,上文的 CSV 路線就是遷移路徑。 ## 遷移操作手冊 -1. **首先映射角色和使用者** —— 沒有擁有者,一切都會成為孤立資料。 -2. **在沙箱中試運行** —— 切勿對生產環境執行首次遷移。 -3. **按此順序遷移**以保持引用關係完整: - 1. 使用者 - 2. 產品 - 3. 客戶(+ 外部 ID) - 4. 聯絡人(+ 關聯到客戶) - 5. 潛在客戶 - 6. 商機(+ 關聯到客戶 + 聯絡人) - 7. 商機行項目 - 8. 報價 / 報價行項目 - 9. 合約 - 10. 工單 - 11. 行銷活動 - 12. 行銷活動成員 - 13. 任務 / 事件 / 備註 / 檔案 -4. **核對數量** —— 來源系統的總數 vs. HotCRM。調查任何 >0.1% 的差距。 -5. **端到端手動抽查 10 條記錄**。 -6. 在上線前**獲得**銷售 / 服務負責人的**簽字核准**。 +依然是對的建議——只是現在它是關於 CSV 這條路的建議。 + +1. **先把使用者建好。** 匯入器從不建立使用者,而一個比對不到任何人的擁有者郵件儲存格會讓該列失敗。先把使用者裝進 HotCRM(或者清空擁有者欄、匯入後再分配),然後再做別的。 +2. **按相依順序載入**——先客戶,再聯絡人,最後潛在客戶。每個聯絡人都要指名一個必須已經存在的客戶。除這三者之外也守同一條原則:指向別人的記錄排在後面。 +3. **寫入前先驗證**,每次都做。精靈裡的*驗證資料*,API 上的 `dryRun`。報告裡的 `row` 就是你檔案裡的列號,改好表格再跑一遍即可。 +4. **核對數量**——來源系統的總數與 HotCRM 的總數對一對,有差距就去查。 +5. **手動端到端抽查十筆記錄。** 數量對上,不等於欄位都落對了。 +6. **動手前先弄清你的復原視窗。** 復原是按作業來的,而且涵蓋不了大作業,所以如果你要這層保險,就把大資料集分塊匯入。 ## 匯出資料 -### 臨時匯出 +### 從清單檢視匯出 + +有四個清單檢視宣告了 `exportOptions: ['csv', 'xlsx']`——**客戶**、**聯絡人**、**潛在客戶** 與 **商機**。打開其中之一,用工具列的**匯出**,選 CSV 或 Excel。你拿到的就是你正在看的那個檢視:它的篩選、排序與欄位都會隨匯出一起走。 + +### 誰可以匯出 + +匯出是一項獨立權限,不是「能讀」的副產品。`allowExport` 是選擇性開啟的,未設定即**拒絕**;`viewAllRecords` 與 `modifyAllRecords` 都不能替代它。HotCRM 按角色逐一撰寫了這些授權: + +| 角色 | 可匯出的物件 | +| --- | --- | +| 業務代表 | 潛在客戶、客戶、聯絡人、商機、工單 | +| 業務經理 | 潛在客戶、客戶、聯絡人、商機、工單 | +| 客服專員 | 潛在客戶、客戶、聯絡人、工單 | +| 行銷使用者 | 潛在客戶、客戶、聯絡人、商機 | +| 系統管理員 | 潛在客戶、客戶、聯絡人、商機、工單 | +| 訪客入口 | 沒有,這是刻意的 | + +授權只是打開那扇門,它不會放寬列範圍。匯出是從讀衍生的,因此記錄範圍與共享規則仍然疊加在上面——一個業務代表匯出商機,拿到的是自己那本帳,而不是全公司的。 + +工單雖然帶有匯出授權,卻沒有任何工單清單檢視宣告 `exportOptions`:那條授權面向的是報表匯出,而報表匯出依託平台的 `reports` 能力。本應用的 `requires` 沒有列出它,所以在預設啟動下,上面那四個清單檢視與資料 API 就是你擁有的匯出面。 -從任何清單檢視或報表 → **匯出** → CSV 或 Excel。受使用者權限和 FLS 約束。 +### 透過資料 API 匯出 -### 排程匯出 +清單檢視上的**匯出**按鈕呼叫的就是該物件的匯出路由,你也可以自己呼叫它:格式為 `csv`、`xlsx` 或 `json`,並可帶 `fields`、`orderby`、`filter`、`search` 與 `limit`。或者用 ObjectQL 查詢該物件並分頁取回。路由形態請按你所執行的版本、以及身分驗證方式,參見 [API 參考](/zh-Hant/docs/customization/api-reference)。 -用於資料倉儲載入(Snowflake、BigQuery、Redshift): +### 排程匯出到資料倉儲(尚未落地) -- **設定 → 資料 → 排程匯出** → 選擇物件、欄位、篩選條件、排程、目標位置。 -- 目標位置:S3 / Azure Blob / GCS / SFTP。 -- 格式:CSV、Parquet 或 JSON Lines。 -- 增量 —— 僅自上次執行以來發生變更的資料。 +設計意圖是這樣的:選擇物件、欄位、篩選條件、排程與目標位置;把檔案以 CSV、Parquet 或 JSON Lines 落到 S3 / Azure Blob / GCS / SFTP;首次之後轉為增量。 -### 批量 API 匯出 +這些一概不存在——沒有排程匯出畫面,沒有資料倉儲目標位置,沒有 Parquet 寫出器,也沒有 `設定 → 資料` 選單來承載它。今天要麼從清單檢視手動匯出,要麼把匯出路由掛到你自己掌控的排程上——一個 cron 作業、一個 Lambda、一個 GitHub Action。參見[整合](/zh-Hant/docs/guides/integrations)。 -用於程式化匯出 —— 使用 [API 參考](/zh-Hant/docs/customization/api-reference)。用 ObjectQL 查詢任何物件;結果分頁返回。 +## 資料刪除請求(尚未落地) -## GDPR / 資料刪除請求 +設計意圖是這樣的:`設定 → 隱私 → 資料主體請求`,按郵件或聯絡人 id 識別該人,看到與他關聯的全部內容,選擇**刪除**或**匿名化**(姓名與郵件替換為 `[redacted]`,記錄保留以供分析),並交給客戶一份稽核憑證。 -當客戶請求 *「刪除我的所有個人資料」* 時: +這些一概不存在。Setup 裡沒有隱私分組,沒有資料主體請求物件,沒有匿名化操作,也沒有稽核憑證——平台的稽核能力並不在本應用啟用的那批能力裡。 -1. **設定 → 隱私 → 資料主體請求 → 新增**。 -2. 識別該人 —— 按郵件或聯絡人 ID。 -3. 精靈會顯示與他們關聯的所有內容:聯絡人記錄、潛在客戶、工單、已記錄的郵件、行銷活動成員資格、檔案。 -4. 選擇 **刪除** 或 **匿名化**(用 `[redacted]` 替換姓名/郵件,同時保留記錄以供分析)。 -5. 確認 —— 系統會進行處理並生成一份你可以交給客戶的**稽核憑證**。 +今天你能做的,是直接刪除或編輯記錄: -通常更推薦匿名化 —— 它在不保留 PII 的情況下保持了歷史報表的完整性。 +- **刪除該聯絡人。** 指名這個人的與會人列會隨之刪除;會議本身保留,因此團隊的歷史不會因此出現空洞。 +- **刪除該客戶**,它的聯絡人會級聯刪除。先看清還有什麼掛在它下面——這條級聯值得事先規劃,而不是事後發現。 +- **手動匿名化**:在你要保留的那筆記錄上清空可識別欄位。沒有一鍵等價物,末尾也不會產出任何憑證。 ## 備份與災難復原 @@ -150,8 +166,9 @@ description: 從 CSV/Excel 批量匯入,從 Salesforce 或 HubSpot 遷移, ## 提示 -- ✅ **始終試運行**大型匯入。 -- ✅ **始終新增外部 ID** 欄 —— 讓你永遠擺脫重複記錄。 -- ✅ **先在沙箱中遷移**,然後在切換的週末執行最終的增量遷移。 -- ✅ 對於匯出,使用**排程匯出**而非臨時匯出 —— 可重複、受監控、失敗時會重試。 -- ✅ 對於 GDPR,使用**匿名化**而非刪除 —— 讓報表保持真實。 +- ✅ **每次匯入前都先驗證**——精靈裡的*驗證資料*,API 上的 `dryRun`。它只花幾秒鐘,卻是擋在「一欄寫錯」和「五萬列」之間的唯一一道東西。 +- ✅ **按相依順序載入**——先客戶,再聯絡人,最後潛在客戶。 +- ✅ **讓業務鍵去做去重。** 不要自己發明 `external_id` 欄;重跑同一個檔案本來就是更新而不是重複建立。 +- ✅ **大批量匯入前先確認復原視窗**,如果你要這層回滾保險,就把檔案分塊。 +- ✅ **謹慎授予 `allowExport`**——它之所以是選擇性開啟的,正因為它是批量外流,資料會跟著人一起離開。 +- ✅ **把每一個 *(尚未落地)* 小節當作需求文件來讀,而不是設定指南**——並關注[路線圖](/zh-Hant/docs/whats-new)。 From 004d2d757e320e2b3e114268743c1a1a54dcb33f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 20:29:16 +0000 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20=E6=8A=8A=20changeset=20=E9=87=8C?= =?UTF-8?q?=E7=9A=84=E7=AB=99=E5=86=85=E8=B7=AF=E5=BE=84=E4=BB=8E=20markdo?= =?UTF-8?q?wn=20=E9=93=BE=E6=8E=A5=E6=94=B9=E4=B8=BA=E7=BA=AF=E6=96=87?= =?UTF-8?q?=E6=9C=AC=20(#763)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI 的 link-check(gaurav-nelson/github-action-markdown-link-check)扫描面是 `.md`,`.changeset/*.md` 在内;它把 `[...](...)` 里的路径当 URL 校验,而 `/docs/guides/importing-your-data` 这样的站内根相对路径无法解析 → Status: 400 / ERROR: 1 dead links found!。config 的 ignorePatterns 也没有 覆盖根相对路径。 本地首轮只核了 .mdx 页,因此没撞上这条。改为反引号纯文本;三个 mdx 页里的 站内链接不受影响(那些是 .mdx,不在该 action 的扫描面内)。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa --- .changeset/import-export-guide-measured.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/import-export-guide-measured.md b/.changeset/import-export-guide-measured.md index 86f5fc393..a3ac318bf 100644 --- a/.changeset/import-export-guide-measured.md +++ b/.changeset/import-export-guide-measured.md @@ -25,7 +25,7 @@ Measured, and restated: import**. It reads HotCRM's own saved mappings — the wizard lists the mappings whose `targetObject` is the object you are on, so `crm_account_import`, `crm_contact_import` and `crm_lead_import` are the same three the API path in - [Import your own data](/docs/guides/importing-your-data) names. The two pages + the `guides/importing-your-data` page names. The two pages now describe one feature from two ends and link to each other. - **No completion email.** Progress is on screen and the run lands in History; the platform email service is outbound-only and delivers nothing until a