如何开发ERP(离散制造-MTO)系统中的销售管理板块(附架构图+流程图+代码参考)

简介: 针对离散制造MTO模式,销售管理是业务核心入口,贯穿报价、订单、ATP、排产与交付。本文详解其架构设计、关键流程、数据模型及开发实践,助力企业提升交付准确率与运营效率。

很多公司做 ERP 时把注意力放在 BOM、生产排产、财务凭证上,结果把“销售”当成一个简单的单据模块:开单 —> 发货。

对于离散制造 + MTO(按订单生产) 企业来说,销售管理是整个业务的入口和“决策中心”:

  • 报价决定毛利与成交概率;
  • 订单驱动生产和采购;
  • 交期承诺直接影响客户满意度与供应链节奏。

因此,做得好能减少沟通、降低交付延期、提高现金回收;

做不好,则频繁改单、加急、库存错配、利润受损。


本文你将了解

  1. 什么是 ERP与销售板块的核心职责
  2. 总体架构(含架构图)
  3. 关键业务流程(含流程图)
  4. 功能拆解
  5. 数据模型设计与索引建议
  6. 接口与事件设计(同步/异步边界)
  7. 开发技巧与工程实践(ATP、并发、幂等、审计、测试、性能)
  8. 实现示例
  9. 验收标准与落地建议

注:本文示例所用方案模板:简道云ERP系统,给大家示例的是一些通用的功能和模块,都是支持自定义修改的,你可以根据自己的需求修改里面的功能。


1. 什么是 ERP(离散制造 - MTO)系统,销售板块为什么重要

离散制造:制造对象是可区分、可拆解的件(设备、机床、零部件)。MTO(Make To Order)意味着大多数生产在接到订单后才启动,库存低、交期关键。销售模块既要做前端的报价和订单管理,还得和物料、BOM、工艺、供应商交期紧耦合:当销售承诺一个交期,需要立刻知道关键件能否按时到位并反馈风险;报价需要结合成本和可变费用来估算毛利,从而指导商务策略。


2.高层架构

+------------------------------------------------------------+

| 客户/销售前端 (Web/移动/自助门户)                          |

+-----------------------------+------------------------------+

                             |

               API 网关(鉴权、限流、统一日志、API 版本)

                             |

+-----------+--------+-------+---------+---------------------+

| Sales Svc | Pricing| Inv Svc| Scheduler | Integration Svc |

| (quotes,  | Svc    | (stock,| (MRP/APS) | (MES/财务/CRM)  |

| orders)   | rules  | ATP)   |           |                 |

+-----------+--------+-------+---------+---------------------+

      |               |         |           |

      +---------------+---------+-----------+

                      |

                事件总线(Kafka / RabbitMQ / Redis Stream)

                      |

               支撑服务:采购、仓库、排产、质检、财务

要点:

  • 拆分服务降低耦合:报价/订单/库存/定价可以分别伸缩。
  • 事件总线用于跨服务异步协同(避免分布式事务)。
  • API 网关统一鉴权(OAuth2/JWT)、审计、限流。

3. 关键业务流程

报价 -> 转订单 -> ATP -> 备料排产 -> 出库 -> 售后(退换货)

  1. 销售/客户在系统提交配置化报价请求(含选配项与特殊工艺)。
  2. 报价服务调用定价引擎(考虑物料成本、工时、外协费用、运输安装等),生成报价单并进入审批流(可选)。
  3. 报价确认后,生成销售订单;系统触发 order.created 事件。
  4. 库存服务对订单行进行 ATP(软检:当前可用量;硬检:在途采购+排产),返回可交期或风险。
  5. 若关键件缺料,触发采购建议/加急单;如果通过,则进入排产并预约产能。
  6. 生产/仓库完成入库与检验后,触发出库拣货、装箱、发运并更新订单状态。
  7. 客户签收后生成应收与财务凭证,如发生退货/换货则走售后流程并联动质检与财务。

4. 功能拆解

4.1 销售管理看板

  • 展示:新建/待确认订单、逾期订单、关键物料缺料列表、出货计划、毛利趋势。
  • 交互:支持按客户/工厂/业务员/产品线筛选,点击订单可穿透到工单、库存和采购建议。
  • 技术点:看板数据大多为汇总,建议用预聚合表 + Redis 缓存 + 定时/事件触发刷新。

