SpringBoot+Vue3:OA、CRM、ERP,为什么要放进一套系统?
销售说合同签了,仓库说还没收到订单,行政说用印记录在另一套系统里。几个人都在系统里工作,交接时却仍要重新发一遍资料。
这是一体化平台值得讨论的场景。但把三个系统放在同一个菜单里,并不会让问题自动消失。
OA关心事情怎样办理,CRM关心客户怎样推进,ERP关心交易与履约怎样执行。把它们放在一起,真正值得追求的是:相同的资料少维护几遍,已经发生的业务有据可查,下一位办理人知道该从哪里接手。
以我们开发的RuoYi Office为例,下面把页面效果、真实数据关系和实现取舍放在一起看。本文没有把所有模块都已自动贯通当成前提,而是区分哪些是共同入口,哪些已经有具体实现,哪些仍需按场景核验。
一套界面,先解决“到哪里办事”
员工通常不会按软件分类思考工作。他想申请用印、查客户、看订单,而不是先决定这件事属于OA、CRM还是ERP。
统一导航能减少寻找入口的成本。对使用多个业务模块的人来说,共同的页面习惯也有价值:哪里是单据编号,哪里看状态,哪里找附件,不必在每个模块重新摸索。
▲ 已有演示用印单。办公事项有自己的业务字段,页面同时保留其他模块入口;导航统一不代表这些模块的数据已全部互通。
这一步看得见,也容易理解,但它只是起点。如果打开CRM要维护一份客户,打开ERP又得录入同一个公司的名称和资料,那么界面已经统一,重复劳动仍然存在。
所以评估“一体化”,可以分成三层:
| 层次 | 员工能感受到什么 | 需要看到什么依据 |
|---|---|---|
| 统一入口 | 找业务更方便,操作习惯接近 | 导航、账号和页面组织 |
| 共享基础资料 | 同一对象不必反复维护 | 主档来源、标识映射与维护责任 |
| 业务联动 | 上一步的结果能供下一步使用 | 单据引用、状态变化与异常处理 |
三层可以逐步建设。只完成第一层并不等于没有价值,但不能把它宣传成后面两层也已经完成。
客户资料少录一遍,背后要多想一步
一家公司可能在销售人员那里叫简称,在订单里写全称,在历史表格里又有另一种写法。按名称直接匹配,既可能漏掉,也可能把不同主体认成同一个。
这就是主数据值得介入的地方。它不负责取代销售跟进,而是为共同使用的对象建立可识别、可维护的档案。
当前工程里,ERP客户列表已经优先读取MDM客商主数据中的客户角色。查询单个客户时,也先尝试读取主档,历史单据引用的旧ERP客户保留兼容回退。
publicErpCustomerDOgetCustomer(Longid){MdmPartnerRespDTOpartner=mdmMasterDataHelper.getPartner(id);if(partner!=null&&Boolean.TRUE.equals(partner.getCustomer())){returntoCustomerDO(partner);}// 历史单据可能仍引用旧 erp_customer.idreturncustomerMapper.selectById(id);}这段实际实现表达了一个产品取舍:新的公共档案逐步向主数据集中,同时照顾已有业务记录。它不是简单把旧表删掉,也不是要求每个业务模块马上重写全部逻辑。
维护入口同样需要收敛。当前ERP客户的新增、修改和删除服务会调用禁止直接维护主数据的辅助方法,避免一边希望统一档案,一边又允许各处任意修改。
对于软件公司,这类细节比“共享数据库”四个字更有参考价值。主档由谁维护,业务模块读哪些字段,历史记录如何解释,决定了一体化是否真的减少维护成本。
跨模块的联系,要能沿着来源找回去
客户资料从哪里来,也需要有记录。
源码中的CRM客户主数据发布器,会组织来源系统、来源ID及客户相关字段,通过Redis Stream发送同步消息;MDM侧有对应入站处理。入站服务会先查来源映射,再决定创建或更新客商档案。
来源映射查询本身并不复杂:
publicLongfindMdmId(StringdataType,StringsourceSystem,StringsourceId){MdmSourceMappingDOmapping=sourceMappingMapper.selectBySource(dataType,sourceSystem,sourceId);returnmapping==null?null:mapping.getMdmId();}关键是这三个维度共同出现:什么类型的数据、来自哪个系统、原来的记录是谁。这样,来源系统里的一个编号不必被误认为全平台唯一,也不必只靠名称猜测。
这里已经有具体同步实现,但“发送了消息”和“其他模块现在就能读到最新数据”仍是两件事。相关组件需要启用,消息需要实际送达并被处理,字段覆盖策略也会影响最终结果。
当前发布器在发送失败时记录警告,并没有在这一段代码里向调用方抛出失败。因此,不能仅凭CRM保存成功,就断言主档同步完成。上线时应核对同步状态与失败处理;如果需要更强的补偿或对账机制,也应作为明确的交付内容验证。
这不是给一体化泼冷水,而是说明它应该怎样做得可解释:谁提供资料、谁负责维护、传递失败在哪里看,都有答案,协同才不会退回到人工猜测。
真正的联动,可以先从订单与出库看起
主数据解决“这是同一个对象”,业务单据还要解决“这些动作属于同一件事”。
下面两张图来自同一批已有ERP演示记录,使用独立移动H5页面展示。它们是历史采集的真实页面,不是本次新建订单,也不是原生APP真机验证。
▲ 订单ERP-HOME-SO-202609-01中,总数量和出库数量均显示58;这是这条演示记录的状态,不代表所有订单都一次交付。
▲ 出库单明确关联前面的订单,客户标识也一致。通过单据引用可以追溯业务关系,不必仅凭相同金额猜测。
这类关系有直接的使用价值。销售看订单时关心交付了多少,仓库处理的是具体出库记录,两者可以保留各自页面和职责,同时围绕订单发生关联。
服务端也有相应的数量回写逻辑:按订单寻找关联出库单,汇总订单项的出库数量,再更新订单。
privatevoidupdateSaleOrderOutCount(LongorderId){List<ErpSaleOutDO>saleOuts=saleOutMapper.selectListByOrderId(orderId);Map<Long,BigDecimal>returnCountMap=saleOutItemMapper.selectOrderItemCountSumMapByOutIds(convertList(saleOuts,ErpSaleOutDO::getId));saleOrderService.updateSaleOrderOutCount(orderId,returnCountMap);}上面的局部代码说明关联与回写的位置,不是对整套库存、审核、退货和财务规则的完整描述。这里也没有把订单金额称为现金收入:交付、收款、开票应分别按各自业务口径理解。
把这种具体关系落实,比做一张“所有模块互相连线”的架构图更重要。每条连线都应该能回答:传什么,什么时候传,谁使用,失败后怎么办。
一体化也需要边界,否则改一个字段就牵动全系统
看见数据可以共用,很容易进一步想:是不是把所有模块的字段都放到同一张表里最省事?
通常没必要。客户的公共身份可以共用,销售跟进记录仍由CRM负责;办公申请保留审批和材料,出入库记录保留自己的库存意义。它们可以引用同一个对象,但不必成为同一个业务模型。
以客户名称变更为例,主档需要反映当前信息;历史单据究竟显示最新名称,还是保留发生当时的抬头,要结合单据用途决定。这是设计问题,不能用“全局实时同步”一笔带过。
组织与权限也一样。一个用户在多个模块有入口,不代表他应当看到所有数据。客户负责人、仓库办理人、审批人员的职责不同,共用账号之后还需要保留正确的操作范围。
对研发团队来说,可以把边界落实为几项明确约定:公共资料由谁写,业务模块通过什么接口读取,哪些事件触发后续动作,哪些字段需要保留历史快照。这些约定足够清楚,模块之间的协作才有机会稳定。
SpringBoot和Vue3提供了熟悉的工程基础,组件和服务也便于组织,但技术栈本身不会替团队决定这些业务责任。真正的一体化设计,最终还是要回到具体记录与操作。
值不值得放在一起,看交接成本而不只看模块数量
如果几个部门经常围绕相同客户、合同或订单协作,共用基础能力和业务引用,值得认真评估。尤其对需要二次开发的团队,已有模块和接口能让新增需求有可复用的起点。
如果现有专业系统已经稳定运行,行业能力很深,也没有必要为了统一界面一次替换全部系统。保留专业应用,通过明确接口和主数据关系接入,同样可以服务一体化目标。
比较方案时,不妨选一件需要跨部门完成的事,让不同角色分别操作。看看资料是否重复录入,单据是否有来源,状态是否能解释,出现异常后是否能找到负责的位置。这比“总共多少模块”更能说明实际价值。
对于RuoYi Office,这篇给出的依据是共同业务界面、客商主数据读取与来源同步实现,以及订单出库的真实关联。它们说明平台里有可以落到代码和页面的协同设计;其他跨模块链路仍应按实际需求逐项确认,不能由此推导全部自动互通。
OA、CRM、ERP放在一套系统里,最有吸引力的结果,是员工少找一次入口、少录一份重复资料,也让下一位办理人更容易接上前一步。模块可以丰富,真正该被组织起来的是业务之间的关系。