简介这份PDF文档是蜂网供应链管理上海有限公司于2017年8月发布的《客户关系管理CRM_客户管理_软件需求说明书_V1.0》面向产品经理、需求分析师及CRM系统开发人员用于指导客户管理模块的设计、开发与测试。文档由周旭丽编写共54页系统梳理了项目背景、需求来源、项目目标与目标顾客并涵盖平台政策、竞品分析、名词解释、业务流程、系统用例、角色用例、功能列表及基本流程等核心章节对客户档案、交互历史、销售漏斗等关键概念均有定义。资源包为单一PDF文件大小约3.11MB结构完整、目录清晰便于按章节检索与对照阅读。目前已有312人学习下载适合需要撰写CRM需求文档、理解客户管理业务逻辑或开展同类系统设计的技术与产品人员参考可帮助读者快速掌握需求说明书的编写框架与功能拆解思路。1. 一份 2017 年的 CRM 客户管理 PRD今天翻出来还能怎么用手里拿到一份名为「客户关系管理CRM-客户管理-PRD-V1.0-20170825.pdf」的文档第一反应往往不是「这玩意儿过时了」而是「客户管理这块到底要拆成几个模块、字段怎么定、状态机怎么流转」。2017 年正是国内 SaaS CRM 从「通讯录跟进记录」往「销售漏斗线索评分」过渡的阶段这份 PRD 的骨架放到今天依然能跑通大部分中小团队的客户管理需求。它解决的核心问题很具体把客户从线索到成交再到复购的全生命周期拆成可配置的字段、可流转的状态和可统计的视图。适合谁看正在从零搭 CRM 的后端、需要给销售团队定字段规范的产品、以及接手老系统要做数据迁移的工程师。别急着照抄先看它怎么把「客户」这个对象拆干净。2. 客户管理模块的字段与状态机怎么拆才不返工2.1 客户主表到底该放哪些字段一份 2017 年的客户管理 PRD客户主表通常围绕「身份 归属 阶段 来源」四类字段展开。身份类包括客户名称、统一社会信用代码、联系人、电话、地址归属类包括所属销售、所属团队、所属区域阶段类包括客户状态、生命周期阶段、最近跟进时间来源类包括渠道来源、首次接触方式、转化活动。这四类字段的拆分逻辑是身份类用于去重和检索归属类用于权限隔离和业绩归属阶段类用于漏斗统计来源类用于渠道 ROI 分析。实际落地时最容易返工的是「客户名称」和「联系人」的关系。很多团队一开始把联系人信息直接平铺在客户表里结果一个客户有多个联系人时只能加字段加到第三个联系人就崩了。正确做法是客户主表只保留「主联系人」冗余字段完整联系人列表放子表通过 customer_id 关联。下面是一个最小可用的建表 SQL字段命名沿用了 PRD 里常见的下划线风格CREATE TABLE crm_customer ( id BIGINT PRIMARY KEY AUTO_INCREMENT, customer_name VARCHAR(128) NOT NULL COMMENT 客户名称用于去重, credit_code VARCHAR(32) DEFAULT NULL COMMENT 统一社会信用代码, owner_id BIGINT NOT NULL COMMENT 所属销售ID, team_id BIGINT DEFAULT NULL COMMENT 所属团队ID, region_code VARCHAR(16) DEFAULT NULL COMMENT 区域编码, stage TINYINT NOT NULL DEFAULT 1 COMMENT 1线索 2意向 3报价 4成交 5复购 6流失, source_channel VARCHAR(32) DEFAULT NULL COMMENT 渠道来源, last_follow_at DATETIME DEFAULT NULL COMMENT 最近跟进时间, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_name_credit (customer_name, credit_code), KEY idx_owner_stage (owner_id, stage), KEY idx_last_follow (last_follow_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT客户主表;逻辑说明uk_name_credit用客户名称加信用代码做联合唯一是为了防止同一客户被不同销售重复录入idx_owner_stage支撑「我的客户按阶段筛选」这个最高频查询idx_last_follow支撑「超过 N 天未跟进」的提醒任务。参数上stage用 TINYINT 而不是 ENUM是因为 ENUM 加值要改表结构TINYINT 配合字典表更灵活。last_follow_at冗余在客户表是为了避免每次列表页都去跟进记录表做 MAX 聚合。2.2 客户状态机怎么定义流转规则PRD 里客户状态不是随便改的必须有流转规则。2017 年常见的做法是画一张状态流转图落到代码里就是一个校验函数。状态从「线索」到「意向」需要至少一条有效跟进记录从「意向」到「报价」需要关联商机从「报价」到「成交」需要合同编号从「成交」到「复购」需要新订单任意状态到「流失」需要填写流失原因。这套规则的价值在于防止销售为了冲阶段数据乱改状态同时让漏斗统计有可信度。下面是一个状态流转校验的 Python 实现用字典定义允许的流转路径避免写一长串 if-else# 客户状态流转校验 STAGE_FLOW { 1: [2, 6], # 线索 - 意向 / 流失 2: [3, 6], # 意向 - 报价 / 流失 3: [4, 6], # 报价 - 成交 / 流失 4: [5, 6], # 成交 - 复购 / 流失 5: [6], # 复购 - 流失 6: [], # 流失为终态 } def can_transition(from_stage: int, to_stage: int, context: dict) - tuple: 返回 (是否允许, 原因) if to_stage not in STAGE_FLOW.get(from_stage, []): return False, f不允许从 {from_stage} 直接流转到 {to_stage} # 附加业务校验 if to_stage 2 and context.get(follow_count, 0) 1: return False, 转意向需要至少一条跟进记录 if to_stage 3 and not context.get(opportunity_id): return False, 转报价需要关联商机 if to_stage 4 and not context.get(contract_no): return False, 转成交需要合同编号 if to_stage 6 and not context.get(lost_reason): return False, 转流失需要填写流失原因 return True, ok逻辑说明STAGE_FLOW把流转规则集中在一处新增状态只改这个字典。context参数承载附加校验所需的数据调用方在改状态前先查好跟进数、商机、合同、流失原因避免校验函数里再查库。参数上follow_count建议从跟进记录表实时 count不要用冗余字段因为跟进记录可能被删除。这个函数返回元组而不是抛异常是为了让上层能直接把原因返回给前端提示。2.3 客户查重与合并的落地做法客户重复是 CRM 里最头疼的问题之一。2017 年的 PRD 通常要求「客户名称 电话」或「客户名称 信用代码」做查重。落地时不能只靠数据库唯一索引因为销售录入时名称可能带空格、括号、后缀差异。常见做法是入库前先做一次标准化去掉空格和全半角差异再用标准化后的名称加电话查重如果命中返回疑似重复列表让用户确认而不是直接拒绝。import re def normalize_name(name: str) - str: 客户名称标准化去空格、全角转半角、去常见后缀 if not name: return # 全角转半角 result [] for ch in name: code ord(ch) if code 0x3000: code 32 elif 0xFF01 code 0xFF5E: code - 0xFEE0 result.append(chr(code)) s .join(result) s re.sub(r\s, , s) # 去掉常见公司后缀避免「XX有限公司」和「XX」被判为不同 for suffix in [有限公司, 股份有限公司, 有限责任公司, 公司]: if s.endswith(suffix): s s[: -len(suffix)] break return s.lower()逻辑说明全角转半角处理的是销售从 Excel 粘贴时常见的全角字符去后缀是为了让「某科技」和「某科技有限公司」能命中同一客户。参数上后缀列表要按长度倒序匹配否则「有限公司」会先被「公司」截断。这个函数只用于查重不用于存储存储仍保留原始名称。合并时把重复客户的跟进记录、商机、合同全部迁移到主客户重复客户标记为「已合并」并保留映射关系方便回溯。3. 从 PRD 到可运行原型客户管理接口与权限怎么落地3.1 客户列表接口的分页与筛选设计PRD 里客户列表是使用频率最高的页面接口设计直接决定前端体验。2017 年常见的是「页码 每页条数」分页但客户数据量大时深分页会慢。更稳的做法是用「游标分页」以last_follow_at或id作为游标。筛选条件通常包括所属销售、客户阶段、来源渠道、最近跟进时间范围、关键词。关键词要支持客户名称和联系人电话的模糊匹配。-- 游标分页 多条件筛选 SELECT id, customer_name, owner_id, stage, source_channel, last_follow_at FROM crm_customer WHERE owner_id ? AND stage ? AND source_channel ? AND last_follow_at ? AND (customer_name LIKE CONCAT(%, ?, %) OR credit_code ?) AND id ? -- 游标上一页最后一条的 id ORDER BY id ASC LIMIT ?;逻辑说明id ?是游标分页的核心避免 OFFSET 深分页扫描。credit_code ?用等值而不是 LIKE因为信用代码是精确匹配。参数上LIMIT建议固定 20 或 50不要开放给前端任意传防止一次拉太多。如果关键词命中信用代码直接走等值查询比 LIKE 快一个量级。索引上(owner_id, stage, id)联合索引能覆盖大部分筛选场景。3.2 基于角色的客户数据权限控制客户管理绕不开权限销售只能看自己的客户主管能看团队的总监能看全公司的。2017 年 PRD 里通常写「数据权限按角色和团队层级控制」。落地时不要在每个查询里手写权限条件而是用一个权限上下文对象统一注入。常见做法是用户登录后计算出一个data_scope包含可见的 owner_id 列表或团队 id 列表查询时统一拼到 WHERE 里。class DataScope: 客户数据权限范围 def __init__(self, user): self.user user self.owner_ids [] self.team_ids [] self._build() def _build(self): role self.user.get(role) if role sales: self.owner_ids [self.user[id]] elif role manager: self.team_ids [self.user[team_id]] elif role director: self.team_ids self.user.get(sub_team_ids, []) # admin 不加限制 def apply(self, query: str, params: list) - tuple: if self.owner_ids: query AND owner_id IN (%s) % ,.join([%s] * len(self.owner_ids)) params.extend(self.owner_ids) elif self.team_ids: query AND team_id IN (%s) % ,.join([%s] * len(self.team_ids)) params.extend(self.team_ids) return query, params逻辑说明_build在登录时执行一次把权限范围算好缓存到 session避免每次查询都递归查团队树。apply方法把权限条件拼到已有查询后面调用方不用关心权限逻辑。参数上sub_team_ids建议在团队表里维护一个「路径」字段如/1/3/7/用 LIKE 前缀匹配查子团队比递归查询快。注意 IN 的参数个数要控制超过 1000 个建议改成临时表关联。3.3 客户跟进记录的写入与提醒任务跟进记录是客户管理的血液。PRD 里通常要求每次跟进写一条记录包含跟进方式、跟进内容、下次跟进时间系统根据下次跟进时间生成提醒。落地时跟进记录表要冗余customer_id和owner_id方便按客户和按人查询。提醒任务用定时任务扫描next_follow_at到期的记录生成站内信或推送。CREATE TABLE crm_follow_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, customer_id BIGINT NOT NULL, owner_id BIGINT NOT NULL, follow_type TINYINT NOT NULL COMMENT 1电话 2拜访 3邮件 4微信, content TEXT, next_follow_at DATETIME DEFAULT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_customer (customer_id, created_at), KEY idx_next_follow (next_follow_at, owner_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT跟进记录表;逻辑说明idx_customer支撑客户详情页按时间倒序拉跟进记录idx_next_follow支撑提醒任务扫描。参数上next_follow_at允许为空表示不需要提醒。提醒任务建议每 10 分钟跑一次扫描next_follow_at BETWEEN NOW() AND NOW() INTERVAL 10 MINUTE的记录避免重复提醒要加一个reminded标记位。写入跟进记录时同步更新客户主表的last_follow_at用事务保证一致性。4. 客户管理落地时最容易踩的五个坑4.1 坑一客户名称唯一索引导致录入失败现象销售录入「某科技上海有限公司」系统提示重复但查不到已有客户。原因唯一索引用了原始名称而库里已有一条「某科技(上海)有限公司」括号全半角不同。解决唯一索引改为「标准化名称 信用代码」标准化函数在应用层做入库前先算好存到一个name_norm字段唯一索引建在name_norm上。4.2 坑二状态流转没有留痕出问题查不到谁改的现象客户阶段从「报价」变回「意向」销售互相甩锅。原因状态更新只改了主表字段没写变更日志。解决加一张crm_customer_stage_log表每次状态变更写一条记录 from_stage、to_stage、操作人、时间、原因。这张表同时是漏斗转化分析的原始数据。4.3 坑三权限用 LIKE 拼团队路径导致越权现象销售能看到其他团队的客户。原因团队路径用 LIKE 匹配时/1/3/会匹配到/1/30/。解决路径字段首尾都加分隔符查询用LIKE /1/3/%或者直接用FIND_IN_SET配合团队 id 列表。更稳的做法是权限范围在应用层算好 owner_id 列表不依赖 SQL 字符串匹配。4.4 坑四跟进提醒重复推送现象同一个客户连续收到多条跟进提醒。原因定时任务扫描时没有标记已提醒或者任务执行时间重叠。解决跟进记录表加reminded_at字段扫描时用UPDATE ... SET reminded_at NOW() WHERE reminded_at IS NULL AND next_follow_at NOW()的原子操作先占位再发提醒。任务加分布式锁防止多实例同时跑。4.5 坑五客户合并后关联数据丢失现象合并两个重复客户后被合并客户的商机和合同不见了。原因合并只改了客户主表没迁移子表数据。解决合并操作在一个事务里完成按「跟进记录 → 商机 → 合同 → 联系人」的顺序批量更新customer_id全部成功才提交。合并前先做一次关联数据量统计数据量大时改成异步任务避免长事务锁表。5. 用一份 PRD 反推数据模型从字段清单到 ER 关系的检查方法拿到任何一份客户管理 PRD我习惯先做一件事把文档里出现的所有名词圈出来按「主数据 / 交易数据 / 行为数据」分三类。主数据是客户、联系人、产品交易数据是商机、报价、合同、订单行为数据是跟进记录、状态变更日志、登录日志。分完类ER 关系基本就出来了主数据之间是一对多或一对一交易数据挂在主数据上行为数据挂在主数据和交易数据上。这个方法能快速发现 PRD 里漏掉的表比如很多 PRD 只写了客户和跟进记录没写状态变更日志但漏斗分析又需要它。检查字段时重点看三类字段有没有定义清楚状态字段的枚举值和流转规则、金额字段的币种和精度、时间字段的时区和业务含义。2017 年的 PRD 经常把金额写成「DECIMAL(10,2)」但没写币种多币种场景下会出问题。时间字段要区分「业务时间」和「系统时间」比如last_follow_at是业务时间updated_at是系统时间统计时用哪个要明确。下面这张表是我常用的字段检查清单对着 PRD 逐项过一遍能省掉后期大量改表检查项常见问题建议做法状态字段枚举值散落在文档各处集中到字典表代码里用常量金额字段没写币种和精度DECIMAL(18,4) currency_code时间字段业务时间和系统时间混用分开命名统计用业务时间关联字段只写 customer_id 没写索引所有外键字段建索引软删除直接物理删除导致数据丢失加 is_deleted deleted_at扩展字段每来一个需求加一列预留 ext_json 字段存非结构化数据最后说一个我自己的习惯PRD 里的每个字段我都会问一句「这个字段谁来写、什么时候写、写错了怎么改」。如果三个问题答不上来这个字段大概率是拍脑袋加的先不建等真有需求再加。客户管理系统的表结构一旦上线改起来成本很高宁可前期多花两小时过一遍字段清单也别后期花两天做数据迁移。希望帮到你。本文还有配套的精品资源点击获取