银川九鸿化工需求:商机状态组改为非必填
- 新增商机时「商机状态组」不再必填;未选状态组则不写默认阶段,也不参与状态组维度统计
- 变更商机阶段时先校验状态组:未设置状态组的商机给出精确提示(新增错误码 1_020_002_005,含商机名称与下一步动作),只允许改「结束状态」
- crm_business.status_type_id / status_id 放宽为可空(配置导出脚本 config_export_all_20260904.sql 同步)
- 业务可视化文档同步(销售流程/商机对象:状态组选填)
过程检验单的生产订单/工位在本分支不具备(IPQC 以产品物料为准),无需改动。
Co-Authored-By: Claude Code <noreply@anthropic.com>
| | |
| | | {"id": "sales", "name": "销售流程", "mermaid": "mermaid/02-sales-flow.mmd", "color": "#4f9cff", |
| | | "summary": "线索转化客户 → 商机推进 → 报价(可AI识别) → 合同 BPM 审批 → 生成 ERP 销售订单并审批 → 需生产行自动建 MPS → 发货通知 → 销售出库(OQC)回写 → 开票(BPM) → 回款(先开票校验)。", |
| | | "steps": [ |
| | | {"stage": "获客", "action": "线索转化客户、建联系人、立项商机", "roles": ["销售内勤"]}, |
| | | {"stage": "获客", "action": "线索转化客户、建联系人、立项商机(商机状态组/阶段选填,不选也能建单)", "roles": ["销售内勤"]}, |
| | | {"stage": "报价", "action": "商机赢单后建报价单(AI OCR 预填),转合同", "roles": ["销售内勤"]}, |
| | | {"stage": "签约", "action": "合同提交 BPM 审批(可选流程定义),通过=20", "roles": ["销售", "审批人"]}, |
| | | {"stage": "接单", "action": "合同一键生成销售订单(XSDD),销售订单再走 BPM;审批通过自动为 needProduction 明细创建 MPS", "roles": ["销售", "计划员"]}, |
| | |
| | | {"key": "crm", "name": "销售管理(客户关系)", "color": "#4f9cff", "tag": "CRM", "flow": "sales", |
| | | "positioning": "销售前端:从线索到回款的客户经营", |
| | | "problem": "客户资产沉淀、商机转化、合同审批与开票回款管控", |
| | | "features": ["线索转化客户", "公海池与领取上限", "联系人", "商机自定义阶段", "跟进记录", "报价单(AI OCR)", "合同(BPM审批→生成销售订单)", "开票(BPM)→回款(先开票校验)", "回款计划", "销售目标", "数据权限三级(负责人/只读/读写)"], |
| | | "features": ["线索转化客户", "公海池与领取上限", "联系人", "商机自定义阶段(状态组选填)", "跟进记录", "报价单(AI OCR)", "合同(BPM审批→生成销售订单)", "开票(BPM)→回款(先开票校验)", "回款计划", "销售目标", "数据权限三级(负责人/只读/读写)"], |
| | | "objects": ["线索", "客户", "联系人", "商机", "报价单", "合同", "发票", "回款", "销售目标"], |
| | | "roles": ["销售内勤", "销售经理", "财务(开票回款)"], |
| | | "upstream": ["主数据(物料)"], "downstream": ["进销存(销售订单)", "售后(客户)", "AI(报价OCR)"], |
| | |
| | | {"name": "客户", "module": "销售管理", "table": "crm_customer", "doc": "客户主档,公海池/锁定/成交状态,负责人数据权限", "upstream": ["线索"], "downstream": ["联系人", "商机", "报价单", "合同", "售后工单", "收款单"], "keywords": "客户 customer 公海"}, |
| | | {"name": "线索", "module": "销售管理", "table": "crm_clue", "doc": "原始销售线索,仅一次转化为客户", "upstream": [], "downstream": ["客户"], "keywords": "线索 clue"}, |
| | | {"name": "联系人", "module": "销售管理", "table": "crm_contact", "doc": "客户下联系人,与商机多对多", "upstream": ["客户"], "downstream": ["商机"], "keywords": "联系人 contact"}, |
| | | {"name": "商机", "module": "销售管理", "table": "crm_business", "doc": "销售机会,DB 配置式阶段推进,结束态: 赢/输/无效", "upstream": ["客户"], "downstream": ["报价单", "合同"], "keywords": "商机 business 赢单 输单 阶段"}, |
| | | {"name": "商机", "module": "销售管理", "table": "crm_business", "doc": "销售机会,DB 配置式阶段推进(商机状态组/阶段为选填:不选状态组也能建单,此时无默认阶段、不可变更阶段、不参与状态组统计),结束态: 赢/输/无效", "upstream": ["客户"], "downstream": ["报价单", "合同"], "keywords": "商机 business 赢单 输单 阶段 状态组"}, |
| | | {"name": "报价单", "module": "销售管理", "table": "crm_sale_quotation", "doc": "QT编号,选 MDM 物料报价,AI OCR 录入,草稿→已转合同", "upstream": ["商机"], "downstream": ["合同"], "keywords": "报价 quotation QT"}, |
| | | {"name": "合同", "module": "销售管理", "table": "crm_contract", "doc": "HT编号(新增/编辑均可自定义,留空则新增自动生成、编辑保持原编号),BPM 审批(0草稿/10审批中/20通过/30驳回/40取消/50作废),通过后可生成销售订单", "upstream": ["报价单"], "downstream": ["销售订单", "回款计划", "开票"], "keywords": "合同 contract 审批"}, |
| | | {"name": "发票", "module": "销售管理", "table": "crm_invoice", "doc": "KP编号,BPM 审批,累计开票≤合同额,附件必传", "upstream": ["合同"], "downstream": ["回款"], "keywords": "开票 发票 invoice"}, |
| | |
| | | {"key": "crm", "name": "销售管理(客户关系)", "color": "#4f9cff", "tag": "CRM", "flow": "sales", |
| | | "positioning": "销售前端:从线索到回款的客户经营", |
| | | "problem": "客户资产沉淀、商机转化、合同审批与开票回款管控", |
| | | "features": ["线索转化客户", "公海池与领取上限", "联系人", "商机自定义阶段", "跟进记录", "报价单(AI OCR)", "合同(BPM审批→生成销售订单)", "开票(BPM)→回款(先开票校验)", "回款计划", "销售目标", "数据权限三级(负责人/只读/读写)"], |
| | | "features": ["线索转化客户", "公海池与领取上限", "联系人", "商机自定义阶段(状态组选填)", "跟进记录", "报价单(AI OCR)", "合同(BPM审批→生成销售订单)", "开票(BPM)→回款(先开票校验)", "回款计划", "销售目标", "数据权限三级(负责人/只读/读写)"], |
| | | "objects": ["线索", "客户", "联系人", "商机", "报价单", "合同", "发票", "回款", "销售目标"], |
| | | "roles": ["销售内勤", "销售经理", "财务(开票回款)"], |
| | | "upstream": ["主数据(物料)"], "downstream": ["进销存(销售订单)", "售后(客户)", "AI(报价OCR)"], |
| | |
| | | {"name": "客户", "module": "销售管理", "table": "crm_customer", "doc": "客户主档,公海池/锁定/成交状态,负责人数据权限", "upstream": ["线索"], "downstream": ["联系人", "商机", "报价单", "合同", "售后工单", "收款单"], "keywords": "客户 customer 公海"}, |
| | | {"name": "线索", "module": "销售管理", "table": "crm_clue", "doc": "原始销售线索,仅一次转化为客户", "upstream": [], "downstream": ["客户"], "keywords": "线索 clue"}, |
| | | {"name": "联系人", "module": "销售管理", "table": "crm_contact", "doc": "客户下联系人,与商机多对多", "upstream": ["客户"], "downstream": ["商机"], "keywords": "联系人 contact"}, |
| | | {"name": "商机", "module": "销售管理", "table": "crm_business", "doc": "销售机会,DB 配置式阶段推进,结束态: 赢/输/无效", "upstream": ["客户"], "downstream": ["报价单", "合同"], "keywords": "商机 business 赢单 输单 阶段"}, |
| | | {"name": "商机", "module": "销售管理", "table": "crm_business", "doc": "销售机会,DB 配置式阶段推进(商机状态组/阶段为选填:不选状态组也能建单,此时无默认阶段、不可变更阶段、不参与状态组统计),结束态: 赢/输/无效", "upstream": ["客户"], "downstream": ["报价单", "合同"], "keywords": "商机 business 赢单 输单 阶段 状态组"}, |
| | | {"name": "报价单", "module": "销售管理", "table": "crm_sale_quotation", "doc": "QT编号,选 MDM 物料报价,AI OCR 录入,草稿→已转合同", "upstream": ["商机"], "downstream": ["合同"], "keywords": "报价 quotation QT"}, |
| | | {"name": "合同", "module": "销售管理", "table": "crm_contract", "doc": "HT编号(新增/编辑均可自定义,留空则新增自动生成、编辑保持原编号),BPM 审批(0草稿/10审批中/20通过/30驳回/40取消/50作废),通过后可生成销售订单", "upstream": ["报价单"], "downstream": ["销售订单", "回款计划", "开票"], "keywords": "合同 contract 审批"}, |
| | | {"name": "发票", "module": "销售管理", "table": "crm_invoice", "doc": "KP编号,BPM 审批,累计开票≤合同额,附件必传", "upstream": ["合同"], "downstream": ["回款"], "keywords": "开票 发票 invoice"}, |
| | |
| | | {"id": "sales", "name": "销售流程", "mermaid": "mermaid/02-sales-flow.mmd", "color": "#4f9cff", |
| | | "summary": "线索转化客户 → 商机推进 → 报价(可AI识别) → 合同 BPM 审批 → 生成 ERP 销售订单并审批 → 需生产行自动建 MPS → 发货通知 → 销售出库(OQC)回写 → 开票(BPM) → 回款(先开票校验)。", |
| | | "steps": [ |
| | | {"stage": "获客", "action": "线索转化客户、建联系人、立项商机", "roles": ["销售内勤"]}, |
| | | {"stage": "获客", "action": "线索转化客户、建联系人、立项商机(商机状态组/阶段选填,不选也能建单)", "roles": ["销售内勤"]}, |
| | | {"stage": "报价", "action": "商机赢单后建报价单(AI OCR 预填),转合同", "roles": ["销售内勤"]}, |
| | | {"stage": "签约", "action": "合同提交 BPM 审批(可选流程定义),通过=20", "roles": ["销售", "审批人"]}, |
| | | {"stage": "接单", "action": "合同一键生成销售订单(XSDD),销售订单再走 BPM;审批通过自动为 needProduction 明细创建 MPS", "roles": ["销售", "计划员"]}, |
| | |
| | | A[线索 crm_clue] -->|转化·仅一次| B[客户 crm_customer] |
| | | B --> C[联系人 crm_contact] |
| | | B --> D[商机 crm_business] |
| | | D -->|自定义阶段推进| E{商机结束} |
| | | D -->|自定义阶段推进(状态组选填)| E{商机结束} |
| | | E -->|赢单| F[报价单 QT·草稿0→已转合同10] |
| | | E -->|输单或无效| X[结束] |
| | | F -->|转合同·带入客户产品折扣| G[合同 HT·草稿0→审批中10] |
| | |
| | | `contact_last_time` datetime DEFAULT NULL COMMENT '最后跟进时间', |
| | | `contact_next_time` datetime DEFAULT NULL COMMENT '下次联系时间', |
| | | `owner_user_id` bigint DEFAULT NULL COMMENT '负责人的用户编号', |
| | | `status_type_id` bigint NOT NULL COMMENT '商机状态组编号', |
| | | `status_id` bigint NOT NULL COMMENT '商机状态编号', |
| | | `status_type_id` bigint DEFAULT NULL COMMENT '商机状态组编号', |
| | | `status_id` bigint DEFAULT NULL COMMENT '商机状态编号', |
| | | `end_status` int DEFAULT NULL COMMENT '结束状态', |
| | | `end_remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '结束时的备注', |
| | | `deal_time` datetime DEFAULT NULL COMMENT '预计成交日期', |
| | |
| | | @ExcelProperty("负责人部门") |
| | | private String ownerUserDeptName; |
| | | |
| | | @Schema(description = "商机状态组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714") |
| | | @Schema(description = "商机状态组编号(未设置状态组时为空)", example = "25714") |
| | | private Long statusTypeId; |
| | | @Schema(description = "商机状组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "进行中") |
| | | @Schema(description = "商机状组名字", example = "进行中") |
| | | @ExcelProperty("商机状态组") |
| | | private String statusTypeName; |
| | | |
| | | @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") |
| | | @Schema(description = "商机状态编号(未设置状态组时为空)", example = "30320") |
| | | private Long statusId; |
| | | @Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "跟进中") |
| | | @Schema(description = "状态名称", example = "跟进中") |
| | | @ExcelProperty("商机状态") |
| | | private String statusName; |
| | | |
| | |
| | | @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME) |
| | | private Long ownerUserId; |
| | | |
| | | @Schema(description = "商机状态组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714") |
| | | @Schema(description = "商机状态组编号(非必填:未选择时不带默认阶段,也不参与状态组统计)", example = "25714") |
| | | @DiffLogField(name = "商机状态组") |
| | | @NotNull(message = "商机状态组不能为空") |
| | | private Long statusTypeId; |
| | | |
| | | @Schema(description = "预计成交日期") |
| | |
| | | ErrorCode BUSINESS_UPDATE_STATUS_FAIL_END_STATUS = new ErrorCode(1_020_002_002, "更新商机状态失败,原因:已经是结束状态"); |
| | | ErrorCode BUSINESS_UPDATE_STATUS_FAIL_STATUS_EQUALS = new ErrorCode(1_020_002_003, "更新商机状态失败,原因:已经是该状态"); |
| | | ErrorCode BUSINESS_CONTACT_CUSTOMER_NOT_MATCH = new ErrorCode(1_020_002_004, "商机关联的联系人不属于该客户"); |
| | | ErrorCode BUSINESS_UPDATE_STATUS_FAIL_NO_STATUS_TYPE = new ErrorCode(1_020_002_005, "商机【{}】未设置「商机状态组」,无法变更「商机阶段」。请在新增商机时选择「商机状态组」(需先在【营销管理 - 系统配置 - 商机状态配置】中创建状态组),或改为只变更「结束状态」"); |
| | | |
| | | // ========== 联系人管理 1-020-003-000 ========== |
| | | ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在"); |
| | |
| | | |
| | | // 2.1 插入商机 |
| | | CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class); |
| | | business.setStatusId(businessStatusService.getBusinessStatusListByTypeId(createReqVO.getStatusTypeId()).get(0).getId()); // 默认状态 |
| | | // 商机状态组为选填:未选择时不设置默认阶段,也不参与「商机状态组」维度的统计 |
| | | if (createReqVO.getStatusTypeId() != null) { |
| | | List<CrmBusinessStatusDO> statusList = businessStatusService.getBusinessStatusListByTypeId(createReqVO.getStatusTypeId()); |
| | | business.setStatusId(CollUtil.isNotEmpty(statusList) ? statusList.get(0).getId() : null); // 默认状态 |
| | | } |
| | | calculateTotalPrice(business, businessProducts); |
| | | businessMapper.insert(business); |
| | | // 2.2 插入商机关联商品 |
| | |
| | | // 1.3 校验商机状态 |
| | | CrmBusinessStatusDO status = null; |
| | | if (reqVO.getStatusId() != null) { |
| | | // 商机状态组为选填:未设置状态组的商机没有可选阶段,不允许变更「商机阶段」 |
| | | if (business.getStatusTypeId() == null) { |
| | | throw exception(BUSINESS_UPDATE_STATUS_FAIL_NO_STATUS_TYPE, business.getName()); |
| | | } |
| | | status = businessStatusService.validateBusinessStatus(business.getStatusTypeId(), reqVO.getStatusId()); |
| | | } |
| | | // 1.4 校验是不是状态没变更 |
| | |
| | | // 3. 记录操作日志上下文 |
| | | LogRecordContext.putVariable("businessName", business.getName()); |
| | | LogRecordContext.putVariable("oldStatusName", getBusinessStatusName(business.getEndStatus(), |
| | | businessStatusService.getBusinessStatus(business.getStatusId()))); |
| | | business.getStatusId() != null ? businessStatusService.getBusinessStatus(business.getStatusId()) : null)); |
| | | LogRecordContext.putVariable("newStatusName", getBusinessStatusName(reqVO.getEndStatus(), status)); |
| | | } |
| | | |