4.2 报价单

  • 支持配置化产品(选型逻辑)、费用项(运输/检测/安装)、折扣与审批流、版本管理。
  • 需要支持“成本仿真”:基于当前采购价、工时、外协报价估算毛利。
  • 审批: 可建规则(金额/客户等级/折扣率),超权限走逐级审批并记录审批意见。

4.3 销售订单

  • 订单转化逻辑:报价确认或手工下单生成订单;支持合单/拆单、分批交付。
  • 订单状态机:NEW -> CONFIRMED -> ALLOCATED -> PICKED -> SHIPPED -> CLOSED(并记录变更日志)。
  • 订单变更:需支持价格/数量/交期变更,变更要触发重新 ATP 和排产调整,并记录历史版本。

4.4 销售出库

  • 支持按订单拣货、按交货单分装箱、序列号/批次管理、出库检验。
  • 出库应生成装箱单、物流单号并通知客服/客户。
  • 出库与财务联动:发运触发应收账款或收入确认(按企业会计政策)。

4.5 销售退货

  • 流程:客户申请 -> 创建退货单 -> 仓库/质检检验 -> 判定(入库/返修/报废) -> 退款/抵扣/换货。
  • 需要记录证据(图片/报告)与检验结论,退货影响库存并触发财务记账。

4.6 销售换货

  • 换货可以是“先退后发”或“先发后退”,系统需支持两种策略并记录费用分摊(运费、维修费)。
  • 若涉及质保期,系统应自动回溯原销售订单,判断是否免费换货或按比例收费。

4.7 报表与统计

  • 报价统计:报价量、通过率(成交率)、平均折扣、平均报价响应时间、报价毛利估算。
  • 订单统计:准时交付率、履约率、订单周期(下单到发运)、退货率、主因分析(物料 / 产能 / 质检)。
  • 技术:定期 ETL 到数据仓库(或使用 OLAP),给 BI 看板(PowerBI / Superset)。


5. 数据模型

示例字段:

  • customers:id, code, name, contact(json), credit_limit, payment_terms
  • products:id, sku, name, is_serial(boolean), default_uom, lead_time_days, bom_id
  • quotes:id, quote_no, customer_id, status, created_by, valid_until, total_amount, currency
  • quote_lines:id, quote_id, product_id, qty, unit_price, line_amount, options(json)
  • sales_orders:id, order_no, quote_id, customer_id, status, order_date, promised_date, total_amount
  • order_lines:id, order_id, product_id, qty, unit_price, delivery_date, status
  • stock:product_id, location_id, qty_on_hand, qty_reserved, batch, serial
  • stock_moves:id, type(order/pick/return), ref_id, product_id, qty, from_loc, to_loc, status
  • shipments:id, shipment_no, order_id, ship_date, carrier, tracking_no, status
  • returns:id, return_no, order_id, reason, inspected_result, action

索引建议:

  • 唯一索引:sales_orders(order_no)、quotes(quote_no)
  • 查询索引:order_lines(order_id, product_id)、stock(product_id, location_id)
  • 高频排序/分页字段上建复合索引(如 sales_orders(customer_id, order_date))

6. 接口与事件设计

6.1 关键 REST API

  • POST /api/quotes:创建报价
  • GET /api/quotes/:id:查询报价
  • POST /api/quotes/:id/confirm:确认报价(生成订单)
  • POST /api/orders:创建订单(或由报价转化)
  • GET /api/orders/:id:订单详情
  • POST /api/orders/:id/lock-stock:尝试库存锁定(幂等)
  • POST /api/shipments:创建出库单并触发发运
  • POST /api/returns:创建退货单

6.2 事件

  • order.created -> payload: { orderId }
  • order.updated
  • stock.locked / stock.lock_failed
  • shipment.dispatched
  • return.created / return.inspected

原则:用户交互(报价创建、订单创建)为同步体验;库存锁定/硬 ATP/排产可以是异步,前端通过看板与通知追踪结果。


7. 开发技巧与工程实践

