从自研到落地:一套轻量级CRM系统如何重塑销售团队客户管理

发布时间:2026/10/19 14:42:39
从自研到落地:一套轻量级CRM系统如何重塑销售团队客户管理 做To B销售的公司应该都有过这种阶段客户资料散落在销售个人的Excel表格里跟进记录躺在微信聊天记录里老板问起来某个大客户的进展要等销售翻半天聊天记录才能拼出一个大概。我经历过这个阶段也正因为这样才有了我们内部代号DeskcommCRM这套系统的诞生。先把背景交代清楚。我们团队当时十几个人销售加售前差不多十个客户主要是企业客户客单价不算低一个单子从首次接触到签约往往要经历几周到几个月。在没有系统之前最大的三个痛点非常典型撞单没人说得清两个人同时跟一个客户聊天记录一翻各说各有理客户跟进断层前任销售的客户交接过来除了一个名字和电话什么都没有离职即失联销售走了客户资源也跟着走了能留下的只有一封离职邮件。“客户是公司资产”这句话在没有任何工具支撑的情况下就是一句空话。所以当时我也调研了一圈市面上的CRM。用了一圈下来又结合团队的实际开发能力最终的决定是不自研大而全的CRM而是搭一套贴合我们自己销售流程的轻量系统代号DeskcommCRM。这篇文章就把这套系统的核心设计、数据模型、权限边界和落地过程中的关键经验拆开来讲给同样在“买现成还是自己搞”之间犹豫的团队一个具体参考。1. 放着现成的CRM不用为什么非要自己搭一套先别急着喷“重复造轮子”。我调研的时候把市面上主流的SaaS CRM和开源CRM都过了一遍最后得出一个结论不是它们不好而是它们和我们的销售流程之间隔着一层“使用成本”。这个使用成本来自几个方面。第一是字段的设计。SaaS CRM的客户字段、商机阶段、跟进模板非常标准化但我们的销售流程里有大量行业特有的字段比如客户的主营业务模式、预算决策链的复杂程度、上一家供应商的使用情况。这些字段如果放在通用CRM里要么塞进备注文本要么就要在配置后台一个个自定义等配置完销售人员已经失去耐心了。第二是客单价的计算方式。按坐席收年费的SaaS模式十几个人一年下来大概率是我们自建服务器成本的十几倍。第三是数据主权的问题客户资料放在第三方平台上虽然一般不会真出问题但心理上总觉得不稳。下面是当时我做的一个内部评估对比贴出来供参考维度SaaS CRM开源CRM如SuiteCRM、Odoo自研轻量系统功能完整度高通用性强较高但定制需二次开发按需裁剪只做高频功能设计成本按坐席订阅长期费用高授权费低但人力维护成本高需要投入研发但边际成本低定制灵活性受限于厂商配置项二次开发复杂度中等完全可控想怎么改怎么改数据主权数据在第三方自托管可控自托管完全可控上手门槛相对友好但字段/流程仍要学略偏技术贴合自家流程零培训成本另外还有一个常被忽略的因素销售团队对新系统的抗拒程度。买个现成系统销售一看界面和自家流程对不上第一反应就是“这又是公司让填表吧”草草录几条数据就放着吃灰。但如果是按他们真实的跟进节奏设计的工具比如点开客户就能看到上次跟进的备注和下一步计划那销售会主动用——好用和有用是两个层面的事。最终我们确认了边界不做一个功能堆砌的CRM而是做一套贴合“线索→客户→商机→合同→回款”这段核心链路、同时能打通企业微信通知和邮件落盘的销售管理系统。DeskcommCRM这个代号就这么来的内部用不对外销售但它把销售团队的日常操作从“多系统切换表格手工维护”压缩成了“一个页面完成90%动作”。2. DeskcommCRM的数据模型客户资产的底层骨架CRM的命根子是数据模型。表结构设计得烂后面加一个字段都是噩梦。我把DeskcommCRM的模型分为五个核心域按销售的实际动作来拆。2.1 五个核心实体和它们的关系第一是客户主体customer代表一个企业客户。第二是联系人contact客户企业里的具体的人一个客户下挂多个联系人。第三是商机deal代表一个正在推进的销售机会商机必须挂在客户下一个客户可以同时有多个商机但每个商机会有独立的金额和预计签单时间。第四是跟进记录activity销售每次电话、拜访、微信沟通后的纪要必须关联到一个客户或商机上。第五是合同contract商机推进到签约后生成合同关联商机和客户回款信息挂在合同下。这五个实体之间的关系用一句话概括就是客户是容器联系人是入口商机是推进的线跟进记录是线上的节点合同是线的终点。其中有一个很容易设计错的点一开始把“联系人”的字段直接挂在“客户”表里一个客户只能存一个联系人。后来发现一个企业客户平均有3到5个关键决策人有采购、有技术负责人、有拍板的高管每个人在沟通中的角色不同。如果只留一个联系人后面的商机阶段推进根本无从谈起。所以联系人和客户必须拆开联系人在客户内部还有主联系人标记。2.2 建表时我坚持的几个关键设计直接看一段建表SQL的简化和核心部分方便理解我们最终的设计思路create table if not exists customer ( id bigserial primary key, name varchar(255) not null, industry varchar(100), source varchar(50), level varchar(20) default A, owner_id bigint not null, status varchar(20) default active, json_extra jsonb default {}, created_by bigint not null, created_at timestamptz default now(), updated_at timestamptz default now() ); create table if not exists contact ( id bigserial primary key, customer_id bigint not null references customer(id), name varchar(100) not null, role varchar(100), mobile varchar(30), wechat varchar(100), is_primary boolean default false, owner_id bigint not null, created_at timestamptz default now() ); create table if not exists deal ( id bigserial primary key, customer_id bigint not null references customer(id), title varchar(255) not null, amount numeric(14, 2), stage varchar(50) not null, expected_close_at date, owner_id bigint not null, created_at timestamptz default now() ); create table if not exists activity ( id bigserial primary key, customer_id bigint references customer(id), deal_id bigint references deal(id), owner_id bigint not null, type varchar(30) not null, content text not null, created_at timestamptz default now() ); create table if not exists contract ( id bigserial primary key, deal_id bigint not null references deal(id), customer_id bigint not null references customer(id), contract_no varchar(100) not null, amount numeric(14, 2) not null, signed_at timestamptz, created_at timestamptz default now() );这个结构的几个设计决策我逐个说明一下为什么这样做。第一owner_id负责人全部显式存储。客户表、联系人表、商机表、合同表都有owner_id。这个字段的含义是“这个数据条目的当前归属人”。业务上有一个很常见的问题是“这个客户算谁的”答案就是看owner_id。后续的权限过滤、业绩归属、公海池回收逻辑全部以owner_id为锚点。synchronous同步的时候这也直接对应到企业微信的通知对象。第二软删除配合操作日志。客户和联系人表没有物理删除操作。销售手滑删错一条跟进记录如果物理删了找回来只能靠数据库binlog体验极差。我们设计上是打deleted标记或在活动表里记录“删除”动作。实际上activity表本身就承担了操作日志的功能只不过它记录的是业务动作而更详细的删除、更换负责人这类敏感操作会单独写入operation_log表。第三json_extra字段。这是为行业特性字段留的后门。一开始我陷入了一个纠结要不要把客户的主营业务模式、供应商偏好等字段单独拉列后来想了半天这些字段的使用频率其实不高查询时也几乎不会作为过滤条件但把它们塞进一个jsonb字段既能保留完整信息又不用频繁alter table。等后续统计哪个字段的利用率超过一定阈值再单独拆出来也来得及。2.3 线索到客户的转化逻辑在设计DeskcommCRM时我没有单独建一张庞大的“线索lead”表。很多标准CRM采用线索和客户分开的模式线索是未经确认的潜在客户经过初步沟通后“转化为客户”。这个模式在B2B大客户销售中比较健康但对于我们的体量来说反而增加了操作成本——销售随手从行业网站抄一批电话号码录成线索再逐个转成客户多了一层动作。我们的做法是官方站点表单、活动名片扫码、销售手动录入进来的数据统一落到customer表但带一个source字段标记来源status为“待确认”。只有通过首次电话沟通确认有真实需求后销售才会把status改成“跟进中”。商机必须挂在已有客户之下。这样一个销售当天录了20个新客户他要做的只是录入加打首通电话而不是在“线索”和“客户”两个列表里反复切换。这个设计和传统CRM相比明显不够“正统”但实测下来录入率比之前用标准流程时高了非常多。3. 权限体系与多租户隔离销售团队协作的边界CRM系统的权限设计一旦翻车后果非常直观销售看到别人的客户价格了主管看不了全局了或者是老板想看所有数据时被系统拦住了。DeskcommCRM的权限模型在实现上分两层功能权限和数据权限。3.1 功能权限角色只分四类不要搞十几种我见过有些CRM把权限角色拆得非常细包括销售专员、高级销售、销售组长、售前工程师、市场专员、系统管理员、只读访客等等。落到实际使用中大部分角色能执行的操作差异并不大反而是配置成本很高。DeskcommCRM只保留了四种角色角色核心能力数据范围销售客户管理、跟进记录、商机推进、合同登记自己负责的数据销售主管销售的全部能力外加团队客户查看、公海池分配本团队数据老板/管理层只读全量数据、查看报表全量数据系统管理员全功能外加用户和角色管理全量数据这四种角色在代码里体现为一组权限标签比如销售拥有customer.edit.own、deal.view.own主管额外拥有customer.view.team、customer.reassign等。每次请求进来先通过中间件解析token拿到用户和角色再调一个统一的权限检查函数按角色生成数据过滤条件。3.2 数据权限行级过滤是权限的灵魂角色只是解决“能不能点这个按钮”真正麻烦的是“能看到哪些行”。DeskcommCRM用了一个很通用的行级数据范围方案-- 伪代码查询客户列表时的权限过滤 if role sales: where_condition owner_id :current_user_id elif role manager: where_condition owner_id in (select user_id from team_members where leader_id :current_user_id) elif role in (admin, leadership): where_condition 11这个方案的查询性能在十几万客户级别的数据量下都很稳因为owner_id上有索引。原则上不让销售跨团队看到数据但有一个明确例外当某条客户记录被设置为“协作共享”时被共享的同事可以查看。共享关系的存储比较简单一张share_record表记录customer_id和shared_user_id。这里有个细节值得注意权限过滤条件一定要在SQL层做而不是在应用层用代码遍历过滤。最开始我偷懒在应用层查出所有客户数据再按owner_id过滤客户量两千条的时候没问题量到两万条时接口就直接超时了。后来全部改成在SQL查询条件里拼装权限条件配合分页问题迎刃而解。3.3 客户公海池防止客户烂在一个人手里客户数据死在某个销售的FollowUp列表里是CRM使用中一个很现实的问题。DeskcommCRM设计了一个简单的公海机制超过N天没有跟进记录的客户自动释放到公海池团队内其他销售可以认领。这个逻辑用定时任务实现。每天凌晨跑一次扫描# 简化后的公海回收逻辑 def recycle_stale_customers(days15): cutoff datetime.now() - timedelta(daysdays) stale_customers (Customer .objects .filter(statusactive, owner_id__isnullFalse) .exclude(id__inActivity.objects .filter(type__in[call, meeting]) .values_list(customer_id, flatTrue)) .exclude(updated_at__gtecutoff)) for c in stale_customers: operation_log.append(c.id, RECYCLE_TO_POOL, c.owner_id) c.owner_id None c.status pool c.save()这个机制上线后团队里一度躺了一年没动的死客户开始被重新激活。前三个月公海池里抢单最多的一个销售独立认领了40多个旧客户成交了2单。足够的存量客户资产一旦流动起来销售的热情完全不一样。当然这个回收逻辑不能太激进我们最初定的是60天后来根据实际跟进节奏调到了15天因为这是个B2B短周期销售场景15天没有有效跟进基本可以判定为僵死客户。4. 与外部系统打通从手工录入到自动化同步CRM如果只是一个孤岛系统价值会打个对折。DeskcommCRM日常运转中最离不开的三个外部系统是企业通知、邮件和官网表单数据源。这里把所有集成细节和踩坑经验一并讲清楚。4.1 对外API的设计REST风格加幂等DeskcommCRM预留了一套REST API给内部其他系统和后续的移动端使用。核心接口包括POST /api/v1/customers创建客户GET /api/v1/customers?owner_idxxxstatusactive查询客户PATCH /api/v1/customers/{id}更新客户POST /api/v1/activities新增跟进记录POST /api/v1/deals创建商机POST /api/v1/webhooks/trigger触发事件通知鉴权统一用Bearer TokenToken在系统配置页面生成按应用维度隔离每个外部系统用独立的Token方便追踪调用方和单独吊销权限。API层有两个细节必须提统一返回结构和幂等设计。返回结构固定为{code, message, data}三段式前端和脚本拿到后不用做二次解析。幂等方面的做法是允许调用方在生成客户时携带client_request_id系统在写入前用这个ID做唯一性校验相同的请求ID只处理一次避免网络重试导致重复建客户。4.2 官网表单数据接入公司官网的“获取演示/联系我们”表单过去是邮件通知到销售主管再由主管手工分配给销售整个过程慢且容易丢。我们在官网表单提交的后端回调里调用了DeskcommCRM的接口把表单里的公司名、电话、需求和页面的来源渠道一起创建成一个新客户记录。这里有个关键配置新客户记录默认负责人是“待分配池”也就是status为待确认、owner_id为空的公海池状态。然后在企业微信群里推送一条模板消息“官网新增线索XX公司需求XX请点击认领”。销售看到后点链接先认领到自己名下再打首通电话。这个链路跑通之后表单线索的平均首次响应时间从原来的2小时缩短到了15分钟以内。后来我们看数据从官网表单进来的客户最终成交率比其他渠道高出一截在这里系统起到的不是替代销售的作用而是把客户的第一接触点管理起来了。4.3 跟进记录的自动提醒每天早上的9点DeskcommCRM会跑一个任务把当天要做的事汇总出来推送到企业微信——包括预计今天要推进的商机、超过3天没跟进的客户提醒、已经进入合同阶段的待办事项。这个提醒不是列表式的干巴巴数据而是直接可以点开详情页面处理。这个功能的实现说白了就是一个定时任务加消息推送但设计上花功夫的地方在于“超时跟进的Threshold”。跟进的节奏每个团队不一样我们摸索出来的是热客户最近一次跟进在7天内只需要每周一提醒一次冷客户超过15天没跟进每天提醒直到要么回收公海、要么被激活。规则不复杂但对销售的自觉性培养帮助非常大。5. 落地过程中的三个“反直觉”教训任何系统一上线就会遇到各种各样预期之外的问题尤其是这类团队内部系统。下面这三个问题是我们实际用了一年后才总结出来的希望能帮大家少走点弯路。5.1 功能不是越多越好销售要的是“少点两步”一开始DeskcommCRM的功能列表里有一堆“锦上添花”的设计客户分值自动计算、商机阶段转化率漏斗、多维度图表报表、活动轨迹热力图。设计文档画得很漂亮可是上线后第二周我特意观察了销售的使用行为发现他们高频使用的功能只有五个新增客户、记录跟进、看当天待办、查客户详情、导出表格。那些花哨的统计功能他们一个月都点不了几次。后来我做了个减法把高频操作改进到极致。比如“新增客户”原来的流程是点“新增”→填表单→选负责人→保存要四步。后来我把“从Excel导入”和“手动新增”合并进同一个页面还加了“从企业微信聊天记录导入客户名片”的快捷入口销售加一个客户只需要十秒钟。结果就是录入数量从每周人均十几个提升到了每天十几个。“少点两步”比“多点功能”更能提升实际使用率这是我做这个系统最大的感悟之一。5.2 查询慢的根因是“按用户名模糊搜索客户”没走索引客户量到五万条之后客户列表的搜索接口开始变慢慢到点一次要等三四秒。第一批排查思路我走偏了以为是服务器配置不行还升级了两倍的CPU。后来用explain看执行计划才发现问题出在一个很愚蠢的查询写法上select * from customer where name like %公司%;前导通配符的like查询哪怕name字段建了B-tree索引也用不上。当时整个客户搜索框支持模糊搜索销售输入“科技有限公司”就会去扫全表五万行的表还好但加上order by和count操作性能就直接崩了。后来把模糊搜索方案改成了pg_trgm模块的GIN索引查询语句改成create extension if not exists pg_trgm; create index idx_customer_name_trgm on customer using gin (name gin_trgm_ops); -- 搜索时仍可以使用 %关键词% select * from customer where name ilike %关键词%;加了GIN索引之后同样的模糊搜索查询从几百毫秒降到几十毫秒。这个问题的教训是写SQL的人一定要养成看执行计划的习惯不能靠直觉排查性能问题。客户的搜索功能在CRM里看似不起眼但它几乎是销售每天点得最多的按钮。5.3 老销售的Excel导入导出竟然是最刚需还有一个认知上的颠覆我原以为销售最讨厌Excel结果恰恰相反他们的Excel技能非常熟练而且极度依赖。这个发现的契机是一个销售骨干跟我反馈说“你们这个系统能不能支持我把Excel里几百行客户直接批量导入现在要我一个个新建我不如自己做表”。然后才意识到Excel导入导出不是“给客户看的导出报表”而是销售日常办公习惯的一部分。后来DeskcommCRM加入了“客户批量导入”功能支持标准模板下载导入时做重复检测用公司名联系人手机号联合判重导入失败的行会返回具体原因手机号格式错误、公司名为空、重复记录等。导入完成后还会生成一条导入报告把失败的数据表列出来方便修改后重新导入。这个功能上线之后系统里的客户总量一天之内新增了接近6000条大量沉睡在销售个人硬盘里的客户数据回流到了系统里。导出功能也是刚需销售每周要跟主管同步进展最直接的方式就是把负责的客户列表导出来发过去比打开系统给对方看更快。如果让我重来一遍我会把Excel导入导出从“计划列表的末尾”提到“第一优先级”这个看似不够“高科技”的功能反而是整个系统用户参与度的最大功臣。最后再多说一点个人体会。做团队内部系统真正的成功不是看系统demo多漂亮而是看上线三个月后销售是不是还在用、有没有形成数据积累的习惯。DeskcommCRM上线一年半目前系统里沉淀了四千多个有效客户、两万多条跟进记录新销售入职后翻一翻历史跟进大致就知道客户情况不用再靠老人喂。我觉得这就是一套内部CRM应该达到的状态。这套系统的表结构、权限模型和集成经验我们已经验证过了如果你也在搞类似的团队内部管理系统希望这篇拆解能帮你避开我们踩过的那些坑。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询