售后工单模块 - 前端联调方案
涉及页面(全部新建)
| 页面 |
路由建议 |
说明 |
| 售后工单列表 |
/aftersales/ticket |
工单列表、搜索、状态筛选 |
| 售后工单详情/编辑 |
/aftersales/ticket/detail?id=:id |
新建/编辑工单、查看详情 |
| 退货申请列表 |
/aftersales/return |
退货申请列表、搜索 |
| 退货申请详情 |
/aftersales/return/detail?id=:id |
退货详情、审核操作 |
业务流程与数据带入
流程一:一般问题工单
创建工单 → 分配处理人 → 问题判定(一般问题)→ 处理 → 完结 → 关闭
流程二:退货问题工单
创建工单 → 分配处理人 → 问题判定(退货问题)
→ 创建退货申请 → 销售审核 → [MES退货流程自动执行] → 退款/换货完成 → 完结 → 关闭
流程三:维修问题工单
创建工单 → 分配处理人 → 问题判定(维修问题)
→ 创建维修记录 → 维修完成 → 完结 → 关闭
数据带入关系
| 步骤 |
来源 |
带入字段 |
| 创建退货申请 |
工单详情 |
ticketId、customerId、saleOrderId、工单明细行(ticketItemId、itemId、itemCode、itemName) |
| 退货申请审批 |
退货申请 |
id(通过 URL 参数或按钮传入) |
重点说明:
- 创建退货申请时,从工单详情页的明细行中筛选 needReturn = true 的行作为退货明细
- 退货明细中的 ticketItemId 必须对应工单明细行的 id
- 物料信息(itemId、itemCode、itemName)从工单明细行带入,不允许修改
- 销售单价(unitPrice)需要从销售订单行查询获取,前端可通过已有的 ERP 销售订单接口查询
API 说明
一、售后工单
| 方法 |
路径 |
权限 |
说明 |
| POST |
/aftersales/ticket/create |
aftersales:ticket:create |
创建工单 |
| PUT |
/aftersales/ticket/update |
aftersales:ticket:update |
更新工单(仅草稿状态可更新) |
| DELETE |
/aftersales/ticket/delete |
aftersales:ticket:delete |
删除工单(仅草稿/已取消状态可删除) |
| GET |
/aftersales/ticket/get |
aftersales:ticket:query |
获取工单详情 |
| GET |
/aftersales/ticket/page |
aftersales:ticket:query |
分页查询工单 |
| POST |
/aftersales/ticket/assign |
aftersales:ticket:update |
分配处理人 |
| POST |
/aftersales/ticket/classify |
aftersales:ticket:update |
问题判定 |
| PUT |
/aftersales/ticket/resolve |
aftersales:ticket:update |
完结工单 |
| PUT |
/aftersales/ticket/close |
aftersales:ticket:update |
关闭工单 |
| PUT |
/aftersales/ticket/cancel |
aftersales:ticket:update |
取消工单 |
1.1 创建工单 POST /aftersales/ticket/create
请求参数:
| 参数 |
类型 |
必填 |
说明 |
| name |
String |
是 |
工单主题 |
| customerId |
Long |
是 |
客户编号 |
| contactId |
Long |
否 |
联系人编号 |
| saleOrderId |
Long |
否 |
销售订单编号 |
| saleOrderNo |
String |
否 |
销售订单号(冗余,与 saleOrderId 配套传入) |
| contractId |
Long |
否 |
合同编号 |
| issueDescription |
String |
否 |
问题描述 |
| remark |
String |
否 |
备注 |
| items |
List<Item> |
否 |
明细行列表 |
Item 结构:
| 参数 |
类型 |
必填 |
说明 |
| saleOrderItemId |
Long |
否 |
销售订单行编号 |
| itemId |
Long |
是 |
MDM 物料编号 |
| itemCode |
String |
否 |
物料编码 |
| itemName |
String |
否 |
物料名称 |
| batchId |
Long |
否 |
生产批次编号 |
| batchCode |
String |
否 |
生产批次号 |
| saleQuantity |
BigDecimal |
否 |
销售数量 |
| returnQuantity |
BigDecimal |
否 |
退货数量 |
| needRepair |
Boolean |
否 |
是否需要维修 |
| needReturn |
Boolean |
否 |
是否需要退货 |
| remark |
String |
否 |
备注 |
请求示例:
json { "name": "产品外观缺陷投诉", "customerId": 1, "contactId": 2, "saleOrderId": 100, "saleOrderNo": "SO202401010001", "contractId": 50, "issueDescription": "收到货后发现产品表面有明显划痕", "remark": "客户要求尽快处理", "items": [ { "saleOrderItemId": 10, "itemId": 200, "itemCode": "MAT-001", "itemName": "电子元器件", "batchId": 5, "batchCode": "BATCH-2024-001", "saleQuantity": 100.000, "returnQuantity": 10.000, "needRepair": false, "needReturn": true, "remark": "外观缺陷" } ] }
响应: { "code": 0, "data": 1 } — data 为新建工单 ID
1.2 更新工单 PUT /aftersales/ticket/update
请求参数与创建相同,额外需要 id 字段。仅草稿状态可更新。
1.3 删除工单 DELETE /aftersales/ticket/delete
| 参数 |
类型 |
必填 |
说明 |
| id |
Long |
是 |
工单编号(Query 参数) |
响应: { "code": 0, "data": true }
1.4 获取工单详情 GET /aftersales/ticket/get
| 参数 |
类型 |
必填 |
说明 |
| id |
Long |
是 |
工单编号(Query 参数) |
响应字段:
| 字段 |
类型 |
说明 |
| id |
Long |
工单编号 |
| no |
String |
工单单号(格式:AS + yyyyMMdd + 6位序号) |
| name |
String |
工单主题 |
| customerId |
Long |
客户编号 |
| customerName |
String |
客户名称 |
| contactId |
Long |
联系人编号 |
| contactName |
String |
联系人名称 |
| saleOrderId |
Long |
销售订单编号 |
| saleOrderNo |
String |
销售订单号 |
| contractId |
Long |
合同编号 |
| issueType |
Integer |
问题类型:1-一般问题 2-维修问题 3-退货问题 |
| issueCategory |
String |
问题分类 |
| issueDescription |
String |
问题描述 |
| severityLevel |
Integer |
严重程度:0-轻微 1-一般 2-严重 3-致命 |
| status |
Integer |
状态(见状态枚举) |
| ownerUserId |
Long |
负责人用户编号 |
| ownerUserName |
String |
负责人名称 |
| handlerUserId |
Long |
处理人用户编号 |
| handlerUserName |
String |
处理人名称 |
| processInstanceId |
String |
BPM 工作流实例编号 |
| returnId |
Long |
关联退货申请编号 |
| repairId |
Long |
关联维修记录编号 |
| resolvedTime |
DateTime |
解决时间 |
| closedTime |
DateTime |
关闭时间 |
| remark |
String |
备注 |
| creator |
String |
创建者 |
| createTime |
DateTime |
创建时间 |
| updater |
String |
更新者 |
| updateTime |
DateTime |
更新时间 |
| items |
List<Item> |
明细行列表 |
响应示例:
json { "code": 0, "data": { "id": 1, "no": "AS20260730000001", "name": "产品外观缺陷投诉", "customerId": 1, "customerName": "XX科技有限公司", "contactId": 2, "contactName": "张三", "saleOrderId": 100, "saleOrderNo": "SO202401010001", "contractId": 50, "issueType": 3, "issueCategory": "外观缺陷", "issueDescription": "收到货后发现产品表面有明显划痕", "severityLevel": 1, "status": 30, "ownerUserId": 1, "ownerUserName": "管理员", "handlerUserId": 2, "handlerUserName": "售后专员", "returnId": 1, "items": [ { "id": 1, "ticketId": 1, "saleOrderItemId": 10, "itemId": 200, "itemCode": "MAT-001", "itemName": "电子元器件", "batchId": 5, "batchCode": "BATCH-2024-001", "saleQuantity": 100.000, "returnQuantity": 10.000, "needRepair": false, "needReturn": true } ] } }
1.5 分页查询工单 GET /aftersales/ticket/page
| 参数 |
类型 |
必填 |
说明 |
| pageNo |
Integer |
否 |
页码,默认 1 |
| pageSize |
Integer |
否 |
每页大小,默认 10 |
| no |
String |
否 |
工单单号 |
| name |
String |
否 |
工单主题 |
| customerId |
Long |
否 |
客户编号 |
| saleOrderId |
Long |
否 |
销售订单编号 |
| status |
Integer |
否 |
工单状态 |
| issueType |
Integer |
否 |
问题类型 |
| sceneType |
Integer |
否 |
场景类型:1-我负责的 2-我参与的 3-下属负责的 4-全部 |
响应:
json { "code": 0, "data": { "list": [ /* AfterSaleTicketRespVO 数组 */ ], "total": 100 } }
注意: 分页列表响应中的每条记录结构与详情接口一致(含 items 和关联名称字段),但列表场景下 items 可为空。
1.6 分配处理人 POST /aftersales/ticket/assign
| 参数 |
类型 |
必填 |
说明 |
| id |
Long |
是 |
工单编号(Query 参数) |
| handlerUserId |
Long |
是 |
处理人用户编号(Query 参数) |
业务规则: 仅草稿、处理中状态可分配。分配后状态自动变为"处理中"。
1.7 问题判定 POST /aftersales/ticket/classify
| 参数 |
类型 |
必填 |
说明 |
| id |
Long |
是 |
工单编号(Query 参数) |
| issueType |
Integer |
是 |
问题类型:1-一般问题 2-维修问题 3-退货问题 |
| issueCategory |
String |
否 |
问题分类(如"外观缺陷"、"功能故障"等) |
| severityLevel |
Integer |
否 |
严重程度:0-轻微 1-一般 2-严重 3-致命 |
业务规则: 仅处理中状态可判定。判定为退货问题后,应引导用户创建退货申请。
1.8 完结工单 PUT /aftersales/ticket/resolve
| 参数 |
类型 |
必填 |
说明 |
| id |
Long |
是 |
工单编号(Query 参数) |
业务规则: 仅处理中/待退货/待退款状态可完结。
1.9 关闭工单 PUT /aftersales/ticket/close
| 参数 |
类型 |
必填 |
说明 |
| id |
Long |
是 |
工单编号(Query 参数) |
业务规则: 仅已完结状态可关闭。
1.10 取消工单 PUT /aftersales/ticket/cancel
| 参数 |
类型 |
必填 |
说明 |
| id |
Long |
是 |
工单编号(Query 参数) |
业务规则: 仅草稿、处理中状态可取消。
二、售后退货申请
| 方法 |
路径 |
权限 |
说明 |
| POST |
/aftersales/return/create |
aftersales:return:create |
创建退货申请 |
| PUT |
/aftersales/return/approve |
aftersales:return:approve |
审核通过(触发 MES 退货单) |
| GET |
/aftersales/return/get |
aftersales:return:query |
获取退货详情 |
| GET |
/aftersales/return/page |
aftersales:return:query |
分页查询退货申请 |
2.1 创建退货申请 POST /aftersales/return/create
请求参数:
| 参数 |
类型 |
必填 |
说明 |
| ticketId |
Long |
是 |
关联工单编号 |
| customerId |
Long |
是 |
客户编号(从工单带入) |
| saleOrderId |
Long |
否 |
关联销售订单编号(从工单带入) |
| returnType |
Integer |
是 |
退货类型:1-退款退货 2-换货 3-仅退款 |
| returnReason |
String |
是 |
退货原因 |
| defectCategory |
String |
否 |
缺陷分类 |
| remark |
String |
否 |
备注 |
| items |
List<Item> |
是 |
退货明细行列表 |
Item 结构:
| 参数 |
类型 |
必填 |
说明 |
| ticketItemId |
Long |
是 |
工单明细行编号(关联 after_sale_ticket_item.id) |
| itemId |
Long |
是 |
MDM 物料编号(从工单明细行带入) |
| itemCode |
String |
否 |
物料编码 |
| itemName |
String |
否 |
物料名称 |
| batchId |
Long |
否 |
批次编号 |
| batchCode |
String |
否 |
批次号 |
| returnQuantity |
BigDecimal |
是 |
退货数量 |
| unitPrice |
BigDecimal |
是 |
销售单价(需从销售订单行查询获取) |
| returnPrice |
BigDecimal |
否 |
退货金额(= returnQuantity × unitPrice) |
| remark |
String |
否 |
备注 |
请求示例:
json { "ticketId": 1, "customerId": 1, "saleOrderId": 100, "returnType": 1, "returnReason": "产品外观缺陷", "defectCategory": "外观缺陷", "remark": "客户要求退货退款", "items": [ { "ticketItemId": 1, "itemId": 200, "itemCode": "MAT-001", "itemName": "电子元器件", "batchId": 5, "batchCode": "BATCH-2024-001", "returnQuantity": 10.000, "unitPrice": 100.00, "returnPrice": 1000.00 } ] }
响应: { "code": 0, "data": 1 } — data 为新建退货申请 ID
业务规则:
- 创建退货申请后,关联工单状态自动变为"待退货(30)"
- returnPrice 后端会自动计算(returnQuantity × unitPrice 求和),前端可不传
2.2 审核通过 PUT /aftersales/return/approve
| 参数 |
类型 |
必填 |
说明 |
| id |
Long |
是 |
退货申请编号(Query 参数) |
业务规则:
- 仅草稿状态(0)可审核
- 审核通过后,后端自动调用 MES 创建销售退货单,状态变为"审核通过-已同步MES(20)"
- 后续 MES 退货流程(质检→入库→退款)由后端回调自动完成,前端无需操作
2.3 获取退货详情 GET /aftersales/return/get
| 参数 |
类型 |
必填 |
说明 |
| id |
Long |
是 |
退货申请编号(Query 参数) |
响应字段(新增/重要字段):
| 字段 |
类型 |
说明 |
| id |
Long |
退货申请编号 |
| no |
String |
退货单号(格式:RT + yyyyMMdd + 6位序号) |
| ticketId |
Long |
关联工单编号 |
| customerId |
Long |
客户编号 |
| customerName |
String |
客户名称 |
| saleOrderId |
Long |
销售订单编号 |
| returnType |
Integer |
退货类型 |
| returnReason |
String |
退货原因 |
| defectCategory |
String |
缺陷分类 |
| totalReturnPrice |
BigDecimal |
应退总金额 |
| actualRefundPrice |
BigDecimal |
实退金额 |
| status |
Integer |
状态(见退货状态枚举) |
| auditUserId |
Long |
审核人 |
| auditTime |
DateTime |
审核时间 |
| mesReturnId |
Long |
MES退货单编号 |
| mesReturnCode |
String |
MES退货单号 |
| qualityResult |
String |
质检结论 |
| qualityReportUrl |
String |
质检报告附件URL |
| returnInboundTime |
DateTime |
退货入库完成时间 |
| refundTime |
DateTime |
退款完成时间 |
| items |
List<Item> |
退货明细行 |
2.4 分页查询退货申请 GET /aftersales/return/page
| 参数 |
类型 |
必填 |
说明 |
| pageNo |
Integer |
否 |
页码 |
| pageSize |
Integer |
否 |
每页大小 |
| no |
String |
否 |
退货单号 |
| ticketId |
Long |
否 |
关联工单编号 |
| customerId |
Long |
否 |
客户编号 |
| status |
Integer |
否 |
退货状态 |
| sceneType |
Integer |
否 |
场景类型 |
枚举值速查
工单状态 (AfterSaleTicketStatusEnum)
| 值 |
名称 |
说明 |
| 0 |
草稿 |
可编辑、可删除、可取消 |
| 10 |
处理中 |
可分配、可判定、可取消 |
| 30 |
待退货 |
退货流程进行中 |
| 40 |
待退款/换货 |
等待退款或换货完成 |
| 50 |
已完结 |
可关闭 |
| 60 |
已关闭 |
终态 |
| 99 |
已取消 |
终态 |
问题类型 (AfterSaleIssueTypeEnum)
| 值 |
名称 |
后续操作 |
| 0 |
未判定 |
创建工单时的初始值 |
| 1 |
一般问题 |
直接处理→完结 |
| 2 |
维修问题 |
创建维修记录→维修完成→完结 |
| 3 |
退货问题 |
创建退货申请→审核→MES退货→完结 |
退货状态 (AfterSaleReturnStatusEnum)
| 值 |
名称 |
说明 |
| 0 |
草稿 |
可审核 |
| 10 |
销售审核中 |
— |
| 20 |
审核通过-已同步MES |
等待MES处理 |
| 30 |
检验中 |
MES质检进行中 |
| 40 |
检验完成 |
等待入库 |
| 50 |
已入库 |
等待退款 |
| 60 |
退款/换货完成 |
— |
| 70 |
已关闭 |
终态 |
| 99 |
已取消 |
终态 |
退货类型
严重程度
| 值 |
名称 |
建议颜色 |
| 0 |
轻微 |
绿色 |
| 1 |
一般 |
蓝色 |
| 2 |
严重 |
橙色 |
| 3 |
致命 |
红色 |
场景类型 (sceneType)
| 值 |
名称 |
说明 |
| 1 |
我负责的 |
当前用户为负责人 |
| 2 |
我参与的 |
当前用户为处理人或被授权 |
| 3 |
下属负责的 |
下属为负责人 |
| 4 |
全部 |
管理员查看所有 |
字段展示规则
工单列表页
| 字段 |
展示位置 |
说明 |
| no |
列表 |
单号,可点击进入详情 |
| name |
列表 |
工单主题 |
| customerName |
列表 |
客户名称 |
| saleOrderNo |
列表 |
销售订单号 |
| issueType |
列表 |
问题类型,用标签区分颜色 |
| severityLevel |
列表 |
严重程度,用标签区分颜色 |
| status |
列表 |
状态,用标签区分颜色 |
| ownerUserName |
列表 |
负责人 |
| handlerUserName |
列表 |
处理人 |
| createTime |
列表 |
创建时间 |
| — |
搜索区 |
单号、主题、客户、订单、状态、问题类型 |
| — |
搜索区 |
场景类型 Tab:我负责的/我参与的/下属负责的/全部 |
工单详情页
| 字段 |
展示区域 |
说明 |
| no |
头部 |
单号 |
| name |
头部 |
工单主题 |
| status |
头部 |
状态标签 + 状态流转按钮 |
| customerId / customerName |
基本信息 |
客户选择器(创建时可选,创建后只读) |
| contactId / contactName |
基本信息 |
联系人选择器 |
| saleOrderId / saleOrderNo |
基本信息 |
销售订单选择器 |
| contractId |
基本信息 |
合同选择器 |
| issueType |
基本信息 |
问题类型(判定后显示) |
| issueCategory |
基本信息 |
问题分类(判定后显示) |
| issueDescription |
基本信息 |
问题描述 |
| severityLevel |
基本信息 |
严重程度(判定后显示) |
| ownerUserId / ownerUserName |
基本信息 |
负责人(创建时自动设为当前用户) |
| handlerUserId / handlerUserName |
基本信息 |
处理人(分配后显示) |
| returnId |
基本信息 |
关联退货申请(可点击跳转) |
| repairId |
基本信息 |
关联维修记录(可点击跳转) |
| resolvedTime |
基本信息 |
解决时间(完结后显示) |
| closedTime |
基本信息 |
关闭时间(关闭后显示) |
| remark |
基本信息 |
备注 |
| items |
明细表格 |
物料、批次、销售数量、退货数量、是否维修、是否退货 |
| createTime / creator |
底部信息 |
创建信息 |
| updateTime / updater |
底部信息 |
更新信息 |
退货申请列表页
| 字段 |
展示位置 |
说明 |
| no |
列表 |
退货单号 |
| ticketId |
列表 |
关联工单编号(可点击跳转) |
| customerName |
列表 |
客户名称 |
| returnType |
列表 |
退货类型标签 |
| totalReturnPrice |
列表 |
应退总金额 |
| actualRefundPrice |
列表 |
实退金额 |
| status |
列表 |
状态标签 |
| mesReturnCode |
列表 |
MES退货单号 |
| qualityResult |
列表 |
质检结论 |
| createTime |
列表 |
创建时间 |
退货申请详情页
| 字段 |
展示区域 |
说明 |
| no |
头部 |
退货单号 |
| status |
头部 |
状态标签 + 审核按钮(草稿状态显示) |
| ticketId |
基本信息 |
关联工单(可点击跳转) |
| customerName |
基本信息 |
客户名称 |
| saleOrderId |
基本信息 |
销售订单 |
| returnType |
基本信息 |
退货类型 |
| returnReason |
基本信息 |
退货原因 |
| defectCategory |
基本信息 |
缺陷分类 |
| totalReturnPrice |
基本信息 |
应退总金额 |
| actualRefundPrice |
基本信息 |
实退金额 |
| mesReturnId / mesReturnCode |
MES信息 |
MES退货单 |
| qualityResult |
MES信息 |
质检结论 |
| qualityReportUrl |
MES信息 |
质检报告链接 |
| returnInboundTime |
MES信息 |
入库时间 |
| refundTime |
退款信息 |
退款时间 |
| items |
明细表格 |
物料、批次、退货数量、单价、退货金额 |
| createTime / creator |
底部信息 |
创建信息 |
业务规则说明
工单操作按钮显隐规则
| 操作 |
草稿(0) |
处理中(10) |
待退货(30) |
待退款(40) |
已完结(50) |
已关闭(60) |
已取消(99) |
| 编辑 |
✅ |
❌ |
❌ |
❌ |
❌ |
❌ |
❌ |
| 删除 |
✅ |
❌ |
❌ |
❌ |
❌ |
❌ |
✅ |
| 分配处理人 |
✅ |
✅ |
❌ |
❌ |
❌ |
❌ |
❌ |
| 问题判定 |
❌ |
✅ |
❌ |
❌ |
❌ |
❌ |
❌ |
| 创建退货申请 |
❌ |
✅(判定为退货后) |
❌ |
❌ |
❌ |
❌ |
❌ |
| 完结 |
❌ |
✅ |
✅ |
✅ |
❌ |
❌ |
❌ |
| 关闭 |
❌ |
❌ |
❌ |
❌ |
✅ |
❌ |
❌ |
| 取消 |
✅ |
✅ |
❌ |
❌ |
❌ |
❌ |
❌ |
退货申请操作按钮显隐规则
| 操作 |
草稿(0) |
审核中(10) |
已同步MES(20) |
检验中(30) |
检验完成(40) |
已入库(50) |
退款完成(60) |
| 审核通过 |
✅ |
❌ |
❌ |
❌ |
❌ |
❌ |
❌ |
| 查看详情 |
✅ |
✅ |
✅ |
✅ |
✅ |
✅ |
✅ |
分页查询数据权限说明
- sceneType=1(我负责的):显示当前用户为
ownerUserId 的工单 + 数据交接的工单
- sceneType=2(我参与的):显示当前用户有读/写权限的工单
- sceneType=3(下属负责的):显示当前用户的下属负责的工单
- sceneType=4(全部):需要管理员权限(
aftersales:admin)
文件上传
售后模块使用统一的文件上传机制,不自行实现上传接口。
上传流程:
1. 调用 POST /system/storage-blob/upload 上传文件 → 获得 blobId
2. 调用 POST /system/storage-attachment/bind 绑定到业务记录
recordType 取值:
| 业务对象 |
recordType |
| 售后工单 |
aftersales_ticket |
| 售后退货申请 |
aftersales_return |
查询附件: GET /system/storage-attachment/list?recordType=aftersales_ticket&recordId=1
注意事项
- 明细行更新策略:更新工单时,明细行采用"先删后增"策略,前端必须传入完整的明细行列表(包括未修改的行),否则会导致明细行丢失。
- 单号生成:工单单号(AS前缀)和退货单号(RT前缀)由后端自动生成,前端不需要传入。
- 状态流转:所有状态变更操作(分配、判定、完结、关闭、取消、审核)后端都会校验当前状态是否允许,前端应根据按钮显隐规则控制操作入口。
- 退货审核后自动同步 MES:审核通过退货申请后,后端自动调用 MES 创建销售退货单,后续 MES 流程(质检→入库→退款)由回调自动推进状态,前端只需轮询刷新查看最新状态。
- 数据带入:创建退货申请时,必须从工单详情页跳转并带入
ticketId、customerId、saleOrderId 以及工单明细行中标记为"需要退货"的行。
- 权限控制:所有接口都有
@PreAuthorize 权限校验,前端菜单和按钮需要对应配置权限标识。
- 路由权限:需在
src/router/routes/modules/ 下新建 aftersales.ts 路由模块文件,并在菜单中注册"售后服务"一级菜单。
- API 模块:需在
src/api/ 下新建 aftersales/ 目录,创建 ticket.ts 和 return.ts 两个 API 文件。