ATP 设计

  • 软检(快速):基于当前可用库存 - 已保留量,返回“可用/不可用/部分可用”。用于快速响应客户。
  • 硬检(深入):计算在途采购、生产排期、外协交期。硬检耗时,放异步任务并在看板展示“可交期”与“风险点”。

幂等与并发

  • 每次外部请求(例如库存锁定)要用 idempotency_key 保证幂等。
  • 库存变更采用乐观锁(version)或数据库行级锁 (SELECT ... FOR UPDATE)。

事件驱动

  • 事件需幂等(事件处理记录消费位移或消费 ID),并支持重试与死信队列。
  • 用事件替代分布式事务:订单写入后发布事件,订阅方做自己的事务与回滚补偿。

审计与可追溯

  • 状态变更、审批记录、价格变更、退货检验都要留痕(谁、何时、原因、附件)。

性能

  • 报价模拟和成本计算可走异步批量,或预缓存关键成本数据(最近采购价、外协成本)。
  • 看板与统计使用物化视图或定时聚合表,避免实时复杂 JOIN。

测试

  • 单元测试(业务逻辑)、集成测试(数据库)、契约测试(服务间 API)和端到端测试(订单流)都要覆盖。

8. 代码实现

后端:server.js(简化)

// server.js

const express = require('express');

const bodyParser = require('body-parser');

const { Pool } = require('pg');

const EventEmitter = require('events');

const bus = new EventEmitter();

const pool = new Pool({

 host: process.env.PG_HOST || 'localhost',

 user: process.env.PG_USER || 'postgres',

 password: process.env.PG_PASS || 'password',

 database: process.env.PG_DB || 'erp',

 port: process.env.PG_PORT || 5432,

});

const app = express();

app.use(bodyParser.json());

// 创建报价

app.post('/api/quotes', async (req, res) => {

 const { customer_id, items, valid_until } = req.body;

 const client = await pool.connect();

 try {

   await client.query('BEGIN');

   const r = await client.query(

     `INSERT INTO quotes (customer_id, status, total_amount, valid_until) VALUES ($1,$2,$3,$4) RETURNING id`,

     [customer_id, 'DRAFT', 0, valid_until]

   );

   const quoteId = r.rows[0].id;

   // items 是 [{product_id, qty, unit_price}]

   for (const it of items) {

     await client.query(

       `INSERT INTO quote_lines (quote_id, product_id, qty, unit_price, line_amount) VALUES ($1,$2,$3,$4,$5)`,

       [quoteId, it.product_id, it.qty, it.unit_price, it.qty * it.unit_price]

     );

   }

   // 简单计算 total

   const t = await client.query(`SELECT SUM(line_amount) as total FROM quote_lines WHERE quote_id=$1`, [quoteId]);

   await client.query(`UPDATE quotes SET total_amount=$1 WHERE id=$2`, [t.rows[0].total || 0, quoteId]);

   await client.query('COMMIT');

   res.status(201).json({ id: quoteId });

 } catch (err) {

   await client.query('ROLLBACK');

   console.error(err);

   res.status(500).send('error');

 } finally {

   client.release();

 }

});

// 报价确认 -> 生成订单并发布事件

app.post('/api/quotes/:id/confirm', async (req, res) => {

 const quoteId = req.params.id;

 const client = await pool.connect();

 try {

   await client.query('BEGIN');

   const q = await client.query(`SELECT * FROM quotes WHERE id=$1 FOR UPDATE`, [quoteId]);

   if (q.rowCount === 0) return res.status(404).send('quote not found');

   const quote = q.rows[0];

   if (quote.status !== 'DRAFT') return res.status(400).send('invalid status');

   const ord = await client.query(

     `INSERT INTO sales_orders (quote_id, customer_id, status, order_date, total_amount) VALUES ($1,$2,$3,$4,$5) RETURNING id`,

     [quoteId, quote.customer_id, 'NEW', new Date(), quote.total_amount]

   );

   const orderId = ord.rows[0].id;

   // copy lines

   const lines = await client.query(`SELECT product_id, qty, unit_price FROM quote_lines WHERE quote_id=$1`, [quoteId]);

   for (const ln of lines.rows) {

     await client.query(

       `INSERT INTO order_lines (order_id, product_id, qty, unit_price, status) VALUES ($1,$2,$3,$4,$5)`,

       [orderId, ln.product_id, ln.qty, ln.unit_price, 'NEW']

     );

   }

   await client.query(`UPDATE quotes SET status='CONFIRMED' WHERE id=$1`, [quoteId]);

   await client.query('COMMIT');

   // 发布事件(事务外)

   bus.emit('order.created', { orderId });

   res.json({ ok: true, orderId });

 } catch (err) {

   await client.query('ROLLBACK');

   console.error(err);

   res.status(500).send('error');

 } finally {

   client.release();

 }

});

