资讯动态

自研CRM系统实战:数据模型、权限与自动化设计

发布时间:2026/10/20 12:45:47 来源:尧图企业网站定制
从最早用 Excel 管理客户到后来咬牙上了一个轻量级 SaaS CRM再到最后我们团队决定自己动手写一套 DeskcommCRM——这条路走下来我最大的感受是CRM 不是买回来的工具而是长出来的业务系统。如果你正好在纠结“要不要自研一套 CRM”或者已经被现成产品的字段限制、权限漏洞和集成成本搞得焦头烂额这篇文章应该能给你一些实在的参考。DeskcommCRM 是我们在 2024 年下半年启动、2025 年初正式上线的内部客户管理系统核心解决三件事销售管道可视化、跨部门客户数据共享、以及日常跟进任务的自动化提醒。名字里的 Deskcomm 是我们公司的业务线代号所以这套系统从一开始就没打算对外商业化完全是冲着“业务部门要什么我们就改什么”这个目标去的。也正是因为这样它在技术选型、数据模型和权限设计上跟市面上大多数通用 CRM 有着相当明显的差异。1. 为什么我们把用了三年的 SaaS CRM 换成了自研系统1.1 现成产品用着用着就撞到了天花板在决定自研之前我们其实正正经经地用过一款在国内挺知名的 SaaS CRM年费不高功能也不少销售团队上手也比较快。但用了大概一年半之后问题开始集中爆发。最先出问题的是字段灵活性。我们的销售流程里有大量“只看一次就回不来”的场景比如客户处于“试用阶段”时需要一个自定义的“试用版本号”字段项目结束后这个字段对整个团队来说就毫无意义了。SaaS 产品的字段管理虽然支持自定义但往往有数量上限、类型限制而且修改历史字段的展示逻辑很痛苦。更麻烦的是跨对象关联查询在 SaaS 里基本做不了太深比如“找出所有在 30 天内做过 3 次跟进但没进入下一阶段的试用客户”这类需求要么靠导出到 Excel 手动筛要么就得等厂商排期开发。第二个大问题是数据所有权。我们用 SaaS CRM 期间销售主管要求看到每个销售人员的跟进记录和成交预估但系统自带的报表模块对“团队数据穿透查看”的支持非常弱。有些销售为了月底报表好看干脆把跟进记录补写在备注里也不愿意填到结构化字段中因为大家心里都清楚——这数据进了 SaaS 系统所有权和导出控制都不在自己手里。第三个问题才是压垮骆驼的最后一根稻草。我们的客户成功团队需要对接企业微信的聊天记录和通话录音销售部的日常流程又强依赖内部工单系统。数据同步靠写脚本调 API但 SaaS CRM 的 API 配额有限同步频率低经常出现抖音上刚聊完的客户晚上才能在 CRM 里看到聊天记录。业务团队开始吵研发团队也开始不爽一个 CRM 项目做成了夹在供应商 API 和业务需求之间的传话筒。1.2 DeskcommCRM 的目标边界只做业务真正需要的五件事在立项会上我们给自己定了一条铁律不要做一个“小 Salesforce”只做业务团队每周至少用五次的模块。最终敲定的边界非常明确联系人管理客户公司、联系人、标签、来源渠道、自定义字段。销售管道阶段、金额、预计成交日期、阶段变更历史。跟进任务电话、拜访、演示、合同审批全部带提醒。报表看板团队漏斗、销售预测、跟进及时率。权限与审计销售数据隔离、操作日志可回溯。不做在线聊天、不做营销邮件群发、不做复杂工单系统也不做移动 App——这五件事是当时业务团队说了三遍以上“必须要”的需求。我们宁可把桌面端体验做精细也不去铺那些看起来热闹但用不上的功能。定完边界之后我们做了一次成本评估SaaS CRM 一年的费用大概是 6 万块自研按两名后端、一名前端全职投入 4 个月算人力成本远高于此。但考虑到 API 配额、字段扩展和企业微信深度集成这些长期收益团队一致认为自研的 ROI 是正的。事实证明这个判断在一年后就得到了验证——我们后来增加的“客户健康度评分”功能如果真的放在 SaaS 系统上又是一轮漫长的需求拉锯战。1.3 技术选型里我们权衡过三条路技术栈的选择我们实际上在立项后就开了两次会。团队成员背景偏全栈有从 Java 转过来的也有一直用 Node.js 的所以讨论比一般团队更激烈一点。当时摆在桌上的方案有三个方案后端前端数据库核心优势APython / DjangoVue 3PostgreSQL自带 Admin 和权限开发快BNode.js / NestJSVue 3PostgreSQL前后端语言统一社区资源多CJava / Spring BootReactMySQL团队最熟招聘容易最后选 B 的原因很实际我们团队里前端有 4 人后端只有 2 人用 NestJS 可以把 DTO 校验、依赖注入、模块化开发这些工程规范交给框架来约束后端的开发量相对可控。数据库统一选 PostgreSQL因为它的 JSONB 类型在实现自定义字段时比 MySQL 友好太多后面数据模型那部分我会重点展开讲。数据库迁移工具用的 TypeORM Migration接口文档直接上 Swagger。部署方面当时很简单先用 Docker Compose 跑在内部测试服务器上稳定后再上 Kubernetes。这套组合可能不是最酷的但是在大约四个月的项目周期里它让我们没在环境问题上耗掉一丝时间。2. 数据模型设计CRM 的骨架决定业务上限2.1 六大核心表的字段设计与关系CRM 系统的数据模型本质上回答一个问题一段销售记录从进入系统到成交归档需要经过哪些实体、产生哪些关联。我们最终收敛出六张核心表基本覆盖了 90% 的业务操作。第一张是companies表也就是客户企业。字段不追求多但必须有唯一性约束和法律实体识别。它包含公司名称、统一社会信用代码、所属行业、规模区间、来源渠道、地址、官网、创建人、负责人。第二张是contacts表存的是具体联系人。每个联系人必须关联一个公司允许关联到“未知公司”这个占位记录包含姓名、职位、电话、微信、邮箱、微信企业号 ID、备注、是否决策人。第三张是deals表也就是商机表也是销售管道的核心。字段包括商机名称、关联公司、金额、预计成交日期、当前阶段、赢率、负责人、创建人、关闭原因、实际成交金额。第四张是activities表记录所有跟进动作。每次电话、拜访、邮件、演示都算一条 activity带type、subject、content、start_at、end_at、owner_id和可选的deal_id。第五张是tasks表用来做待办和提醒。它跟 activity 的区别是activity 是已发生的记录task 是计划要做的事所以 tasks 表里有due_at、remind_at、done_at。第六张是users表加上它的延伸表user_roles和role_permissions负责权限基础数据。这六张表的关系非常清晰一个公司有多个联系人多个商机一个联系人和商机的关联需要通过deal_contacts这张关系表因为一个商机的参与人可能不止一个。task 和 activity 都通过owner_id找到对应的 user同时可选地通过deal_id和contact_id挂到具体业务对象上。2.2 自定义字段的存储方案EAV 老路和 JSONB 新路自定义字段是 CRM 的刚需但实现方式决定后续开发是顺风还是逆风。我们最初的方案参考了经典 EAVEntity-Attribute-Value设计也就是把字段定义存在一张custom_fields表里把字段值存在另一张custom_field_values表里每行存一个字段的值。好处是结构最灵活坏处是当列表页需要展示 20 个自定义字段时要 JOIN 20 次SQL 写起来要人命性能也会随数据量增长快速劣化。后来我们换成了 PostgreSQL 的 JSONB 方案。具体做法是在每张核心业务表上加一个custom_data字段类型是 JSONB。字段定义存一张company_field_configs表里面记录字段名、字段类型、是否必填、可选项配置。查询的时候直接在 JSONB 上做-操作符提取值。-- 查询所有“试用版本号”等于 v2.3 的公司 SELECT * FROM companies WHERE custom_data-trial_version v2.3;JSONB 方案在写入时需要多一道校验逻辑根据company_field_configs表里的配置校验传入的custom_data是否包含必填字段、类型是否正确。我们把这部分做成了通用工具函数在 NestJS 的 Service 层调用代码量不大但保证了数据洁癖。如果你也想在项目里用 JSONB 做自定义字段我建议一开始就把应该做成系统字段和自定义字段的边界划清楚。比如“公司名称”“负责人”这种所有业务线都要用到的必须是固定字段不能放进 JSONB否则索引、权限、排序都会变得异常别扭。2.3 索引策略与数据约束从丁零当啷的 SQL 到跨模块查询数据模型设计完之后真正让系统跑得顺的是索引和约束。我们在测试环境里导入了一批真实的脱敏数据大概有 10 万条 company、30 万条 contact、20 万条 deal然后开始调索引。最常用的查询场景有两个。第一个是列表页分页查询条件通常包含负责人 ID、公司名称模糊搜索、阶段、更新时间范围。针对这个场景我们建了复合索引(owner_id, updated_at DESC)并把公司名称的模糊搜索改成了 PostgreSQL 的pg_trgmGIN 索引否则LIKE %关键词%会全表扫描。CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE INDEX idx_companies_name_trgm ON companies USING gin (name gin_trgm_ops);第二个场景是报表的聚合查询比如“每个销售在每一阶段的商机总金额”。这种查询对deals表的stage、owner_id、amount三列有固定过滤条件我们加了一个覆盖索引(owner_id, stage, amount)让查询可以走 Index Only Scan减少回表。约束方面有几个关键设计是上线后才补上的。金额字段设了CHECK (amount 0)防止负数金额进入报表。阶段变更历史表加了version字段同一张 deal 的同一阶段只允许出现一次如果重复进入需要先走“重开”流程。联系人的手机号做了格式校验但允许空值因为不是每个联系人都能拿到手机号。3. 核心模块实现销售管道、联系人管理和活动记录的实操细节3.1 销售管道Pipeline的状态机设计销售管道在整个系统里是业务属性最强的模块也是销售团队每天打开系统第一时间看的页面。我们的管道有六个阶段初次沟通、需求确认、方案演示、商务谈判、合同审批、赢单/输单。前五个是进行中状态最后两个是终态。在数据库里阶段字段用VARCHAR存字符串标识但业务操作上不能直接 UPDATE 这个字段就完事必须走一个统一的状态机转移接口。状态机里定义了两类规则合法流转路径、状态迁移的副作用。合法流转路径用一张配置表存起来stage_from - stage_to initial-communication - needs-confirmation needs-confirmation - demo demo - negotiation negotiation - contract contract - won contract - lost negotiation - lost demo - lost注意这里允许“跳过阶段”的流转比如初次沟通后直接到商务谈判这在紧急客户场景里确实会出现。但禁止了逆向流转比如从谈判回退到需求确认必须走“阶段回退申请”审批保证销售漏斗数据的可信度。状态迁移的副作用会触发两个动作自动记录一条activities历史以及更新 deal 上冗余保存的stage_changed_at字段。这个字段很关键因为我们的“跟进及时率”指标就定义成“当前阶段停留时间超过 N 天的商机数量”没有这个时间戳报表逻辑会绕一大圈。赢单和输单的处理也有讲究。赢单时系统把deal.amount复制到closed_amount同时自动检查并更新关联公司的“活跃状态”。输单时必须填写输单原因价格、竞品、需求消失、无决策权这个原因是业务分析的重要维度不能留空。3.2 联系人/公司去重合并逻辑看起来很土但真的不能不做数据重复是 CRM 系统的头号污染源。我们的数据来源有两个手动录入和从企业微信导入。手动录入的场景里销售经常把同一家公司写成“某某科技有限公司”和“某某科技责任有限公司”两个名称都进了系统后续的跟进记录全部分散报表也会出现同一个客户算两遍的闹剧。去重逻辑我们分两层做。第一层是录入时实时查重。在新建公司和联系人的表单提交前前端调用后端的POST /companies/check-duplicate接口后端用pg_trgm的相似度函数similarity(company.name, :input_name)跑一遍相似度超过 0.7 的就返回候选列表提示用户选择“关联到已有公司”而不是新建。// NestJS 中的查重服务片段 async checkDuplicate(name: string): PromiseCompany[] { return this.companyRepo .createQueryBuilder(company) .where(similarity(company.name, :name) 0.7, { name }) .orderBy(similarity(company.name, :name), DESC) .limit(5) .getMany(); }第二层是定期的合并任务。每周日凌晨跑一个批处理扫描所有相似度超过阈值的公司对如果两个公司都没有关联 deal 和 activities自动合并有关联的话挂到人工审核队列。之所以不全程自动化是怕误合并把销售的数据串到别人头上这个风险不能赌。联系人去重相对简单主要按手机号和微信号做精确匹配。但这里有个特殊场景同一个客户一个账号是个人微信一个账号是企业微信系统里可能出现两条联系人记录。我们干脆新增了一个字段contact_source标记是“个人微信导入”还是“企业微信导入”展示层让用户自己决定是否合并不强行统一。3.3 任务和活动的时间线处理把零散动作串成一条线一个客户从建立到成交中间的跟进动作可能超过 20 次。如果没有一个清晰的时间线展示销售自己都记不住上次聊了什么更别说替班的同事接手之后怎么快速上平。DeskcommCRM 的时间线设计基于activities表所有类型的事件都落到这张表里。展示时按created_at倒序每条活动显示类型标签、标题、内容、操作人和时间。电话事件还要显示通话时长如果是用企业微信拨打的自动同步录音链接。任务模块的设计要考虑的是“提醒”而不是“记录”。很多 CRM 的 task 模块做成了单纯的待办列表没有提醒能力销售根本不会主动去看。我们的做法是tasks表里设置了remind_at字段后台有一个定时任务每分钟扫描一次把remind_at在 5 分钟内的未完成任务推送给对应负责人的企微工作通知内容包含任务内容、关联客户名和跳转链接。除了给用户提醒还有一类任务需要自动创建。比如最新的策略是每次通话结束后如果客户提到“需要找技术确认接口”系统要通过关键词匹配自动给销售创建一个“向客户技术确认接口细节”的任务。这个功能上线后忘跟进的投诉明显变少。其实实现逻辑不复杂正则匹配加固定模板但收益却非常高。4. 权限体系设计团队协作不越权的关键4.1 基于角色的访问控制RBAC落地CRM 里权限没做好轻则数据混乱重则销售离职后直接带走客户。我们的权限体系分成三层功能权限、数据权限、操作日志。功能权限用 RBAC 模型实现。系统内置五个角色超级管理员、销售主管、销售人员、客户成功、只读访客。每个角色挂一个权限码集合比如deal:read、deal:create、deal:update_stage、deal:delete、report:view、settings:manage。权限码统一用字符串存在role_permissions表里。判断权限时NestJS 的守卫层会读取当前用户的权限码数组然后和路由装饰器里声明的RequirePermissions(deal:update_stage)做比对。这套思路是标准的 Casbin 简化版没有引入额外依赖实现起来也很直观。值得注意的一点是功能权限并不能真正保障数据安全。一个销售即使有deal:read权限他也不应该看到其他销售跟进的商机详情。这就需要数据权限出场。4.2 数据级权限谁能看到谁的客户数据权限的设计我们参考了国内大厂的做法按“数据归属 可见范围”两个维度控制。每个 company、contact、deal 都有一个owner_id字段表示当前负责人。对于 deal 的列表查询系统会根据当前用户的角色和部门动态生成查询条件销售人员只看到自己创建的 deal以及自己是被共享人的 deal。销售主管看到自己直属团队的 deal也就是owner_id在我管理的用户 ID 列表内。客户成功看到所有进入“方案演示”阶段及以后的 deal因为协作需要。超级管理员全部数据。部门维度我们当时没做。因为团队规模不到 50 人一个销售主管管 6-8 人用“直属团队”就够了。但如果你在更大的组织里落地这套逻辑建议在 users 表上增加department_id再做一层部门可见范围的叠加判断。共享逻辑单独说一下。销售经常需要临时把某个客户共享给售前工程师看。我们在deal_shares表里记录deal_id、user_id、expired_at。查询 deal 详情时先判断owner_id current_user不满足就查deal_shares再不行就检查角色是否满足特权条件。这个顺序不能反否则容易误放权限。4.3 审计日志与操作回溯上线第三周救了我们一次审计日志最早是运维团队要求的说“出了问题不能查不到”。我原本打算草草往表里扔点 JSON没想到上线第三周就派上了大用场。那周有销售反馈说自己的 deal 金额被人改掉了但系统里没有任何记录。我们当时只有updated_at字段完全无法判断谁改的、改了什么。后来紧急给deals表加了一个deal_histories表每次 UPDATE 都通过触发器记录变更前后的关键字段金额、阶段、负责人、预计成交日期。触发器的实现方式是现学现卖CREATE OR REPLACE FUNCTION deal_change_trigger() RETURNS TRIGGER AS $$ BEGIN INSERT INTO deal_histories(deal_id, changed_at, changed_by, old_amount, new_amount, old_stage, new_stage, old_owner_id, new_owner_id) VALUES ( OLD.id, NOW(), current_setting(app.current_user_id)::uuid, OLD.amount, NEW.amount, OLD.stage, NEW.stage, OLD.owner_id, NEW.owner_id ); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_deal_change AFTER UPDATE ON deals FOR EACH ROW EXECUTE FUNCTION deal_change_trigger();这里有几个细节app.current_user_id是我们在 NestJS 里通过SET LOCAL app.current_user_id ...在事务里设置的必须用current_setting读取因为参数带点号set_config的命名空间规则很重要。另外注意触发器必须判断 NEW 和 OLD 的值是不是真的不一样否则每次更新都会插入一条无意义的日志。现在我们的审计日志不仅用于排查问题还在部门复盘时用来分析“这个 deal 为什么在某个阶段停留了 20 天”已经变成了业务数据分析的一部分。5. 集成层与自动化让 CRM 真正成为工作台5.1 邮件收发与日历同步打通个人工具和企业协作CRM 的价值有相当大一部分来自集成但我们一开始没有做完整的企业微信全套对接而是从两个最痛的点切入邮件和日历。邮件集成的方案用的是 IMAP 监听。每个销售在自己的邮箱配置里填入 IMAP 地址、账号和授权码系统用imap-client库每 30 秒扫描一次收件箱把主题包含客户公司名或者发件人匹配联系人的邮件抓取出来新建一条 activity。抓取邮件时做了两个保护只添加到曾经有往来记录的 company 下面且不会自动删除用户邮箱里的邮件只是在 CRM 里加一条索引。日历同步稍微复杂一点。我们团队内部用的是企业微信日历外部销售用的是自建 CalDAV。因为确实没有精力同时兼容两个协议最后决定先做 CalDAV。用户在设置页关联 CalDAV 账号后系统会每天把他当天的会议日程拉取到 CRM 时间线里。不直接双向同步因为双向同步涉及到的冲突处理逻辑太多第一版先做到“可见”就好。“可见”这个诉求是销售提的他们说客户打电话来说今天下午三点有个会我经常忘记如果 CRM 能在我点开客户的时候看到今天跟他有什么会就不会漏了。就这么一个简单的展示功能上线后使用率非常高。5.2 自动化规则状态变更触发动作自动化功能延展了 CRM 的主动性。我们的自动化引擎核心很简单事件Event、条件Condition、动作Action简称 ECA。目前跑的自动化规则有六条其中效果最明显的是“商机阶段变更通知”和“跟进超时提醒”。商机阶段变更通知当 deal 从“需求确认”进入“方案演示”阶段时系统自动给售前工程师发送一个企微工作通知附上 deal 链接和相关链接。这个规则把原本需要人工转告的流程给消灭了。跟进超时提醒每天晚上 8 点系统扫描所有“当前阶段停留超过 7 天且今天没有 activity”的 deal给负责人发一条提醒同时给自己的主管抄送一条。实施后的两周整体漏斗的推进速度提高了约 15%业务主管对这个改变非常满意。自动化规则的实现没有引入独立的工作流引擎就是定时任务加简单的条件判断。NestJS 的Cron装饰器足够用了。Cron(CronExpression.EVERY_MINUTE) async runPendingRules() { const pendingActivities await this.activityService.getCreatedInLastMinute(); for (const activity of pendingActivities) { for (const rule of await this.ruleService.getAllEnabled()) { if (rule.matches(activity)) { await this.ruleExecutor.execute(rule, activity); } } } }这套轮询方案在每分钟几千条 activity 量级下没有任何压力但如果你预估的数据量再大一个量级建议用消息队列触发规则别靠扫表硬扛。6. 部署运维与性能优化自研系统上线后的 90 天6.1 从 Docker Compose 到 Kubernetes小步慢跑的部署演进上线初期整个系统就是四个容器Nginx静态资源反向代理、NestJS 后端、PostgreSQL、Redis缓存和任务中间态。docker-compose.yml 里把端口、环境变量、卷映射都写清楚一台 4C8G 的测试服务器就能跑。这个阶段最大的心得是数据库的持久化卷一定要提前配上volume否则一次docker compose down就把数据清零了。我们第一次部署时就吃过这个亏亏在一个“顺手重启”的命令上。后来加了pg_dump的定时任务才算松了口气。上线一个半月后用户量上来PostgreSQL 的 CPU 开始出现持续 80% 的情况。我们把部署切换到了 Kubernetes通过 HPA 自动扩缩容后端实例数据库做了一主一从的读写分离。切换本身不算难真正麻烦的是把 NestJS 的无状态化改造完整落地——比如本地文件缓存全部迁到 Redis日志统一输出到标准输出由 EFK 收集这个改造花了大概两周。6.2 查询性能优化与缓存策略20 万行以后才开始真刀真枪数据量到了 20 万行之后之前慢得不够明显的查询开始变得肉眼可见。我们用pg_stat_statements插件找出 TOP 5 慢查询发现问题集中在两类一类是列表页的分页排序另一类是报表页的聚合。分页排序优化做了两件事。第一件是把前端的分页参数从offset改成keyset分页也就是基于updated_at和id的游标分页。它的好处是越往后翻页性能越稳定不会因为 offset 大到几十万行而全表扫描。第二件是给列表页的所有“筛选条件”组合建了复合索引前文提到的(owner_id, updated_at)就是这里的产物。报表聚合的优化更走了一点捷径。有些统计指标比如团队月底 KPI 达成率每次刷新报表页面都要跑到deals表聚合一遍数据量大时响应时间接近 4 秒。我们加了一层 Redis 缓存key 设计成report:kpi:2025:01过期时间设 30 分钟。报表数据本身不需要实时到秒级30 分钟的延迟完全可接受但查询响应时间直接从 4 秒降到了 200 毫秒以内。Redis 缓存有几个必须避开的坑。第一个是缓存穿透——大量请求查同一个不存在的 key 时会直接打到数据库我们给空结果也做了一层“空缓存”过期时间 2 分钟。第二个是缓存雪崩——所有 key 同一时间过期会导致瞬间压力我们在 key 过期时间上加了一个随机 0-60 秒的偏移量。6.3 备份、监控与容灾没有事故的时候最容易忽视它备份这件事我们在上线当天就做了但做完之后整整两个星期没检查过备份是否可用。在第二次复盘时运维同事提议做一次“备份演练”结果发现 pg_dump 的备份文件里缺了最新的 3 天数据原因是 Cron 里挂的容器名字变了备份任务其实一直在报错只是错误没有同步给任何人看到。从那以后我们的备份策略改成三层PostgreSQL 每天凌晨 1 点做一次全量pg_dump保留 14 天。WAL 日志实时归档到外部存储确保最多丢失 5 分钟的数据。每周做一次恢复演练确保备份真的能在空库上恢复出可用系统。监控方面Prometheus Grafana 是标配。我们自己额外写了一个接口/health/ready不仅检查数据库连接还检查 Redis 连通性和关键定时任务最近一次执行时间。这个接口接入告警之后像“自动化任务挂了三天”这类静默故障才终于能第一时间发现。7. 从项目到产品我学到的最重要的几件事写到这里DeskcommCRM 已经上线运行了十个月。我不敢说这套系统已经完美但它确确实实让销售团队的工作效率提升了一个台阶也让管理层第一次能看到“漏斗每一层到底卡在哪”。复盘整个过程有几件事对我个人来说特别有启发。第一件事自研系统最怕的不是技术难而是需求蔓延。立项的时候我们划定了边界但在开发过程中销售主管几乎每两周就会提出一个“加个小功能”的需求。我们定了两条拦截规则使用频率低于每周一次的暂时不做能通过现有配置实现的不另起炉灶。靠着这两条规则我们挡掉了至少十个看似合理但不重要的需求在第四个月按时完成了核心模块。第二件事数据模型比接口 API 重要得多。接口设计错了可以快速改前端页面错了可以推翻重写但表结构一旦上线数据迁移的成本是指数级的。我们前期花在数据模型设计上的时间大概有 20 个工作日包括反复跟业务主管确认“公司”和“联系人”的边界、确认“商机阶段是否允许跳转”等细节。现在回头看这些讨论的时间一点都没白费。第三件事自研 CRM 是一个持续投入的过程不是一锤子买卖。上线之后我们每个月至少还会花 3-5 天维护和迭代。新需求的优先级不是我们研发团队说了算而是业务主管带着数据来申请说得清楚业务价值排期评审才会放行。如果你正在考虑自己动手做一套 CRM我的建议是先把“不做清单”写出来然后再写“要做清单”。想清楚边界之后你会发现这套系统的功能和架构都会清晰很多。DeskcommCRM 对我们来说远远不只是一个软件它已经变成了理解业务、推动业务的一种方式。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