
简介这份CRM客户管理系统功能清单以doc文档形式呈现面向企业信息化选型人员、CRM产品经理及销售管理岗从业者用于梳理客户管理系统的完整功能边界与模块划分。压缩包内共1个doc文件约376KB内容以功能条目与三级编码结构为主便于直接对照查阅。文档按计划管理、销售合同管理、开票管理、帐款管理、业绩考核管理、市场信息、促销品广告、统计报表、公共关系管理九大模块展开每个功能点均标注编号与操作说明如销售计划维护、合同变更历史查询、调拨与税票对帐、月末结账与反结、业绩考核查询等覆盖从计划、合同、开票到回款、费用、广告的完整业务链路。已有800人学习适合作为CRM需求调研、功能对标或系统设计阶段的参考底稿。1. 从一张 Excel 到一套系统CRM 客户管理到底该有哪些功能很多团队做 CRM 客户管理起点都是一张共享 Excel销售把客户名、电话、跟进状态填进去主管每周导出一次做透视表。前三个月还能跑一旦销售超过五个人、客户超过两千条这张表就开始失控——两个人同时改一行会互相覆盖字段越加越乱谁最后跟进过全靠翻聊天记录。CRM 管理系统功能清单要解决的正是把这张表升级成一套有权限、有流程、有数据约束的系统。它适合两类人一类是正在选型、需要拿清单去比对产品的业务负责人另一类是准备自研、需要知道每个模块背后数据模型怎么设计的开发者。下面我按一套可落地的功能清单往下拆重点讲每个模块为什么存在、表怎么建、接口怎么定。2. 客户主数据与联系人功能清单的地基怎么打CRM 里所有功能都挂在“客户”这个实体上所以第一件事不是画页面而是把客户主数据模型定死。很多系统后期改不动就是因为一开始把客户和联系人揉在一张表里一个客户多个联系人时只能加contact2_phone这种字段越加越离谱。2.1 客户、联系人、商机三张核心表的关系常见做法是拆成三张表客户account代表一家公司或一个购买主体联系人contact代表具体的人商机opportunity代表一次可能成交的机会。一个客户可以有多个联系人一个客户也可以有多个商机联系人和商机之间通过关联表挂接。这样拆的好处是人员离职换联系人不影响客户归属一个客户复购直接新建商机而不是复制客户。下面是最小可用的建表语句字段做了精简但保留了关键约束-- 客户主表一个购买主体一行 CREATE TABLE crm_account ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(128) NOT NULL, -- 客户名称必填 owner_id BIGINT NOT NULL, -- 归属销售决定数据权限 industry VARCHAR(64), -- 行业用于分层 level TINYINT DEFAULT 1, -- 客户等级 1-5 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_name_owner (name, owner_id) -- 同一销售下客户名不重复 ); -- 联系人表挂在客户下一个客户可多条 CREATE TABLE crm_contact ( id BIGINT PRIMARY KEY AUTO_INCREMENT, account_id BIGINT NOT NULL, -- 外键指向客户 name VARCHAR(64) NOT NULL, phone VARCHAR(32), email VARCHAR(128), is_primary TINYINT DEFAULT 0, -- 是否主联系人 KEY idx_account (account_id) ); -- 商机表一次成交机会 CREATE TABLE crm_opportunity ( id BIGINT PRIMARY KEY AUTO_INCREMENT, account_id BIGINT NOT NULL, contact_id BIGINT, -- 对接人可为空 stage VARCHAR(32) NOT NULL, -- 阶段初步/方案/报价/赢单/输单 amount DECIMAL(14,2) DEFAULT 0, -- 预计金额 owner_id BIGINT NOT NULL, KEY idx_account_stage (account_id, stage) );逻辑说明owner_id是整套 CRM 数据权限的根后面所有“销售只能看自己客户”的规则都靠它过滤。uk_name_owner这个唯一键是血泪经验——没有它销售会重复录入同一个客户后期合并数据非常痛苦。stage用字符串而不是枚举是为了让业务能自己加阶段代价是需要在应用层做校验。参数上level用 TINYINT 而不是字符串方便做区间查询amount用 DECIMAL 而不是 FLOAT金额计算不能有浮点误差。联系人表里的is_primary保证一个客户在列表页能显示一个主联系方式避免展示时随机取一条。2.2 客户查重与合并上线前必须做的两个接口客户重复是 CRM 最常见的脏数据。功能清单里必须包含“查重”和“合并”两个动作否则系统跑半年就得停机清洗。查重一般按名称、电话、税号做模糊匹配合并则是把重复客户的联系人、商机、跟进记录迁移到主客户下再软删除被合并方。# 客户查重按名称相似 电话精确双通道 def find_duplicate_accounts(name, phone, owner_id): # 通道一同销售下电话完全一致直接判定重复 exact db.query( SELECT id, name FROM crm_account WHERE owner_id %s AND id IN (SELECT account_id FROM crm_contact WHERE phone %s), (owner_id, phone) ) if exact: return exact # 通道二名称去空格后相似度大于 0.8 candidates db.query( SELECT id, name FROM crm_account WHERE owner_id %s, (owner_id,) ) result [] for row in candidates: if similar(strip(name), strip(row[name])) 0.8: result.append(row) return result逻辑说明先走电话精确匹配因为电话重复几乎可以确定是同一主体速度快且误判低再走名称相似度兜底处理“某某科技”和“某某科技有限公司”这类情况。similar可以用编辑距离或分词后的 Jaccard 相似度阈值 0.8 是我在几个项目里试出来比较平衡的值调低会误报调高会漏报。合并接口要注意不要物理删除被合并客户而是打一个merged_into字段指向主客户这样历史跟进记录还能追溯。合并时商机的account_id要批量更新联系人的account_id同理操作放在一个事务里失败就整体回滚。3. 销售流程与跟进记录让功能清单跑起来客户数据建好只是静态的CRM 真正产生价值是在销售流程和跟进记录上。这一块的功能清单如果只写“支持跟进”落地时一定扯皮——跟进到底记什么、阶段怎么流转、提醒什么时候发都得有明确规则。3.1 销售阶段流转状态机比自由字段可靠商机的stage字段如果允许销售随便改数据就没法做预测。常见做法是定义一个状态机规定只能从“初步”到“方案”到“报价”到“赢单/输单”不能跳级也不能回退回退需要主管审批。状态机可以用配置表驱动避免硬编码。// 阶段流转规则key 是当前阶段value 是允许的下一阶段 const STAGE_FLOW { 初步: [方案, 输单], 方案: [报价, 输单], 报价: [赢单, 输单], 赢单: [], 输单: [] }; // 校验流转是否合法 function canTransfer(from, to) { const allowed STAGE_FLOW[from] || []; return allowed.includes(to); } // 流转时写一条历史便于统计各阶段停留时长 async function transferStage(oppId, toStage, operatorId) { const opp await getOpportunity(oppId); if (!canTransfer(opp.stage, toStage)) { throw new Error(不允许从 ${opp.stage} 流转到 ${toStage}); } await db.transaction(async (tx) { await tx.update(crm_opportunity, { stage: toStage }, { id: oppId }); await tx.insert(crm_stage_history, { opp_id: oppId, from_stage: opp.stage, to_stage: toStage, operator_id: operatorId }); }); }逻辑说明STAGE_FLOW把规则集中在一处业务要加阶段只改这个对象。每次流转写crm_stage_history后期就能算“平均多少天从方案走到报价”这是销售管理最想要的指标。参数上operatorId必须记录否则回退操作查不到责任人。注意状态机不要做成完全不可逆实际业务里销售经常需要回退修改信息。我的做法是允许回退但要求填原因原因字段进历史表主管在报表里能看到回退次数异常的商机。3.2 跟进记录结构化字段比纯文本有用跟进记录如果只给一个富文本框后期没法分析。功能清单里应该把跟进拆成“跟进方式电话/拜访/邮件 跟进结果 下次跟进时间 备注”四个部分。其中“下次跟进时间”是驱动提醒的关键。CREATE TABLE crm_follow_up ( id BIGINT PRIMARY KEY AUTO_INCREMENT, account_id BIGINT NOT NULL, opportunity_id BIGINT, way VARCHAR(16) NOT NULL, -- phone/visit/email result VARCHAR(32), -- 已接通/未接通/有意向等 next_time DATETIME, -- 下次跟进时间用于提醒 remark TEXT, owner_id BIGINT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_next_time (next_time), -- 提醒任务扫描用 KEY idx_account (account_id) );逻辑说明idx_next_time是给定时任务用的每分钟扫一次next_time落在未来五分钟内的记录推送给对应owner_id。way和result用短字符串而不是枚举方便业务扩展但要在应用层做白名单校验。参数上next_time允许为空表示不需要提醒opportunity_id允许为空表示这次跟进只针对客户不针对具体商机。提醒推送建议做成站内信加邮件双通道只做站内信的话销售不看系统就漏掉了。4. 权限、报表与集成功能清单里最容易被低估的部分前几章讲的是业务功能这一章讲支撑功能。很多 CRM 项目失败不是因为业务功能少而是权限太粗、报表太慢、集成太封闭。这三块在功能清单里往往只有一行字实际工作量能占一半。4.1 数据权限按角色和归属两个维度控制CRM 的数据权限通常有两个维度角色决定能看哪些字段归属决定能看哪些行。比如销售只能看自己owner_id的客户主管能看团队成员的管理员看全部。字段级权限则控制金额、成本这类敏感字段对普通销售隐藏。# 数据行权限根据当前用户角色拼接 WHERE 条件 def build_owner_filter(user): if user.role admin: return 11 # 管理员无限制 if user.role manager: # 主管看自己和下属的 ids [user.id] get_subordinate_ids(user.id) return fowner_id IN ({,.join(map(str, ids))}) return fowner_id {user.id} # 销售只看自己逻辑说明把权限拼成 SQL 条件是最直接的做法但要注意ids必须来自可信来源不能由前端传入否则就是越权漏洞。字段级权限建议在序列化层做返回给前端前把无权字段置空或删除而不是靠前端隐藏。参数上get_subordinate_ids建议加缓存团队大了之后每次查组织树会很慢。缓存失效时机是组织架构变更时不要设固定过期时间。4.2 报表与集成先定指标口径再写 SQL报表功能清单里最常被忽略的是“口径定义”。同一个“成交额”是按商机创建时间算还是按赢单时间算结果差很多。我的做法是每个报表指标都写一句口径说明和 SQL 一起进版本管理。-- 月度成交额按赢单时间归属月份只统计赢单商机 SELECT DATE_FORMAT(h.to_stage_time, %Y-%m) AS month, SUM(o.amount) AS deal_amount FROM crm_opportunity o JOIN crm_stage_history h ON h.opp_id o.id AND h.to_stage 赢单 WHERE o.stage 赢单 GROUP BY month ORDER BY month;逻辑说明用crm_stage_history的赢单时间而不是商机的updated_at因为商机赢单后可能还会被修改其他字段updated_at会变导致金额归属月份漂移。这是踩过的坑报表对不上账时查了很久。集成方面功能清单里至少要留出 Webhook 和开放 API 两类出口。Webhook 用于客户创建、阶段变更时推送给外部系统开放 API 用于外部系统反查客户。两者都要有签名和限流否则容易被刷。5. 避坑与排查CRM 上线后最常见的五个问题这一章按“现象 → 原因 → 解决”写都是实际运维中反复出现的。现象一销售说看不到自己刚建的客户。原因通常是列表查询的权限过滤条件用了缓存的组织树新建客户后缓存没失效或者owner_id写入时取了当前登录人但登录态过期取了默认值。解决检查owner_id落库值是否正确再检查权限缓存失效逻辑组织树缓存建议在用户登录时刷新。现象二客户列表翻到后面几页越来越慢。原因是用了LIMIT offset, size深分页offset 很大时数据库要扫描大量行。解决改成基于id的游标分页WHERE id last_id ORDER BY id LIMIT size前端记录上一页最后一个 id。现象三跟进提醒重复推送。原因是定时任务扫描next_time时没有标记已推送多实例部署时每个实例都扫到同一条。解决加一个remind_status字段推送前用UPDATE ... WHERE remind_status 0抢占影响行数为 1 才推送。现象四合并客户后商机金额统计翻倍。原因是合并时把被合并客户的商机account_id更新了但报表按account_id关联时没排除被合并客户导致同一批商机被算两次。解决被合并客户打merged_into标记所有报表查询加WHERE merged_into IS NULL。现象五导入 Excel 后客户名带不可见空格查重失效。原因是 Excel 复制粘贴常带全角空格或零宽字符。解决入库前统一做strip加不可见字符过滤查重时也对两侧做同样处理不要只在展示层处理。6. 从功能清单到可验证的验收脚本功能清单写得再全不验证就是纸面功夫。我一般会在交付前写一组验收脚本按模块跑一遍确认每个功能真的能用。下面是一个用 Python 写的轻量验收脚本覆盖客户创建、查重、阶段流转、权限过滤四个核心点。import requests BASE http://localhost:8000/api TOKEN test-token # 测试环境固定 token HEADERS {Authorization: fBearer {TOKEN}} def test_create_and_duplicate(): # 创建客户 r requests.post(f{BASE}/accounts, headersHEADERS, json{name: 测试客户A, phone: 13800000000}) assert r.status_code 200, r.text acc_id r.json()[id] # 同电话再建一次应触发查重提示 r2 requests.post(f{BASE}/accounts, headersHEADERS, json{name: 测试客户A副本, phone: 13800000000}) assert r2.status_code 409, 查重未生效 return acc_id def test_stage_transfer(acc_id): # 建商机 r requests.post(f{BASE}/opportunities, headersHEADERS, json{account_id: acc_id, stage: 初步, amount: 1000}) opp_id r.json()[id] # 合法流转 r requests.post(f{BASE}/opportunities/{opp_id}/transfer, headersHEADERS, json{to_stage: 方案}) assert r.status_code 200 # 非法跳级应被拒绝 r requests.post(f{BASE}/opportunities/{opp_id}/transfer, headersHEADERS, json{to_stage: 赢单}) assert r.status_code 400, 跳级未被拦截 def test_permission_filter(): # 用销售身份查列表返回的 owner_id 应只有自己 r requests.get(f{BASE}/accounts, headersHEADERS) for item in r.json()[list]: assert item[owner_id] 1, 越权看到他人客户逻辑说明test_create_and_duplicate验证查重接口返回 409 而不是静默创建test_stage_transfer验证状态机既放行合法流转也拦截跳级test_permission_filter验证行级权限没有漏。参数上BASE和TOKEN从环境变量读更好这里为了可读性写死。这套脚本我一般挂在 CI 里每次改权限或状态机相关代码就跑一遍。教训是CRM 的 bug 很少是崩溃式的大多是“数据悄悄错了”比如权限漏了一条、金额算重了一次等业务发现时已经过去几个月。所以验收脚本要覆盖数据正确性而不只是接口通不通。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。