简介:这份文档面向企业信息化建设人员、CRM系统选型与实施团队,以及需要梳理销售业务流程的产品与开发人员,提供一套完整的客户管理系统功能清单参考。内容以计划管理、销售合同、开票、帐款、业绩考核、市场信息、促销品广告、统计报表及公共关系管理等模块为主线,逐项列出三级功能点与功能描述,并配有XSP编码体系,便于按模块检索与对照。压缩包内共1个doc文件,约376KB,适合直接查阅或作为需求文档、功能对照表的底稿使用。目前已有801人学习下载,说明其在CRM功能梳理场景中具备一定参考价值。读者可据此快速了解销售计划、合同变更、调拨开票、应收结转、费用报销与广告合同等业务环节的功能边界,为系统设计、选型评估或二次开发提供清晰的功能框架与字段参考。
1. 从一张“功能清单”到能跑的系统:CRM 客户管理到底管什么
很多团队拿到 CRM 需求时,第一反应是画客户表、联系人表、商机表,结果上线三个月发现销售最关心的回款、开票、调拨全在另一套 Excel 里。这份 CRM 客户管理系统功能清单反着来:它从销售计划、合同、开票、帐款一路铺到业绩考核和市场信息,九个一级模块、上百个三级功能点,本质是一张“营销业务全链路”的施工图。适合谁?适合手里有这份清单、想把它落成可运行系统的开发者,也适合做二次开发时要对照功能边界的产品同学。下面我按“先看清结构、再跑起来、最后能改”的顺序拆一遍。
2. 功能清单背后的模块划分:九个一级模块怎么落到代码结构
2.1 从 XSP01 到 XSP09 看业务闭环
清单里的编号不是随便编的,XSP01 到 XSP09 基本对应一条从计划到执行的链路。计划管理(XSP01)先定销售、回款、费用、生产、要货五类计划;销售合同管理(XSP02)承接计划转成合同;开票管理(XSP03)处理调拨、发票、税票跟踪;帐款管理(XSP04)做回款和月末结转;业绩考核(XSP05)管借款、报销、费用;市场信息(XSP06)和促销品广告(XSP07)是市场侧;统计报表(XSP08)做汇总;公共关系管理(XSP09)是短信、日程、任务这些辅助能力。
落到工程上,我一般按“模块即包”来切,一个一级模块一个顶层包,三级功能点对应到 Controller 的一个方法或 Service 的一个用例。这样清单和代码能一一对上,后期对着功能清单查漏补缺不会漏。
crm-server/ ├── plan/ # XSP01 计划管理 │ ├── controller/ # 销售/回款/费用/生产/要货计划 │ ├── service/ │ └── mapper/ ├── contract/ # XSP02 销售合同 ├── billing/ # XSP03 开票管理(调拨、发票、税票) ├── account/ # XSP04 帐款管理(回款、结转) ├── performance/ # XSP05 业绩考核 ├── market/ # XSP06 市场信息 ├── promotion/ # XSP07 促销品广告 ├── report/ # XSP08 统计报表 └── common/ # XSP09 公共关系 + 公共能力这个结构的好处是,功能清单里任何一个 XSP 编号都能直接定位到目录。坏处是模块间耦合点集中在“单据关联”上,比如合同生成调拨申请、调拨单生成发票,这些跨模块调用要单独抽一个flow包,别塞进某个业务模块里,否则后面改开票逻辑会牵一发动全身。
2.2 单据状态机是这套系统的骨架
清单里反复出现“审核”“红冲”“作废”“结转”“反结”,这些词背后都是状态流转。以调拨单为例,从申请到出库大致是:草稿 → 待审批 → 已审批 → 已调拨 → 已开票 → 已出库,中间还可能走到已作废或已红冲。如果状态用字符串散落在各处判断,后面加一个“部分出库”就能让代码到处翻车。
常见做法是给每类单据建一张状态流转配置表,或者至少在枚举里把合法迁移写死:
public enum TransferStatus { DRAFT, // 草稿 PENDING, // 待审批 APPROVED, // 已审批 TRANSFERRED, // 已调拨 INVOICED, // 已开票 SHIPPED, // 已出库 VOIDED, // 已作废 REVERSED; // 已红冲 // 合法迁移表,新增状态时只改这里 private static final Map<TransferStatus, Set<TransferStatus>> FLOW = Map.of( DRAFT, Set.of(PENDING, VOIDED), PENDING, Set.of(APPROVED, DRAFT), APPROVED, Set.of(TRANSFERRED, VOIDED), TRANSFERRED, Set.of(INVOICED, REVERSED), INVOICED, Set.of(SHIPPED, REVERSED) ); public boolean canMoveTo(TransferStatus target) { return FLOW.getOrDefault(this, Set.of()).contains(target); } }FLOW就是这张状态机的黑匣子,任何业务代码改状态前先调canMoveTo,非法迁移直接抛业务异常。参数说明:this是当前状态,target是目标状态,返回布尔值。这样“已开票的单据不能直接作废、只能红冲”这种规则就落在枚举里,而不是散在 Service 的 if 里。
2.3 可开票量这类计算字段要单独收口
清单里 XSP03.07 写得很清楚:可开票量 = 实际库存 − 开票未提。这种公式型字段最怕每个查询各写一遍,最后口径不一致。我的做法是抽一个BillingCapacityService,所有涉及可开票量的地方都走它:
public BigDecimal calcBillableQty(Long productId, Long warehouseId) { // 实际库存:从库存表汇总 BigDecimal stock = stockMapper.sumAvailable(productId, warehouseId); // 开票未提:已开调拨单但未出库的数量 BigDecimal invoicedNotShipped = transferMapper.sumInvoicedNotShipped(productId, warehouseId); return stock.subtract(invoicedNotShipped); }两个参数分别是成品和库房,返回可开票数量。注意这里sumInvoicedNotShipped的统计口径要和 XSP03.08“开票未提查询”完全一致,否则页面查出来的数和可开票量对不上,业务方第一反应就是系统有 bug。我一般让这两个功能共用同一个 Mapper 方法,从源头保证口径统一。
3. 把系统跑起来:环境、依赖和第一张调拨单
3.1 技术选型与本地环境
这份清单描述的是典型的企业级后台系统,功能点以增删改查、审核、统计报表为主,没有高并发实时计算诉求。按这个场景,合格开发最可能用的组合是 Spring Boot + MyBatis-Plus + MySQL + Redis,前端 Vue 或 React 配 Element/Ant Design 这类组件库。清单里 XSP09 提到短信邮件服务器设置,说明还需要一个消息发送的适配层,本地跑的时候用日志模拟即可。
本地环境我一般这样准备:JDK 17、Maven 3.9、MySQL 8.0、Redis 7。数据库字符集用utf8mb4,因为客户名称、经销单位信息里难免有生僻字。建库脚本按模块拆成多个schema-xxx.sql,初始化数据单独放># 建库并导入结构 mysql -uroot -p -e "CREATE DATABASE crm DEFAULT CHARACTER SET utf8mb4;" mysql -uroot -p crm < sql/schema-all.sql mysql -uroot -p crm < sql/data-init.sql # 启动后端,本地 profile 指向本机 MySQL mvn spring-boot:run -Dspring-boot.run.profiles=local
启动参数里-Dspring-boot.run.profiles=local是关键,application-local.yml里配本机数据库地址和 Redis,别把生产配置混进来。第一次启动如果报数据源连接失败,先确认 MySQL 端口和账号,再看schema-all.sql是否按依赖顺序建表——合同表引用客户表、调拨表引用合同表,顺序错了外键会建不上。
3.2 用一条调拨链路验证环境
环境起来后别急着点页面,先用接口把“合同 → 调拨申请 → 审批 → 调拨单 → 发票”这条链路走一遍,能跑通说明核心模块和状态机都正常。下面是一段用 curl 串链路的示例:
# 1. 从合同生成调拨申请(XSP03.01) curl -X POST http://localhost:8080/api/transfer/apply/from-contract \ -H "Content-Type: application/json" \ -d '{"contractId":1001,"warehouseId":1,"items":[{"productId":2001,"qty":50}]}' # 2. 审批调拨申请(XSP03.03) curl -X POST http://localhost:8080/api/transfer/apply/10001/approve \ -H "Content-Type: application/json" -d '{"approver":"tester"}' # 3. 生成调拨单(XSP03.04) curl -X POST http://localhost:8080/api/transfer/order/from-apply/10001 # 4. 由调拨单生成发票(XSP03.05) curl -X POST http://localhost:8080/api/invoice/from-transfer/20001每一步返回体里都有单据号和状态,重点看状态是否按 2.2 的状态机推进。如果第 3 步报“状态不允许”,大概率是第 2 步审批没成功或者审批人参数为空。这条链路跑通后,再去看 XSP03.07 可开票量,数值应该等于库存减去刚生成的“开票未提”数量,对得上就说明计算口径收口生效了。
3.3 月末结转这类批量任务怎么本地验证
XSP04.02 的月末结转分正式结转、模拟结转、反结三步,这是整套系统里最容易出数据问题的地方。本地验证时不要直接跑正式结转,先跑模拟结转看结果:
-- 模拟结转:只计算不落正式标记 SELECT customer_id, SUM(receivable_amount) AS period_receivable, SUM(received_amount) AS period_received FROM ar_ledger WHERE period = '2024-06' AND settled = 0 GROUP BY customer_id;这条 SQL 就是模拟结转的核心逻辑,settled = 0过滤未结帐数据,按客户汇总应收和已收。正式结转时把结果写入结帐快照表并置settled = 1,反结则是把快照撤销、settled改回 0。注意反结要有权限控制和时间窗口限制,清单里说“方便修改工作”,但没说不限时间,实际项目里我一般限制只能反结最近一个期间,否则历史帐目会被改乱。
4. 核心模块拆解:合同、开票、帐款三处关键实现
4.1 销售合同导入的校验链路
XSP02.01 要求把客户电子文本格式的合同导入系统,还要提供校验。这类导入功能翻车点不在解析,而在校验规则和重复导入。我的做法是解析、校验、落库三段分离,校验不通过整批回滚并返回行级错误。
public ImportResult importContracts(MultipartFile file) { List<ContractRow> rows = excelParser.parse(file); // 解析 List<RowError> errors = new ArrayList<>(); Set<String> seenContractNo = new HashSet<>(); for (int i = 0; i < rows.size(); i++) { ContractRow row = rows.get(i); // 必填校验 if (StringUtils.isBlank(row.getContractNo())) { errors.add(new RowError(i + 2, "合同编号为空")); continue; } // 文件内重复校验 if (!seenContractNo.add(row.getContractNo())) { errors.add(new RowError(i + 2, "合同编号文件内重复")); continue; } // 库内重复校验 if (contractMapper.existsByNo(row.getContractNo())) { errors.add(new RowError(i + 2, "合同编号已存在")); } } if (!errors.isEmpty()) { return ImportResult.fail(errors); // 有错整批不入库 } contractMapper.batchInsert(rows); return ImportResult.ok(rows.size()); }file是上传的电子文本,rows是解析后的行对象,RowError里的行号按 Excel 实际行号算(表头占一行所以i + 2)。这里有个血泪经验:文件内重复和库内重复要分开报,否则用户看到“编号重复”不知道是文件里重了还是系统里已经有了。另外批量插入要控制单批条数,几千行一次性 insert 容易超包大小,按 500 条一批切。
4.2 调拨与税票对帐的关联查询
XSP03.09 要求把调拨单和发票核对,XSP03.16 要求查合同、申请、调拨、出库单据之间的关联关系。这两个功能本质是同一类问题:单据之间靠业务单号串起来,查询时要做多表关联。设计上我建议每张单据都冗余存上游单号,而不是只存上游主键,因为对帐时业务方给的是单号。
-- 调拨与税票对帐:找出调拨单与发票金额/数量不一致的记录 SELECT t.transfer_no, t.qty AS transfer_qty, i.invoice_qty, t.amount AS transfer_amount, i.invoice_amount FROM transfer_order t LEFT JOIN invoice i ON i.source_transfer_no = t.transfer_no WHERE t.status = 'INVOICED' AND (i.invoice_qty IS NULL OR i.invoice_qty <> t.qty OR i.invoice_amount <> t.amount);source_transfer_no就是调拨单号冗余到发票表上的字段,LEFT JOIN保证没生成发票的调拨单也能被查出来。结果集里invoice_qty IS NULL表示调拨了但没开票,数量金额不等表示开票有差异。这个查询直接对应 XSP03.09 的对帐页面,业务方每天跑一遍就能发现漏开票和错开票。
4.3 业绩考核的多维统计
XSP05.07 要求按年月、部门、地区统计费用、销量和销售额,XSP08 的报表也大量是这种多维汇总。这类查询不要在每个报表里写一遍 group by,我一般建一张宽表或者用统一的事实表加维度字段,报表层只做条件拼接。
-- 业绩考核:按部门、地区、月份汇总 SELECT dept_id, region_id, DATE_FORMAT(biz_date, '%Y-%m') AS ym, SUM(sales_amount) AS sales_amount, SUM(sales_qty) AS sales_qty, SUM(expense_amount) AS expense_amount FROM sales_fact WHERE biz_date BETWEEN #{start} AND #{end} GROUP BY dept_id, region_id, DATE_FORMAT(biz_date, '%Y-%m');start、end是查询起止日期,sales_fact是销售事实表,把销量、销售额、费用都放进去,避免报表时反复 join 多张业务表。代价是事实表需要定时同步,我一般用定时任务每天凌晨从各业务表汇总一次,报表查询只读事实表,响应能压到秒级。注意DATE_FORMAT在大数据量下会走不了索引,如果数据上千万,改成按ym字段预存月份。
5. 避坑与排查:这套 CRM 落地时最容易踩的五个坑
5.1 月末结转后改数据导致帐实不符
现象:正式结转后业务方发现某笔回款录错,直接改回款表,结果应收帐款和总帐对不上。原因:结转后应收数据已经进快照,回款表改动不会回写快照。解决:结转后禁止直接改原始单据,走反结流程,反结、修改、重新结转三步走,并在回款维护接口上加期间校验,已结帐期间的回款只允许查看不允许编辑。
5.2 可开票量出现负数
现象:某成品可开票量查出来是负的。原因:开票未提的统计把已作废调拨单也算进去了,或者库存扣减和调拨单生成不在同一事务里。解决:sumInvoicedNotShipped里加status NOT IN ('VOIDED','REVERSED')过滤,同时把调拨单生成和库存预占放在同一个事务,事务提交后再允许开票。
5.3 合同导入部分成功部分失败
现象:导入 100 条合同,成功 60 条失败 40 条,用户不知道哪条错了。原因:逐条插入没有整批事务,错误行被跳过。解决:按 4.1 的做法先全量校验,有错整批不入库并返回行级错误清单,用户改完再导。如果业务坚持要部分成功,至少返回成功和失败两个明细文件。
5.4 调拨单红冲后发票没跟着处理
现象:调拨单红冲了,但对应发票还挂着,对帐时出现负数差异。原因:红冲逻辑只改了调拨单状态,没联动发票。解决:红冲做成一个编排服务,先校验发票状态,未开票的直接红冲调拨单,已开票的先红冲发票再红冲调拨单,整个编排放在一个事务里,任何一步失败全部回滚。
5.5 报表查询慢拖垮业务库
现象:销售月报一跑,业务录入页面就卡。原因:报表直接查业务表还带多表 join,大时间段查询扫全表。解决:按 4.3 建事实表,报表只读事实表;同时给报表查询加时间范围上限,超过一年强制走异步导出,别让一个查询把连接池占满。
6. 二次开发怎么改:从加一个字段到加一个模块
拿到这套系统做二次开发,最常见的需求是“清单里没有但业务要的功能”。我的经验是先判断改动落在哪一层,再决定动哪里。加一个字段最简单:数据库加列、实体加属性、页面加表单项,三步走完,但要注意如果这个字段参与统计,事实表同步任务也要跟着改,否则报表里永远是新字段为空。
加一个三级功能点,比如在 XSP03 下加“调拨单打印次数统计”,就按模块内聚原则放在billing包下,Controller 加一个接口,Service 复用现有调拨查询,别新起一个模块。加一个一级模块,比如客户关系维护,才需要新建包、新建表、在菜单表里注册权限。
改造时我习惯先写一个验证脚本,把改动前后的关键数据对比跑一遍:
# 改造前后对比可开票量口径是否一致 mysql -uroot -p crm -e " SELECT product_id, warehouse_id, billable_qty FROM v_billable_qty ORDER BY product_id, warehouse_id;" > before.txt # 改完代码重新生成视图后再跑一次 diff before.txt after.txt这个习惯救过我一次:改可开票量公式时以为只是加了个过滤条件,结果 diff 出来几十条差异,才发现把“已红冲未重新开票”的单据漏掉了。从那以后我每次动计算口径,都强制走一遍改造前后数据对比,哪怕只改一行 SQL。希望这份拆解能帮你把这份功能清单真正跑起来,少走几个我踩过的坑。
本文还有配套的精品资源,点击获取