// 监听 order.created 并尝试锁库存(示例)

bus.on('order.created', async (payload) => {

 console.log('event order.created', payload);

 // 这里实际应调用库存服务 API 或发送消息到队列

 // 简单示例:读取订单行并标记 reserve(伪操作)

 const client = await pool.connect();

 try {

   const lines = await client.query(`SELECT product_id, qty FROM order_lines WHERE order_id=$1`, [payload.orderId]);

   for (const ln of lines.rows) {

     // 伪逻辑:插入 stock_moves 表当作锁定请求

     await client.query(

       `INSERT INTO stock_moves (type, ref_id, product_id, qty, from_loc, to_loc, status) VALUES ($1,$2,$3,$4,$5,$6,$7)`,

       ['reserve', payload.orderId, ln.product_id, ln.qty, 'WH:MAIN', 'RESERVE', 'PENDING']

     );

   }

   // 这里可触发异步任务处理实际库存扣减

 } catch (err) {

   console.error(err);

 } finally {

   client.release();

 }

});

app.listen(3000, () => console.log('listening 3000'));

数据库示例(Postgres)

CREATE TABLE customers (id serial PRIMARY KEY, code text UNIQUE, name text);

CREATE TABLE products (id serial PRIMARY KEY, sku text UNIQUE, name text, lead_time_days int);

CREATE TABLE quotes (id serial PRIMARY KEY, quote_no text, customer_id int references customers(id), status text, total_amount numeric, valid_until date);

CREATE TABLE quote_lines (id serial PRIMARY KEY, quote_id int references quotes(id), product_id int references products(id), qty numeric, unit_price numeric, line_amount numeric);

CREATE TABLE sales_orders (id serial PRIMARY KEY, order_no text, quote_id int references quotes(id), customer_id int references customers(id), status text, order_date timestamp, total_amount numeric);

CREATE TABLE order_lines (id serial PRIMARY KEY, order_id int references sales_orders(id), product_id int references products(id), qty numeric, unit_price numeric, status text);

CREATE TABLE stock_moves (id serial PRIMARY KEY, type text, ref_id int, product_id int references products(id), qty numeric, from_loc text, to_loc text, status text);

前端 React 示例(创建报价并确认)

// QuoteForm.jsx

import React, { useState } from 'react';

import axios from 'axios';

export default function QuoteForm(){

 const [items, setItems] = useState([{ product_id:1, qty:1, unit_price:100 }]);

 async function createQuote(){

   const res = await axios.post('/api/quotes', { customer_id:1, items, valid_until: '2025-12-31' });

   alert('报价创建 ID: ' + res.data.id);

 }

 async function confirmQuote(id){

   const res = await axios.post(`/api/quotes/${id}/confirm`);

   alert('已确认,订单ID: ' + res.data.orderId);

 }

 return (

   <div>

     <h3>报价(示例)</h3>

     <button onClick={createQuote}>创建报价</button>

     <button onClick={() => confirmQuote(1)}>确认报价ID=1(示例)</button>

   </div>

 );

}


9. 验收标准与交付建议

建议分阶段交付:

  • 阶段 1(基础流):报价创建 → 报价确认 → 生成订单 → 基本出库流程。验收:10 条样例订单完整走通,出库单生成,库存变更正确。
  • 阶段 2(ATP & 统计):实现软 ATP、硬 ATP(异步)、报价毛利模拟、报价审批流。验收:ATP 判定与看板风险提示正确;报价毛利与手工核算误差 <5%。
  • 阶段 3(自动化 & 优化):事件驱动联动采购/排产、看板自定义、性能 & 高可用。验收:系统在 X 并发下响应时间 <200ms(查询类)和能处理并发报价模拟 N 并发(按业务需求定义)。

