首页 / 资讯中心 / 文章详情

CRM客户管理系统数据库设计:合同、开票、回款核心表与流程实现

CRM客户管理系统数据库设计:合同、开票、回款核心表与流程实现 ★ FEATURED ARTICLE
简介这份文档面向企业信息化建设者、CRM系统选型人员及软件开发与实施团队系统梳理了客户管理系统的完整功能框架可作为需求调研、方案设计与功能对照的参考清单。资源为1个doc文件压缩包约376KB内容以分级条目形式组织便于按模块快速检索。文档覆盖计划管理、销售合同管理、开票管理、帐款管理、业绩考核、市场信息、促销品广告、统计报表及公共关系管理等子系统每个功能点均配有编号与简要说明如销售计划维护、合同变更历史查询、调拨与税票对帐、月末结账与反结、费用借还查询等能帮助读者理清业务模块之间的衔接关系。已有801人学习下载适合需要搭建或评估CRM功能体系的技术与业务人员参考。1. 从一张“功能清单”倒推系统骨架CRM 客户管理到底该先建哪几块很多团队拿到 CRM 需求时第一反应是先把客户表和联系人表建起来结果做到一半发现合同、开票、回款三条线全缠在一起改一个字段牵动五个模块。这份功能清单反着来——它把计划、合同、开票、账款、考核、市场、广告、报表、公共关系九大块一次性摊开等于提前把企业销售链路的全貌摆在了桌面上。对正在做 CRM 客户管理毕设或课程设计的人来说这份清单的价值不在于功能多而在于它给出了一个可以直接裁剪的模块边界哪些是主干合同—开票—回款哪些是旁支市场信息、促销品广告哪些是支撑系统设置、群组、日程。先看清骨架再决定自己这版系统砍到哪一层比闷头建表靠谱得多。2. 需求拆解与模块划分九大子系统怎么收敛成可交付的六层结构2.1 从原始清单到分层模型原始清单是九个子系统、上百个三级功能点直接照搬进毕设会失控。我一般会按“数据流向”重新收敛计划层销售/回款/费用/生产/要货计划产生目标数据合同层承接目标并锁定履约义务开票层把合同转成可执行的调拨与发票动作账款层负责资金回笼与月末结转考核层把费用和业绩挂到人市场与广告层是外围信息采集报表层做横向汇总公共关系层是辅助工具。收敛后得到六层结构层级对应原始子系统核心职责毕设建议保留计划层XSP01年/月/季目标录入与审核保留销售计划、回款计划合同层XSP02合同导入、维护、变更、履约全保留开票层XSP03调拨申请→审批→调拨→发票保留直接开票主线账款层XSP04回款录入、月末结转/反结全保留考核层XSP05费用、借款、报销、业绩保留费用与业绩查询支撑层XSP06–XSP09市场信息、广告、报表、公关报表保留其余可裁剪这张表的作用是让你在开题报告里能说清楚“为什么只做这些”——不是做不完是按数据闭环裁剪。计划层和合同层是输入开票层和账款层是输出考核层是反馈四层闭合成一个可演示的销售管理循环答辩时逻辑自洽。2.2 模块边界与依赖关系收敛之后要明确依赖方向否则开发到中期会出现循环引用。我的做法是画一张单向依赖图文字描述即可计划层不依赖任何业务层合同层依赖计划层合同金额要对照计划开票层依赖合同层调拨申请可从合同生成账款层依赖开票层回款对应发票考核层依赖账款层和开票层业绩回款开票报表层依赖以上所有层但只读不写。支撑层中的系统设置被所有层引用但自身不反向依赖。这个依赖方向决定了建表顺序和接口顺序。先建计划表和合同表再建调拨单表和发票表最后建回款表和费用表。如果反过来先做回款你会发现回款要挂发票、发票要挂调拨、调拨要挂合同一路补表补到崩溃。血泪经验是依赖图没画清楚就动手后面每加一个查询都要回头改三张表。2.3 裁剪原则哪些功能点可以合并原始清单里有些功能点其实是同一个动作的不同视角。比如 XSP03.06 销售开票直接开票和 XSP03.04 调拨管理 XSP03.05 发票管理间接开票本质是两种开票模式毕设里保留直接开票一条线即可间接开票作为扩展说明写在设计文档里。再比如 XSP03.15 税票跟踪查询出现了两次清单里编号重复合并为一个查询接口。XSP05.01 分类费用维护在原始清单里编号跳到了 XSP05 下面但描述独立归入考核层的费用模块。合并的原则是同一张主表的不同查询视图合并为一个接口加不同参数同一业务动作的两种模式保留主模式另一种写进设计说明。这样功能点从上百个压到四十个左右一个学期能做完且能演示完整闭环。3. 数据库与核心表设计合同、调拨、发票、回款四张主表怎么串3.1 主表结构与关键字段四张主表是整个系统的脊柱字段设计要预留状态机和关联键。下面给出核心建表语句MySQL 语法字段按“业务必需可扩展”原则取舍-- 销售合同主表合同是后续所有单据的源头 CREATE TABLE crm_contract ( id BIGINT PRIMARY KEY AUTO_INCREMENT, contract_no VARCHAR(32) NOT NULL UNIQUE COMMENT 合同编号业务主键, customer_id BIGINT NOT NULL COMMENT 客户ID关联客户表, sign_date DATE NOT NULL COMMENT 签订日期, total_amount DECIMAL(14,2) NOT NULL DEFAULT 0 COMMENT 合同总额, status TINYINT NOT NULL DEFAULT 0 COMMENT 0草稿 1已审核 2执行中 3已完成 4已变更, version INT NOT NULL DEFAULT 1 COMMENT 变更版本号每次变更1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- 调拨单主表开票动作的载体直接开票模式下与发票一一对应 CREATE TABLE crm_transfer ( id BIGINT PRIMARY KEY AUTO_INCREMENT, transfer_no VARCHAR(32) NOT NULL UNIQUE COMMENT 调拨单号, contract_id BIGINT COMMENT 来源合同可为空无合同调拨, customer_id BIGINT NOT NULL, transfer_date DATE NOT NULL, total_qty INT NOT NULL DEFAULT 0 COMMENT 调拨总数量, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待审 1已审 2已开票 3已作废 4已红冲, invoice_id BIGINT COMMENT 关联发票ID开票后回填, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 发票主表由调拨单生成记录税票信息 CREATE TABLE crm_invoice ( id BIGINT PRIMARY KEY AUTO_INCREMENT, invoice_no VARCHAR(32) NOT NULL UNIQUE COMMENT 发票号, transfer_id BIGINT NOT NULL COMMENT 来源调拨单, customer_id BIGINT NOT NULL, amount DECIMAL(14,2) NOT NULL COMMENT 开票金额, tax_amount DECIMAL(14,2) NOT NULL DEFAULT 0 COMMENT 税额, invoice_date DATE NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0已开 1已登记 2已核销, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 回款主表资金回笼记录挂发票或挂合同 CREATE TABLE crm_receipt ( id BIGINT PRIMARY KEY AUTO_INCREMENT, receipt_no VARCHAR(32) NOT NULL UNIQUE, customer_id BIGINT NOT NULL, invoice_id BIGINT COMMENT 对应发票可空预收款, contract_id BIGINT COMMENT 对应合同可空, amount DECIMAL(14,2) NOT NULL COMMENT 回款金额, receipt_date DATE NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待审 1已审 2已核销, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );字段设计里有几个关键决策。status用 TINYINT 而不是字符串查询快且状态机清晰version字段给合同变更用每次变更生成新版本记录而不是覆盖原记录这样 XSP02.04 合同变更历史查询才有数据可查invoice_id在调拨表里可空因为调拨单先于发票存在开票后回填这个“先有单后有票”的顺序不能反。receipt表同时挂invoice_id和contract_id且都可空是为了支持预收款只挂合同和按票回款挂发票两种场景。3.2 关联关系与状态流转四张表的关系是链式的合同 1:N 调拨单调拨单 1:1 发票直接开票模式下发票 1:N 回款。状态流转要卡死不能跳步。调拨单从“待审”到“已审”需要审批动作对应 XSP03.03 调拨申请审批从“已审”到“已开票”需要生成发票作废只能从“待审”或“已审”发起红冲只能从“已开票”发起且要生成反向单据。状态流转的实现建议用一张状态流水表记录每次变更而不是只改主表状态字段。流水表字段单据类型、单据ID、原状态、新状态、操作人、操作时间、备注。这张表在答辩时是加分项——老师问“你怎么保证数据可追溯”你把流水表一展示比口头解释强十倍。而且 XSP03.16 单据跟踪合同、申请、调拨、出库之间的关联关系本质上就是查这张流水表加关联主表。3.3 可开票量的计算逻辑XSP03.07 可开票量查询的公式是“实际库存开票未提”这个公式看着简单落地时容易翻车。实际库存要从库存表取开票未提要从调拨单里筛“已开票但未出库”的数量。我的做法是建一个视图而不是在代码里拼 SQLCREATE VIEW v_available_invoice_qty AS SELECT p.product_id, p.product_name, IFNULL(s.stock_qty, 0) AS stock_qty, IFNULL(t.pending_qty, 0) AS pending_qty, IFNULL(s.stock_qty, 0) - IFNULL(t.pending_qty, 0) AS available_qty FROM crm_product p LEFT JOIN crm_stock s ON s.product_id p.product_id LEFT JOIN ( SELECT d.product_id, SUM(d.qty) AS pending_qty FROM crm_transfer_detail d JOIN crm_transfer t ON t.id d.transfer_id WHERE t.status 2 -- 已开票 AND NOT EXISTS (SELECT 1 FROM crm_outbound o WHERE o.transfer_id t.id) GROUP BY d.product_id ) t ON t.product_id p.product_id;视图的好处是口径统一报表和查询都走同一个视图不会出现“开票页面算出来 100报表页面算出来 95”的玄学问题。NOT EXISTS子查询判断是否已出库比 LEFT JOIN 加 IS NULL 更直观执行计划也更稳。注意status 2这个硬编码要跟状态字典对齐建议在代码里用常量而不是魔法数字。4. 开票与账款主流程实现从调拨申请到月末结转的代码落地4.1 调拨申请到发票生成的服务层逻辑开票主流程是调拨申请录入 → 审批 → 生成调拨单 → 生成发票。直接开票模式下调拨单和发票可以一步生成。下面给出服务层核心方法Java 风格伪代码重点看逻辑顺序和事务边界Transactional(rollbackFor Exception.class) public InvoiceVO directInvoice(Long transferId, Long operatorId) { // 1. 锁调拨单防止并发重复开票 Transfer transfer transferMapper.selectForUpdate(transferId); if (transfer null) { throw new BizException(调拨单不存在); } // 2. 状态校验只有已审状态才能开票 if (transfer.getStatus() ! TransferStatus.APPROVED) { throw new BizException(调拨单状态不允许开票 transfer.getStatus()); } // 3. 校验可开票量防止超开 for (TransferDetail d : transfer.getDetails()) { int available stockService.getAvailableQty(d.getProductId()); if (d.getQty() available) { throw new BizException(商品[ d.getProductName() ]可开票量不足); } } // 4. 生成发票主表 Invoice invoice new Invoice(); invoice.setInvoiceNo(invoiceNoGenerator.next()); invoice.setTransferId(transferId); invoice.setCustomerId(transfer.getCustomerId()); invoice.setAmount(transfer.getTotalAmount()); invoice.setTaxAmount(transfer.getTotalAmount().multiply(TAX_RATE)); invoice.setInvoiceDate(LocalDate.now()); invoice.setStatus(InvoiceStatus.ISSUED); invoiceMapper.insert(invoice); // 5. 回填调拨单的发票ID并改状态 transfer.setInvoiceId(invoice.getId()); transfer.setStatus(TransferStatus.INVOICED); transferMapper.updateById(transfer); // 6. 写状态流水 statusLogService.log(TRANSFER, transferId, TransferStatus.APPROVED, TransferStatus.INVOICED, operatorId); return toVO(invoice); }这段代码有几个设计点值得说。第一selectForUpdate行锁是必须的两个人同时点“开票”按钮不加锁会生成两张发票这是开票模块最常见的翻车点。第二状态校验放在锁之后、业务计算之前快速失败。第三可开票量校验逐条明细做不能只校验总额因为不同商品库存独立。第四发票号和调拨单号用独立的号段生成器不要用数据库自增 ID 拼字符串否则并发下会重号。第五状态流水在事务内写保证主表和流水一致。4.2 月末结转与反结的幂等设计XSP04.02 月末结账分正式结转、模拟结转、月末反结三个动作。正式结转后应收帐款不可修改反结可以把已结数据置回未结。这里最大的坑是重复结转——用户连点两次“正式结转”如果没做幂等应收帐款会被结转两遍。我的做法是给结转批次建一张表字段包括结账期间年月、结转类型正式/模拟、状态、操作人、操作时间。正式结转前先查该期间是否已有“正式”且“成功”的批次有则拒绝。反结时把该批次状态改为“已反结”并记录反结时间原结转数据打上批次号但不删除查询时按批次过滤。这样既保证了幂等又保留了审计痕迹。-- 结转批次表 CREATE TABLE crm_settle_batch ( id BIGINT PRIMARY KEY AUTO_INCREMENT, period VARCHAR(7) NOT NULL COMMENT 结账期间格式2024-06, settle_type TINYINT NOT NULL COMMENT 1正式 2模拟, status TINYINT NOT NULL DEFAULT 0 COMMENT 0处理中 1成功 2已反结 3失败, operator_id BIGINT NOT NULL, settled_at DATETIME, reversed_at DATETIME, UNIQUE KEY uk_period_type (period, settle_type, status) );唯一索引uk_period_type里带上status是个技巧同一期间同一类型只允许一条“成功”记录但允许存在“已反结”的历史记录。这样反结后可以再次正式结转唯一索引不会挡住。如果只带period settle_type反结后就再也结不了账这是踩过的坑。4.3 回款核销与应收余额联动回款录入后要核销对应发票或合同核销动作影响应收余额。XSP04.01 回款信息维护带审核功能审核通过才参与余额计算。实现上建议回款表加一个verified_amount字段记录已核销金额而不是直接改发票的应收余额——发票余额是计算出来的不是存出来的。应收余额的计算口径合同应收 合同总额已开票金额 该合同下所有有效发票金额之和已回款金额 该合同下所有已审核回款金额之和应收余额 已开票金额 − 已回款金额。这个口径要在设计文档里写死所有查询统一走一个ReceivableService.calcBalance(contractId)方法不允许各页面自己拼 SQL。否则报表页和回款页的数字对不上答辩时被问一句就露馅。5. 避坑与常见问题开票、结转、报表三条线上最容易翻车的五件事5.1 并发开票导致重复生成发票现象两个人同时操作同一张调拨单开票系统生成两张发票金额翻倍。原因查询调拨单时没加行锁两个事务都读到“已审”状态都通过了校验。解决selectForUpdate行锁 状态校验 发票表对transfer_id加唯一索引兜底。唯一索引是最后一道防线即使代码漏了锁数据库也会拒绝第二条记录。5.2 月末反结后数据对不上现象反结某月账务后应收余额和反结前不一致。原因反结时只改了批次状态没有把该批次结转时生成的应收明细标记为无效查询时新旧数据混在一起。解决应收明细表加settle_batch_id字段查询时只取当前有效批次的明细反结时把批次状态改为“已反结”查询条件自动排除。不要物理删除结转数据审计要求留痕。5.3 合同变更后历史履约数据被覆盖现象合同金额从 100 万变更为 80 万后之前按 100 万生成的调拨单和发票对不上账。原因合同变更直接 UPDATE 主表没有保留版本。解决合同表加version字段变更时 INSERT 新版本记录旧版本标记为历史调拨单和发票记录生成时引用的合同版本号履约查询按版本号关联。XSP02.04 合同变更历史查询就是查这张版本表。5.4 可开票量查询结果与库存页不一致现象开票页显示可开票 50库存页显示 60。原因两个页面各自拼 SQL一个减了“开票未提”一个没减。解决统一走v_available_invoice_qty视图所有涉及可开票量的查询都从这个视图取数。视图定义变更时只改一处不会漏。5.5 报表统计口径与明细页对不上现象销售月报的金额和合同明细页的合计差几分钱。原因报表用了ROUND而明细页没舍入或者报表按开票日期统计而明细按合同日期统计。解决报表层统一调用服务层的统计方法不在 SQL 里做二次聚合统计口径按什么日期、含不含作废单据写进设计文档并在报表页脚注明。几分钱的差异在答辩时是致命伤老师会追着问。6. 验收与答辩用一条数据链路跑通全流程比背功能清单管用验收这套 CRM 系统我一般不看功能列表勾了多少项而是让演示者用一条数据从头走到尾新建一个客户 → 录入一份年度销售计划 → 签一份合同 → 从合同生成调拨申请 → 审批 → 生成调拨单 → 开票 → 录入回款 → 核销 → 跑一次模拟结转 → 查销售月报。这条链路走通说明计划、合同、开票、账款、报表五层的数据是打通的走不通功能列表再长也是散的。答辩时老师最爱问的三个问题提前准备好答案。第一个是“你的可开票量怎么算的”答实际库存减开票未提走统一视图并现场展示视图定义。第二个是“月末结转后数据还能改吗”答正式结转后应收明细锁定反结走批次状态切换原数据留痕不删除现场演示反结后重新结转。第三个是“合同变更了之前的发票怎么办”答合同按版本管理发票记录生成时引用的合同版本号变更不影响已生成单据现场查变更历史。验收检查表可以按下面这个结构准备答辩前自己先跑一遍检查项通过标准验证方式主流程闭环客户→计划→合同→调拨→发票→回款→报表全通现场走一遍状态机完整调拨单五种状态流转无跳步逐状态操作并发安全同一调拨单重复开票只成功一次两个浏览器同时点结转幂等同期间重复正式结转被拒绝连点两次反结可恢复反结后重新结转数据一致反结再结对比报表口径一致月报合计等于明细合计交叉核对最后说个习惯。我每次带项目验收前一定自己当“刁钻用户”把每个按钮连点三遍尤其是开票、结转、反结这三个动作——这三个地方出问题演示时当场翻车没有后悔药。从那以后我每次交方案前都强制走一遍这条数据链路哪怕功能只做了一半链路必须是通的。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站