在离散制造-MTO(Make-To-Order,按订单生产)的场景下,客户就是制造的起点。客户信息、跟进记录和客户分析直接决定报价准度、交期管理、产能排产和资金回笼效率。很多企业把ERP的“客户管理”当成CRM的简单复制,但在MTO场景里它需要更紧密地和BOM、工艺路线、报价模块、订单、交付与售后衔接。本文将给出一套可实施的客户管理模块设计(包含架构图、流程图、功能拆解、开发技巧、以及一个较大的参考代码),帮助企业快速搭建或改造适配离散制造-MTO的客户管理板块。
本文你将了解
- 概念与必要性:什么是ERP(离散制造-MTO)系统?为什么要在ERP里做客户管理?
- 客户管理在MTO中的定位与目标(客户、跟进记录、客户分析)
- 整体架构设计(含架构图)
- 详细功能模块与业务流程
- 开发详解与实现技巧(表设计、索引、接口、权限、审计、缓存、并发)
- 参考代码
- 实现效果与交付验收建议
注:本文示例所用方案模板:简道云ERP系统,给大家示例的是一些通用的功能和模块,都是支持自定义修改的,你可以根据自己的需求修改里面的功能。
1. 概念与必要性
什么是 ERP(离散制造-MTO)系统? 简要说:ERP 是企业资源计划系统。离散制造指产品是离散的(零部件、装配件),MTO 表示多数生产是按订单生产而非库存生产。MTO 最大的挑战是订单多变、每单可能不同(BOM、交期、工序不同),因此前端的客户信息和跟进记录直接影响制造端决策。
为什么要在 ERP 里做客户管理而不是单独用 CRM? 因为在 MTO 场景下:客户的需求常常牵涉到工艺、BOM、报价与交期。把客户管理放在 ERP,能实现客户信息与报价、销售订单、生产排程、采购需求的无缝联动,减少信息孤岛与重复录入,提升响应速度与准交率。
2. 客户管理在 MTO 中的定位与目标
核心要解决的问题:
- 快速、准确地拿到客户需求(产品/规格/交期/价格敏感度)并能追溯历史沟通(跟进记录)。
- 把客户分层(重点客户/战略客户/低频客户),自动化提示跟进、促单或流失预警。
- 提供面向业务的分析(下单周期、单均金额、转化率、流失率),支持报价策略和产能保障决策。
关键功能围绕三大对象:客户(Customer)、跟进记录(FollowUp)、客户分析(Analysis)。
3. 整体架构设计(文字 + 架构图 —— Mermaid)
下面给出一个典型的模块化架构(微服务或单体模块化均可适配):
graph TD
A[前端应用:销售/客服/售后] -->|REST/GraphQL| B[API 网关 / 后端服务]
B --> C[客户管理服务]
C --> D[(数据库:Customer, FollowUp, Tags, Events)]
C --> E[分析服务 / OLAP层]
B --> F[订单服务]
B --> G[报价服务]
B --> H[生产排程服务]
E --> I[BI / 报表 & 告警]
C --> J[消息队列(通知/提醒/钉钉/邮件)]
subgraph ThirdParty
K[外部CRM/线索系统] -->|同步 / 导入| B
L[电话/邮件系统] -->|Webhook| C
end
说明:客户管理处在 ERP 的核心位置,需要与订单、报价、生产排程和外部通信系统互通,另外提供向分析服务输出维度化数据以支持 BI 报表和预警。
4. 功能拆解与业务流程(含流程图)
总体客户生命周期流程:线索 → 客户建立 → 跟进(多轮)→ 报价/试产 → 下单 → 交付 → 售后/回访 → 留存或流失。
下面是流程图(Mermaid):
flowchart TD
Lead[线索/客户线索] -->|验证| CustCreate[建立客户档案]
CustCreate --> Follow[跟进记录(多次)]
Follow -->|需求确认| Quote[报价/评审]
Quote -->|通过| SO[销售订单]
SO --> Prod[生产排程]
Prod --> Delivery[交付]
Delivery --> AfterSale[售后/回访]
AfterSale -->|满意| Retain[留存]
AfterSale -->|不满意| Churn[流失/降级]
4.1 客户主档(Customer)
必有字段(最小集):
- id, name, short_name, type(经销/直客), industry, area, 联系人列表(联系人表)、默认结算条款、信用等级、付款方式、结算周期、税号、开户行、状态(active/inactive/潜在) 建议字段:签约日期、客户来源、客户负责人(销售 id)、优先级标签、最近下单时间、累计下单金额、SKU 多样性指标。
设计要点:
- 联系人单独建表(support 多联系人、多渠道)。
- 客户与公司业务一一绑定或多绑定(多组织/多工厂时考虑 tenant 或 org_id)。
4.2 跟进记录(FollowUp / Interaction)
跟进记录要能精确复盘,最少字段:
- id, customer_id, creator_id, create_time, follow_type(电话/邮件/上门/线上会议)、内容(富文本或markdown)、结果(意向/报价中/失效)、下次跟进时间(可空)、附件列表、关联订单/报价 id。
实现要点:
- 每条记录带不可变的时间戳、创建者与编辑记录(审计)。
- 支持 @提醒、状态流转、并且可关联到工单/合同/报价。
4.3 客户标签、分级与生命周期
- 标签系统:自由标签 + 系统标签(VIP、重要、潜在风险、设计合作)。标签用于筛选和触发规则(例如:VIP 下单优先排产)。
- 分级规则示例:可以采用 RFM(最近购买 Recency、频次 Frequency、金额 Monetary)结合交付准时率和投诉率,自动计算分级(A/B/C/D)。
4.4 客户分析(必须有的报表和告警)
关键指标:
- 下单转化率(线索→报价→下单)
- 平均报价响应时间、平均交期偏差、准交率
- 客户等级分布、流失率(N 个月无下单)
- 单均金额、下单周期分布、产品线购买占比
实现建议:
- 把事实数据导入到 OLAP 层(如 ClickHouse 或专门的报表数据库),对关键指标做定时计算。
- 对流失客户做常驻预警(如 90 天无下单且历史贡献 > X 的客户)。
5. 开发技巧与实现注意点(干货)
5.1 数据库与表结构(简要)
- 使用关系型数据库(Postgres/MySQL)存客户主档和跟进记录。分析层可用 OLAP。
- 跟进记录建议做 append-only 表,做软删除(is_deleted 字段),以便审计。
- 指数设计:常用查询字段(customer_id、creator_id、create_time、状态、标签)需建联合索引。对于“最近下单时间”可以在客户表冗余存一列并在订单创建时更新,避免频繁 JOIN。
5.2 接口设计
- REST 风格:分页(cursor 或 page+size)、筛选(标签/负责人/区域/最近下单时间区间)、权限过滤(数据隔离)。
- 支持批量导入(Excel/CSV)与去重策略(根据税号/公司名模糊匹配)。
- 支持模糊搜索(客户名/联系人/电话)——推荐用 Elasticsearch 或数据库的全文索引。
5.3 权限与审计
- 角色权限:销售、销售经理、客服、财务、系统管理员。数据可见范围基于组织/团队/个人。
- 审计日志:跟进记录任何编辑都记录差异(who/when/what),客户敏感字段(信用额度)修改需高级权限并记录审批流程。
5.4 并发与实时性
- 跟进记录写入并发不高,但同时可能存在导入、API 写入、手动修改。注意事务边界,避免重复写入(使用唯一键或幂等 token)。
- 实时性方面,关键的是“变更通知”:跟进后触发消息队列(RabbitMQ/Redis Stream/Kafka)推送给负责人/提醒系统。
5.5 性能与扩展
- 列出常见性能瓶颈:模糊搜索、复杂分析查询、关联大量附件。解决方案:异步处理、缓存(Redis)、专用搜索服务(ES)、把历史跟进归档到冷表或归档库。
5.6 可靠性与数据质量
- 导入/同步接口应有数据校验层(必填字段、格式、重复检查),并返回错误明细。
- 设置“客户清洗”任务:定期合并重复客户、补全重要字段(如税号/结算方式)。
6. 参考代码(较大示例:TypeScript + Express + TypeORM 后端示例)
下面给出一个便于落地的后端示例,包含实体定义、主要 API、以及一个示例分析 SQL(用于计算 RFM)。代码经过注释,作为开发参考。
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany, Index } from 'typeorm';
import { FollowUp } from './FollowUp';
@Entity({ name: 'customers' })
export class Customer {
@PrimaryGeneratedColumn('uuid')
id: string;
@Index()
@Column({ type: 'varchar', length: 200 })
name: string;
@Column({ type: 'varchar', length: 100, nullable: true })
short_name: string;
@Column({ type: 'varchar', length: 50, nullable: true })
type: string; // 'direct' | 'distributor' etc.
@Column({ type: 'varchar', length: 100, nullable: true })
industry: string;
@Column({ type: 'varchar', length: 100, nullable: true })
region: string;
@Column({ type: 'jsonb', nullable: true })
contacts: { name: string; phone?: string; email?: string; title?: string }[];
@Column({ type: 'decimal', precision: 18, scale: 2, default: 0 })
total_amount: number; // 累计下单金额冗余
@Column({ type: 'timestamp', nullable: true })
last_ordered_at: Date;
@Column({ type: 'varchar', length: 50, nullable: true })
tags: string; // simple csv tags, or use relation table
@Column({ type: 'boolean', default: true })
is_active: boolean;
@CreateDateColumn()
created_at: Date;
@UpdateDateColumn()
updated_at: Date;
@OneToMany(() => FollowUp, fu => fu.customer)
followUps: FollowUp[];
}
// src/entities/FollowUp.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn, Index } from 'typeorm';
import { Customer } from './Customer';
@Entity({ name: 'follow_ups' })
export class FollowUp {
@PrimaryGeneratedColumn('uuid')
id: string;
@Index()
@Column()
customerId: string;
@ManyToOne(() => Customer, c => c.followUps)
customer: Customer;
@Column()
creatorId: string; // user id
@Column({ type: 'varchar', length: 50 })
// src/entities/FollowUp.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn, Index } from 'typeorm';
import { Customer } from './Customer';
@Entity({ name: 'follow_ups' })
export class FollowUp {
@PrimaryGeneratedColumn('uuid')
id: string;
@Index()
@Column()
customerId: string;
@ManyToOne(() => Customer, c => c.followUps)
customer: Customer;
@Column()
creatorId: string; // user id
@Column({ type: 'varchar', length: 50 })
type: string; // phone/email/visitation/meeting
@Column({ type: 'text' })
content: string;
@Column({ type: 'varchar', length: 50, nullable: true })
result: string; // 意向、报价中、失效等
@Column({ type: 'timestamp', nullable: true })
next_follow_at: Date;
@Column({ type: 'jsonb', nullable: true })
attachments: { name: string; url: string }[];
@CreateDateColumn()
created_at: Date;
}
7. 实现效果与交付验收建议
交付时建议验收列表(最少项):
- 功能性:客户档案能创建/编辑/删除(软删除),联系人能管理,跟进记录可增删改查,并关联订单/报价。
- 权限:不同角色看到正确的数据范围(销售只能看到自己负责的客户,经理能全公司查看)。
- 性能:客户列表 95% 请求在 200ms 内返回(根据硬件与缓存)。
- 数据质量:导入接口可展示错误明细并支持回滚。
- 报表:至少有一张 RFM 报表和一个流失预警(邮件/钉钉/站内消息)。
- 审计:所有跟进记录的编辑历史可查看。
- 集成:能与报价服务和订单服务联动(至少通过 API 调用/事件总线)。
上线建议:
- 先以“核心最小可用版本”上线(客户档案 + 跟进 + 简单分析),再通过迭代增加自动分级、预警、与生产的深度联动。
- 做用户培训与流程文件(怎样填客户资料、跟进规范、标签规范),保证数据标准化。
8. FAQ
FAQ 1:客户管理模块一定要和外部 CRM 完全合并到 ERP 吗?
不一定。很多企业会同时使用专业 CRM(例如 Salesforce)和 ERP。在 MTO 场景下,关键是“数据一致性”和“流程联动”。建议做法是评估两个系统中的“主数据”归属:如果销售线索、市场活动、外部营销自动化以 CRM 为主,把 CRM 当作前端线索系统;而把 ERP 作为订单、报价、生产与账务的主系统。通过同步(双向或单向)接口,每次将客户主档和重要字段(信用等级、结算方式、最近下单时间)同步到 ERP,并确保在 ERP 中有来源标记与冲突解决策略。若预算允许,也可以把 CRM 的可视化和营销能力通过插件或嵌入方式接入 ERP 界面,减少用户切换。总之,是否合并要基于企业的组织方式、数据规模与成本评估,而不是简单地“全部搬到 ERP”。
FAQ 2:如何避免客户重复、数据不一致的问题?
客户重复通常来自多个入口(手工录入、导入、业务员个人 excel、外部 CRM 同步)。技术上要做三项工作:1)在写入层进行幂等和去重:用税号、统一社会信用码、公司名称 + 联系人电话等做多字段匹配,优先使用精确字段(税号)做唯一性约束;2)建立清洗与合并流程:提供后台合并工具,人工审核并合并重复档案(保留历史跟进与订单);3)在 UI 层提供实时提示:输入公司名时做模糊检索提示可能重复的客户,提示业务员先核查再新增。组织上要制定规范:谁有权限创建客户、创建时必须填写哪几个关键字段(税号/联系人/负责人),并做定期数据质量检查和指标(重复率、空值率)考核。
FAQ 3:如何结合客户跟进记录做自动化提醒与提高成单率?
跟进记录是业务的核心“记忆体”。要把它用好,可以做三件事:1)把跟进记录结构化,关键字段(跟进类型、结果、下次跟进时间、意向强度)标准化,便于机器识别;2)基于结构化字段建立规则引擎,比如“跟进结果=报价中 && 下次跟进时间 <= 今天 且 意向强度 >= 高”触发提醒给对应的销售并在系统中标红;3)把这些规则与分析结合,形成闭环优化:统计哪些跟进组合(如 1 次电话 + 1 次上门)转化率高,把最优跟进路径作为团队最佳实践。技术上提醒可以通过消息队列+通知服务(邮件、站内、企业微信/钉钉)实现;效果评估需要埋点和 A/B 测试(例如对一组客户采用系统化提醒,另一组不采用,观察成单率差异)。