关键交付件:API 文档(OpenAPI)、ER 图、事件契约文档、运维/备份方案、单元/集成测试报告。


10. 开发中常见问题与解决思路

  • 报价多配置复杂:把配置选项模块化,做配置模板与规则引擎(避免把逻辑硬编码)。
  • 库存冲突:库存锁定用幂等、乐观锁、并行控制,必要时按 SKU 分片处理。
  • 退货占用库存:退货入库要先经过质检再真正增加可用量,并在入库时记录来源与批次。
  • 复杂审批影响效率:审批规则建议配置化(基于金额/客户/折扣),支持并行审批和超时提醒。

11. 结论

  1. 对 MTO 企业,销售管理模块是“把订单可靠地变成生产指令”的关键入口。投资在报价模拟、ATP 与事件驱动的自动化上,能显著提升履约率。
  2. 建议先做小步快跑的 MVP(报价 + 订单 + 出库),再逐步加 ATP、自动下采购、看板与统计。
  3. 强调审计与追溯:销售变更、退货检验、报价审批都要有可查证据,便于责任归属和改进闭环。

FAQ

FAQ 1:在 MTO 场景下,如何设计可用性检查(ATP)以既精准又高效?

ATP(Available To Promise)应做到“快 + 准”。建议分层:

  • 软检查用于快速响应,基于当前可用库存、已承诺数量和简单规则(例如优先分配、保留比),在用户下单瞬间给出反馈;
  • 硬检查会更准确,考虑在途采购、生产排产、外协交期和装配能力,它通常是异步任务并把结果回写到订单看板,提示是否需要延期或触发采购。

硬检查可在低峰时段或通过队列批量执行,避免阻塞用户。关键是把软检作为用户交互的默认体验,硬检作为风险管理与自动化触发的依据,同时在页面上把“软检结果”和“硬检结果”分开展示,便于销售做判断与沟通。

FAQ 2:退货与换货流程怎样联动质检与财务,避免库存与账务错配?

退货流程必须把“质检”作为决定库存动作前的必须步骤:

  • 客户发起退货后系统生成退货单并通知仓库/质检。
  • 质检人员在系统记录检验结论(可入库/返修/报废)并上传照片/检测报告;
  • 只有判定为“可入库”时才正式把物料增加到可用库存并触发财务的入库凭证;
  • 若为“报废”,则生成报废单并触发成本冲销/损失记账。
  • 换货要区分“先退后发”和“先发后退”两种操作流程,并把运费或维修费用的分摊规则在系统中固化。

所有步骤都要有审计日志(谁、何时、结果、附件),以便处理争议。技术上,退货入库应是事务性写入,质检结论驱动库存与会计凭证的生成,确保库存与账务并行一致或有可回溯的补偿流程。

FAQ 3:销售管理模块如何与生产(排产)、采购系统高效协同以降低交期风险?

在 MTO 场景下,销售订单往往直接触发生产与采购。最佳实践是通过事件驱动将订单信息以标准事件(如 order.created、order.updated)广播给库存、采购和排产服务:

  • 库存先尝试锁定关键件,若失败会发布 stock.lock_failed 事件并在看板标记风险;
  • 采购服务订阅事件并根据物料缺口生成采购建议、询价或加急单;
  • 排产服务根据优先级与交期将订单纳入计划并返回可执行的产能承诺。

这样可以避免同步阻塞、提高系统鲁棒性。关键点是设计清晰的事件契约(payload 结构、幂等键、重试策略),并保证事件处理的幂等性与可追踪性。

同时,建立反馈循环:当采购或排产发现无法达成承诺,应立刻把信息回写给销售看板并触发人工或自动化的补救(如调整交期或更换供应商),从而把交期风险在最早阶段暴露并处理。

相关文章
|
14天前
|
SQL 前端开发 关系型数据库
如何开发一套研发项目管理系统?(附架构图+流程图+代码参考)
研发项目管理系统助力企业实现需求、缺陷与变更的全流程管理,支持看板可视化、数据化决策与成本优化。系统以MVP模式快速上线,核心功能包括需求看板、缺陷闭环、自动日报及关键指标分析,助力中小企业提升交付效率与协作质量。
|
10天前
|
人工智能 监控 测试技术
告别只会写提示词:构建生产级LLM系统的完整架构图​
本文系统梳理了从提示词到生产级LLM产品的八大核心能力:提示词工程、上下文工程、微调、RAG、智能体开发、部署、优化与可观测性,助你构建可落地、可迭代的AI产品体系。
203 43
|
9天前
|
消息中间件 数据采集 NoSQL
秒级行情推送系统实战:从触发、采集到入库的端到端架构
本文设计了一套秒级实时行情推送系统,涵盖触发、采集、缓冲、入库与推送五层架构,结合动态代理IP、Kafka/Redis缓冲及WebSocket推送,实现金融数据低延迟、高并发处理,适用于股票、数字货币等实时行情场景。
秒级行情推送系统实战:从触发、采集到入库的端到端架构
|
14天前
|
前端开发 API 定位技术
如何开发车辆管理系统中的用车申请板块(附架构图+流程图+代码参考)
本文详细解析了如何将传统纸质车辆管理流程数字化,涵盖业务规则、审批流、调度决策及数据留痕等核心环节。内容包括用车申请模块的价值定位、系统架构设计、数据模型构建、前端表单实现及后端开发技巧,助力企业打造可落地、易扩展的车辆管理系统。
|
10月前
|
弹性计算 API 持续交付
后端服务架构的微服务化转型
本文旨在探讨后端服务从单体架构向微服务架构转型的过程,分析微服务架构的优势和面临的挑战。文章首先介绍单体架构的局限性,然后详细阐述微服务架构的核心概念及其在现代软件开发中的应用。通过对比两种架构,指出微服务化转型的必要性和实施策略。最后,讨论了微服务架构实施过程中可能遇到的问题及解决方案。
|
11月前
|
Cloud Native Devops 云计算
云计算的未来:云原生架构与微服务的革命####
【10月更文挑战第21天】 随着企业数字化转型的加速,云原生技术正迅速成为IT行业的新宠。本文深入探讨了云原生架构的核心理念、关键技术如容器化和微服务的优势,以及如何通过这些技术实现高效、灵活且可扩展的现代应用开发。我们将揭示云原生如何重塑软件开发流程,提升业务敏捷性,并探索其对企业IT架构的深远影响。 ####
260 3
|
11月前
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####
|
6月前
|
Cloud Native Serverless 流计算
云原生时代的应用架构演进:从微服务到 Serverless 的阿里云实践
云原生技术正重塑企业数字化转型路径。阿里云作为亚太领先云服务商,提供完整云原生产品矩阵:容器服务ACK优化启动速度与镜像分发效率;MSE微服务引擎保障高可用性;ASM服务网格降低资源消耗;函数计算FC突破冷启动瓶颈;SAE重新定义PaaS边界;PolarDB数据库实现存储计算分离;DataWorks简化数据湖构建;Flink实时计算助力风控系统。这些技术已在多行业落地,推动效率提升与商业模式创新,助力企业在数字化浪潮中占据先机。
354 12
|
10月前
|
Java 开发者 微服务
从单体到微服务:如何借助 Spring Cloud 实现架构转型
**Spring Cloud** 是一套基于 Spring 框架的**微服务架构解决方案**,它提供了一系列的工具和组件,帮助开发者快速构建分布式系统,尤其是微服务架构。
793 70
从单体到微服务:如何借助 Spring Cloud 实现架构转型
|
8月前
|
传感器 监控 安全
智慧工地云平台的技术架构解析:微服务+Spring Cloud如何支撑海量数据?
慧工地解决方案依托AI、物联网和BIM技术,实现对施工现场的全方位、立体化管理。通过规范施工、减少安全隐患、节省人力、降低运营成本,提升工地管理的安全性、效率和精益度。该方案适用于大型建筑、基础设施、房地产开发等场景,具备微服务架构、大数据与AI分析、物联网设备联网、多端协同等创新点,推动建筑行业向数字化、智能化转型。未来将融合5G、区块链等技术,助力智慧城市建设。
363 1

热门文章

最新文章